’初始化项目‘

This commit is contained in:
sundongyu 2024-04-17 15:27:43 +08:00
parent 1cb56eebfa
commit 3661827a9f
468 changed files with 49376 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
unpackage
.hbuilder
.vite

17
App.vue Normal file
View File

@ -0,0 +1,17 @@
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
</style>

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

22
main.js Normal file
View File

@ -0,0 +1,22 @@
import App from './App'
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif

72
manifest.json Normal file
View File

@ -0,0 +1,72 @@
{
"name" : "足球人",
"appid" : "__UNI__9C758F7",
"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" : "3"
}

17
pages.json Normal file
View File

@ -0,0 +1,17 @@
{
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"uniIdRouter": {}
}

52
pages/index/index.vue Normal file
View File

@ -0,0 +1,52 @@
<template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<view class="text-area">
<text class="title">{{title}}</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'Hello'
}
},
onLoad() {
},
methods: {
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

10
uni.promisify.adaptor.js Normal file
View File

@ -0,0 +1,10 @@
uni.addInterceptor({
returnValue (res) {
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
return res;
}
return new Promise((resolve, reject) => {
res.then((res) => res[0] ? reject(res[0]) : resolve(res[1]));
});
},
});

View File

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

View File

@ -0,0 +1,64 @@
<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-plus 3.0</h3>
<h3 align="center">多平台快速开发的UI框架</h3>
[![stars](https://img.shields.io/github/stars/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus)
[![forks](https://img.shields.io/github/forks/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus)
[![issues](https://img.shields.io/github/issues/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus/issues)
[![release](https://img.shields.io/github/v/release/ijry/uview-plus?style=flat-square)](https://gitee.com/jry/uview-plus/releases)
[![license](https://img.shields.io/github/license/ijry/uview-plus?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
## 说明
uview-plus是uni-app全面兼容vue3/nvue的uni-app生态框架全面的组件和便捷的工具会让您信手拈来如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本感谢uView。
## [官方文档https://uview-plus.jiangruyi.com](https://uview-plus.jiangruyi.com)
## 预览
您可以通过**微信**扫码,查看最佳的演示效果。
<br>
<br>
<img src="https://uview-plus.jiangruyi.com/common/h5_qrcode.png" width="220" height="220" >
## 链接
- [官方文档](https://uview-plus.jiangruyi.com)
- [更新日志](https://uview-plus.jiangruyi.com/components/changelog.html)
- [升级指南](https://uview-plus.jiangruyi.com/components/changeGuide.html)
- [关于我们](https://uview-plus.jiangruyi.com/cooperation/about.html)
## 交流反馈
欢迎加入我们的QQ群交流反馈[点此跳转](https://uview-plus.jiangruyi.com/components/addQQGroup.html)
## 关于PR
> 我们非常乐意接受各位的优质PR但在此之前我希望您了解uview-plus是一个需要兼容多个平台的小程序、h5、ios app、android app包括nvue页面、vue页面。
> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢
## 安装
#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?name=uview-plus](https://ext.dcloud.net.cn/plugin?name=uview-plus)
请通过[官网安装文档](https://uview-plus.jiangruyi.com/components/install.html)了解更详细的内容
## 快速上手
请通过[快速上手](https://uview-plus.jiangruyi.com/components/quickstart.html)了解更详细的内容
## 使用方法
配置easycom规则后自动按需引入无需`import`组件,直接引用即可。
```html
<template>
<u-button text="按钮"></u-button>
</template>
```
## 版权信息
uview-plus遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议意味着您无需支付任何费用也无需授权即可将uview-plus应用到您的产品中。

View File

@ -0,0 +1,145 @@
## 3.2.102024-04-17
完善input清空事件App端失效的兼容性
修复日历组件二次打开后当前月份显示不正确
## 3.2.92024-04-16
组件内uni.$u用法改为import引入
规范化及兼容性增强
## 3.2.82024-04-15
修复up-tag语法错
## 3.2.72024-04-15
修复下拉菜单背景色在支付宝小程序无效
setConfig改为浅拷贝解决无法用import导入代替uni.$u.props设置
## 3.2.62024-04-14
修复某些情况下滑动单元格默认右侧按钮是展开的问题
## 3.2.52024-04-13
调整分段器尺寸及修复窗口大小改变时重新计算尺寸
多个组件支持cursor-pointer增强PC端体验
## 3.2.42024-04-12
初步支持typescript
## 3.2.32024-04-12
fix: 修复square属性在小程序下无效问题
fix:修复lastIndex异常导致的column异常问题
fix: alipayapp picker style
feat(button): 添加用户同意隐私协议事件回调
fix: input switch password
fix: 修复u-code组件keepRuning失效问题
feat: form-item添加labelPosition属性
新增dropdown组件
分段器支持内部current值
优化cell和action-sheet视觉大小
修复tabs文字换行
## 3.2.22024-04-11
修复换行符问题
## 3.2.12024-04-11
修复演示H5二维码
fix: #270 ReadMore 展开阅读更多内容变化兼容
fix: #238Calendar组件maxDate修改为不能小于minDate
checkbox支持独立使用
修复popup中在微信小程序中真机调试滚动失效
## 3.2.02024-04-10
修复轮播图在nvue显示
修复疑似u-slider名称被占用导致slider在App下不显示
解决微信小程序提示 Some selectors are not allowed in component wxss
示例中u-前缀统一为up-
增加瀑布流与图片懒加载组件
fix: #308修复tag组件缺失iconColor参数
fix: #297使用grid布局解决目前编译为抖音小程序无法开启virtualHost
## 3.1.522024-04-07
工具类方法调用import化改造
新增up-copy复制组件
## 3.1.512024-04-07
优化时间选择器自带输入框格式化显示
防止按钮文字换行
修复订单列表模板滑动
增加u-qrcode二维码组件
## 3.1.492024-03-27
日期时间组件支持自带输入框
fix: popup弹窗滚动穿透问题
fix: 修复小程序numberbox bug
## 3.1.482024-03-18
fix:[plugin:uni:pre-css] Unbalanced delimiter found in string
## 3.1.472024-03-18
fix: setConfig设置组件默认参数无效问题
fix: 修复自定义图标无效问题
feat: 增加u-form-item单独设置规则变量
fix#293小程序是自定义导航栏的时候即传了customNavHeight的时候会出现跳转偏移的情况
## 3.1.462024-01-29
beforeUnmount
## 3.1.452024-01-24
fix: #262ext组件为超链接的情况下size属性不生效
fix: #263最新版本3.1.42中微信小程序u-swipe-action-item报错
fix: #224最新版本3.1.42中微信小程序u-swipe-action-item报错
fix: #263支持支付宝小程序
fix: #261u-input在直接修改v-model的绑定值时每隔一次会无法出发change事件
优化折叠面板兼容微信小程序
## 3.1.422024-01-15
修复u-number-box默认值0时在小程序不显示值
优化u-code的timer判断
优化支付宝小程序下textarea字数统计兼容
优化u-calendar
## 3.1.412023-11-18
#215优化u-cell图标容器间距问题
## 3.1.402023-11-16
修复u-slider双向绑定
## 3.1.392023-11-10
修复头条小程序不支持env(safe-area-inset-bottom)
优化#201u-grid 指定列数导致闪烁
#193IndexList 索引列表 高度错误
其他优化
## 3.1.382023-10-08
修复u-slider
## 3.1.372023-09-13
完善emits定义及修复code-input双向数据绑定
## 3.1.362023-08-08
修复富文本事件名称大小写
## 3.1.352023-08-02
修复编译到支付宝小程序u-form报错
## 3.1.342023-07-27
修复App打包uni.$u.mpMixin方式sdk暂时不支持导致报错
## 3.1.332023-07-13
修复弹窗进入动画、模板页面样式等
## 3.1.312023-07-11
修复dayjs引用
## 3.0.82022-07-12
修复u-tag默认宽度撑满容器
## 3.0.72022-07-12
修复u-navbar自定义插槽演示示例
## 3.0.62022-07-11
修复u-image缺少emits申明
## 3.0.52022-07-11
修复u-upload缺少emits申明
## 3.0.42022-07-10
修复u-textarea/u-input/u-datetime-picker/u-number-box/u-radio-group/u-switch/u-rate在vue3下数据绑定
## 3.0.32022-07-09
启用自建演示二维码
## 3.0.22022-07-09
修复dayjs/clipboard等导致打包报错
## 3.0.12022-07-09
增加插件市场地址
## 3.0.02022-07-09
# uview-plus(vue3)初步发布

View File

@ -0,0 +1,80 @@
<template>
<uvForm
ref="uForm"
:model="model"
:rules="rules"
:errorType="errorType"
:borderBottom="borderBottom"
:labelPosition="labelPosition"
:labelWidth="labelWidth"
:labelAlign="labelAlign"
:labelStyle="labelStyle"
:customStyle="customStyle"
>
<slot />
</uvForm>
</template>
<script>
/**
* 此组件存在的理由是在nvue下u-form被uni-app官方占用了u-form在nvue中相当于form组件
* 所以在nvue下取名为u--form内部其实还是u-form.vue只不过做一层中转
*/
import uvForm from '../u-form/u-form.vue';
import props from '../u-form/props.js';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
export default {
// #ifdef MP-WEIXIN
name: 'u-form',
// #endif
// #ifndef MP-WEIXIN
name: 'u--form',
// #endif
mixins: [mpMixin, props, mixin],
components: {
uvForm
},
created() {
this.children = []
},
methods: {
//
setRules(rules) {
this.$refs.uForm.setRules(rules)
},
validate() {
/**
* 在微信小程序中通过this.$parent拿到的父组件是u--form而不是其内嵌的u-form
* 导致在u-form组件中拿不到对应的children数组从而校验无效所以这里每次调用u-form组件中的
* 对应方法的时候在小程序中都先将u--form的children赋值给u-form中的children
*/
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.validate()
},
validateField(value, callback) {
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.validateField(value, callback)
},
resetFields() {
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.resetFields()
},
clearValidate(props) {
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.clearValidate(props)
},
setMpData() {
this.$refs.uForm.children = this.children
}
},
}
</script>

View File

@ -0,0 +1,50 @@
<template>
<uvImage
:src="src"
:mode="mode"
:width="width"
:height="height"
:shape="shape"
:radius="radius"
:lazyLoad="lazyLoad"
:showMenuByLongpress="showMenuByLongpress"
:loadingIcon="loadingIcon"
:errorIcon="errorIcon"
:showLoading="showLoading"
:showError="showError"
:fade="fade"
:webp="webp"
:duration="duration"
:bgColor="bgColor"
:customStyle="customStyle"
@click="$emit('click')"
@error="$emit('error')"
@load="$emit('load')"
>
<template v-slot:loading>
<slot name="loading"></slot>
</template>
<template v-slot:error>
<slot name="error"></slot>
</template>
</uvImage>
</template>
<script>
/**
* 此组件存在的理由是在nvue下u-image被uni-app官方占用了u-image在nvue中相当于image组件
* 所以在nvue下取名为u--image内部其实还是u-iamge.vue只不过做一层中转
*/
import uvImage from '../u-image/u-image.vue';
import props from '../u-image/props.js';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
export default {
name: 'u--image',
mixins: [mpMixin, props, mixin],
components: {
uvImage
},
emits: ['click', 'error', 'load']
}
</script>

View File

@ -0,0 +1,74 @@
<template>
<uvInput
<!-- #ifdef VUE2 -->
:value="value"
@input="e => $emit('input', e)"
<!-- #endif -->
<!-- #ifdef VUE3 -->
:modelValue="modelValue"
@update:modelValue="e => $emit('update:modelValue', e)"
<!-- #endif -->
:type="type"
:fixed="fixed"
:disabled="disabled"
:disabledColor="disabledColor"
:clearable="clearable"
:password="password"
:maxlength="maxlength"
:placeholder="placeholder"
:placeholderClass="placeholderClass"
:placeholderStyle="placeholderStyle"
:showWordLimit="showWordLimit"
:confirmType="confirmType"
:confirmHold="confirmHold"
:holdKeyboard="holdKeyboard"
:focus="focus"
:autoBlur="autoBlur"
:disableDefaultPadding="disableDefaultPadding"
:cursor="cursor"
:cursorSpacing="cursorSpacing"
:selectionStart="selectionStart"
:selectionEnd="selectionEnd"
:adjustPosition="adjustPosition"
:inputAlign="inputAlign"
:fontSize="fontSize"
:color="color"
:prefixIcon="prefixIcon"
:suffixIcon="suffixIcon"
:suffixIconStyle="suffixIconStyle"
:prefixIconStyle="prefixIconStyle"
:border="border"
:readonly="readonly"
:shape="shape"
:customStyle="customStyle"
:formatter="formatter"
:ignoreCompositionEvent="ignoreCompositionEvent"
>
<!-- #ifdef MP -->
<slot name="prefix"></slot>
<slot name="suffix"></slot>
<!-- #endif -->
<!-- #ifndef MP -->
<slot name="prefix" slot="prefix"></slot>
<slot name="suffix" slot="suffix"></slot>
<!-- #endif -->
</uvInput>
</template>
<script>
/**
* 此组件存在的理由是在nvue下u-input被uni-app官方占用了u-input在nvue中相当于input组件
* 所以在nvue下取名为u--input内部其实还是u-input.vue只不过做一层中转
*/
import uvInput from '../u-input/u-input.vue';
import props from '../u-input/props.js';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
export default {
name: 'u--input',
mixins: [mpMixin, props, mixin],
components: {
uvInput
},
}
</script>

View File

@ -0,0 +1,45 @@
<template>
<uvText
:type="type"
:show="show"
:text="text"
:prefixIcon="prefixIcon"
:suffixIcon="suffixIcon"
:mode="mode"
:href="href"
:format="format"
:call="call"
:openType="openType"
:bold="bold"
:block="block"
:lines="lines"
:color="color"
:decoration="decoration"
:size="size"
:iconStyle="iconStyle"
:margin="margin"
:lineHeight="lineHeight"
:align="align"
:wordWrap="wordWrap"
:customStyle="customStyle"
></uvText>
</template>
<script>
/**
* 此组件存在的理由是在nvue下u-text被uni-app官方占用了u-text在nvue中相当于input组件
* 所以在nvue下取名为u--input内部其实还是u-text.vue只不过做一层中转
* 不使用v-bind="$attrs"而是分开独立写传参是因为微信小程序不支持此写法
*/
import uvText from "../u-text/u-text.vue";
import props from "../u-text/props.js";
import mpMixin from '../../libs/mixin/mpMixin.js'
import mixin from '../../libs/mixin/mixin.js'
export default {
name: "u--text",
mixins: [mpMixin, mixin, props,],
components: {
uvText,
},
};
</script>

View File

@ -0,0 +1,47 @@
<template>
<uvTextarea
:value="value"
:modelValue="modelValue"
:placeholder="placeholder"
:height="height"
:confirmType="confirmType"
:disabled="disabled"
:count="count"
:focus="focus"
:autoHeight="autoHeight"
:fixed="fixed"
:cursorSpacing="cursorSpacing"
:cursor="cursor"
:showConfirmBar="showConfirmBar"
:selectionStart="selectionStart"
:selectionEnd="selectionEnd"
:adjustPosition="adjustPosition"
:disableDefaultPadding="disableDefaultPadding"
:holdKeyboard="holdKeyboard"
:maxlength="maxlength"
:border="border"
:customStyle="customStyle"
:formatter="formatter"
:ignoreCompositionEvent="ignoreCompositionEvent"
@input="e => $emit('input', e)"
@update:modelValue="e => $emit('update:modelValue', e)"
></uvTextarea>
</template>
<script>
/**
* 此组件存在的理由是在nvue下u--textarea被uni-app官方占用了u-textarea在nvue中相当于textarea组件
* 所以在nvue下取名为u--textarea内部其实还是u-textarea.vue只不过做一层中转
*/
import uvTextarea from '../u-textarea/u-textarea.vue';
import props from '../u-textarea/props.js';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
export default {
name: 'u--textarea',
mixins: [mpMixin, props, mixin],
components: {
uvTextarea
},
}
</script>

View File

@ -0,0 +1,55 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 操作菜单是否展示 默认false
show: {
type: Boolean,
default: () => defProps.actionSheet.show
},
// 标题
title: {
type: String,
default: () => defProps.actionSheet.title
},
// 选项上方的描述信息
description: {
type: String,
default: () => defProps.actionSheet.description
},
// 数据
actions: {
type: Array,
default: () => defProps.actionSheet.actions
},
// 取消按钮的文字,不为空时显示按钮
cancelText: {
type: String,
default: () => defProps.actionSheet.cancelText
},
// 点击某个菜单项时是否关闭弹窗
closeOnClickAction: {
type: Boolean,
default: () => defProps.actionSheet.closeOnClickAction
},
// 处理底部安全区默认true
safeAreaInsetBottom: {
type: Boolean,
default: () => defProps.actionSheet.safeAreaInsetBottom
},
// 小程序的打开方式
openType: {
type: String,
default: () => defProps.actionSheet.openType
},
// 点击遮罩是否允许关闭 (默认true)
closeOnClickOverlay: {
type: Boolean,
default: () => defProps.actionSheet.closeOnClickOverlay
},
// 圆角值
round: {
type: [Boolean, String, Number],
default: () => defProps.actionSheet.round
}
}
}

View File

@ -0,0 +1,281 @@
<template>
<u-popup
:show="show"
mode="bottom"
@close="closeHandler"
:safeAreaInsetBottom="safeAreaInsetBottom"
:round="round"
>
<view class="u-action-sheet">
<view
class="u-action-sheet__header"
v-if="title"
>
<text class="u-action-sheet__header__title u-line-1">{{title}}</text>
<view
class="u-action-sheet__header__icon-wrap"
@tap.stop="cancel"
>
<u-icon
name="close"
size="17"
color="#c8c9cc"
bold
></u-icon>
</view>
</view>
<text
class="u-action-sheet__description"
:style="[{
marginTop: `${title && description ? 0 : '18px'}`
}]"
v-if="description"
>{{description}}</text>
<slot>
<u-line v-if="description"></u-line>
<view class="u-action-sheet__item-wrap">
<view :key="index" v-for="(item, index) in actions">
<!-- #ifdef MP -->
<button
class="u-reset-button"
:openType="item.openType"
@getuserinfo="onGetUserInfo"
@contact="onContact"
@getphonenumber="onGetPhoneNumber"
@error="onError"
@launchapp="onLaunchApp"
@opensetting="onOpenSetting"
:lang="lang"
:session-from="sessionFrom"
:send-message-title="sendMessageTitle"
:send-message-path="sendMessagePath"
:send-message-img="sendMessageImg"
:show-message-card="showMessageCard"
:app-parameter="appParameter"
@tap="selectHandler(index)"
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
>
<!-- #endif -->
<view
class="u-action-sheet__item-wrap__item"
@tap.stop="selectHandler(index)"
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
:hover-stay-time="150"
>
<template v-if="!item.loading">
<text
class="u-action-sheet__item-wrap__item__name"
:style="[itemStyle(index)]"
>{{ item.name }}</text>
<text
v-if="item.subname"
class="u-action-sheet__item-wrap__item__subname"
>{{ item.subname }}</text>
</template>
<u-loading-icon
v-else
custom-class="van-action-sheet__loading"
size="18"
mode="circle"
/>
</view>
<!-- #ifdef MP -->
</button>
<!-- #endif -->
<u-line v-if="index !== actions.length - 1"></u-line>
</view>
</view>
</slot>
<u-gap
bgColor="#eaeaec"
height="6"
v-if="cancelText"
></u-gap>
<view hover-class="u-action-sheet--hover">
<text
@touchmove.stop.prevent
:hover-stay-time="150"
v-if="cancelText"
class="u-action-sheet__cancel-text"
@tap="cancel"
>{{cancelText}}</text>
</view>
</view>
</u-popup>
</template>
<script>
import openType from '../../libs/mixin/openType'
import button from '../../libs/mixin/button'
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit } from '../../libs/function/index';
/**
* ActionSheet 操作菜单
* @description 本组件用于从底部弹出一个操作菜单供用户选择并返回结果本组件功能类似于uni的uni.showActionSheetAPI配置更加灵活所有平台都表现一致
* @tutorial https://ijry.github.io/uview-plus/components/actionSheet.html
*
* @property {Boolean} show 操作菜单是否展示 默认 false
* @property {String} title 操作菜单标题
* @property {String} description 选项上方的描述信息
* @property {Array<Object>} actions 按钮的文字数组见官方文档示例
* @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮
* @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 默认 true
* @property {Boolean} safeAreaInsetBottom 处理底部安全区 默认 true
* @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting getPhoneNumber error )
* @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true )
* @property {Number|String} round 圆角值默认无圆角 (默认 0 )
* @property {String} lang 指定返回用户信息的语言zh_CN 简体中文zh_TW 繁体中文en 英文
* @property {String} sessionFrom 会话来源openType="contact"时有效
* @property {String} sendMessageTitle 会话内消息卡片标题openType="contact"时有效
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径openType="contact"时有效
* @property {String} sendMessageImg 会话内消息卡片图片openType="contact"时有效
* @property {Boolean} showMessageCard 是否显示会话内消息卡片设置此参数为 true用户进入客服会话会在右下角显示"可能要发送的小程序"提示用户点击后可以快速发送小程序消息openType="contact"时有效 默认 false
* @property {String} appParameter 打开 APP APP 传递的参数openType=launchApp 时有效
*
* @event {Function} select 点击ActionSheet列表项时触发
* @event {Function} close 点击取消按钮时触发
* @event {Function} getuserinfo 用户点击该按钮时会返回获取到的用户信息回调的 detail 数据与 wx.getUserInfo 返回的一致openType="getUserInfo"时有效
* @event {Function} contact 客服消息回调openType="contact"时有效
* @event {Function} getphonenumber 获取用户手机号回调openType="getPhoneNumber"时有效
* @event {Function} error 当使用开放能力时发生错误的回调openType="error"时有效
* @event {Function} launchapp 打开 APP 成功的回调openType="launchApp"时有效
* @event {Function} opensetting 在打开授权设置页后回调openType="openSetting"时有效
* @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
*/
export default {
name: "u-action-sheet",
// propsmethodsmixin
mixins: [openType, button, mixin, props],
data() {
return {
}
},
computed: {
//
itemStyle() {
return (index) => {
let style = {};
if (this.actions[index].color) style.color = this.actions[index].color
if (this.actions[index].fontSize) style.fontSize = addUnit(this.actions[index].fontSize)
//
if (this.actions[index].disabled) style.color = '#c0c4cc'
return style;
}
},
},
emits: ["close", "select"],
methods: {
closeHandler() {
// close
if(this.closeOnClickOverlay) {
this.$emit('close')
}
},
//
cancel() {
this.$emit('close')
},
selectHandler(index) {
const item = this.actions[index]
if (item && !item.disabled && !item.loading) {
this.$emit('select', item)
if (this.closeOnClickAction) {
this.$emit('close')
}
}
},
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-action-sheet-reset-button-width:100% !default;
$u-action-sheet-title-font-size: 16px !default;
$u-action-sheet-title-padding: 12px 30px !default;
$u-action-sheet-title-color: $u-main-color !default;
$u-action-sheet-header-icon-wrap-right:15px !default;
$u-action-sheet-header-icon-wrap-top:15px !default;
$u-action-sheet-description-font-size:13px !default;
$u-action-sheet-description-color:14px !default;
$u-action-sheet-description-margin: 18px 15px !default;
$u-action-sheet-item-wrap-item-padding:17px !default;
$u-action-sheet-item-wrap-name-font-size:16px !default;
$u-action-sheet-item-wrap-subname-font-size:13px !default;
$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
$u-action-sheet-item-wrap-subname-margin-top:10px !default;
$u-action-sheet-cancel-text-font-size:16px !default;
$u-action-sheet-cancel-text-color:$u-content-color !default;
$u-action-sheet-cancel-text-font-size:15px !default;
$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
.u-reset-button {
width: $u-action-sheet-reset-button-width;
}
.u-action-sheet {
text-align: center;
&__header {
position: relative;
padding: $u-action-sheet-title-padding;
&__title {
font-size: $u-action-sheet-title-font-size;
color: $u-action-sheet-title-color;
font-weight: bold;
text-align: center;
}
&__icon-wrap {
position: absolute;
right: $u-action-sheet-header-icon-wrap-right;
top: $u-action-sheet-header-icon-wrap-top;
}
}
&__description {
font-size: $u-action-sheet-description-font-size;
color: $u-tips-color;
margin: $u-action-sheet-description-margin;
text-align: center;
}
&__item-wrap {
&__item {
padding: $u-action-sheet-item-wrap-item-padding;
@include flex;
align-items: center;
justify-content: center;
flex-direction: column;
&__name {
font-size: $u-action-sheet-item-wrap-name-font-size;
color: $u-main-color;
text-align: center;
}
&__subname {
font-size: $u-action-sheet-item-wrap-subname-font-size;
color: $u-action-sheet-item-wrap-subname-color;
margin-top: $u-action-sheet-item-wrap-subname-margin-top;
text-align: center;
}
}
}
&__cancel-text {
font-size: $u-action-sheet-cancel-text-font-size;
color: $u-action-sheet-cancel-text-color;
text-align: center;
padding: $u-action-sheet-cancel-text-font-size;
}
&--hover {
background-color: $u-action-sheet-cancel-text-hover-background-color;
}
}
</style>

View File

@ -0,0 +1,60 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 图片地址Array<String>|Array<Object>形式
urls: {
type: Array,
default: () => defProps.album.urls
},
// 指定从数组的对象元素中读取哪个属性作为图片地址
keyName: {
type: String,
default: () => defProps.album.keyName
},
// 单图时,图片长边的长度
singleSize: {
type: [String, Number],
default: () => defProps.album.singleSize
},
// 多图时,图片边长
multipleSize: {
type: [String, Number],
default: () => defProps.album.multipleSize
},
// 多图时,图片水平和垂直之间的间隔
space: {
type: [String, Number],
default: () => defProps.album.space
},
// 单图时,图片缩放裁剪的模式
singleMode: {
type: String,
default: () => defProps.album.singleMode
},
// 多图时,图片缩放裁剪的模式
multipleMode: {
type: String,
default: () => defProps.album.multipleMode
},
// 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
maxCount: {
type: [String, Number],
default: () => defProps.album.maxCount
},
// 是否可以预览图片
previewFullImage: {
type: Boolean,
default: () => defProps.album.previewFullImage
},
// 每行展示图片数量如设置singleSize和multipleSize将会无效
rowCount: {
type: [String, Number],
default: () => defProps.album.rowCount
},
// 超出maxCount时是否显示查看更多的提示
showMore: {
type: Boolean,
default: () => defProps.album.showMore
}
}
}

View File

@ -0,0 +1,264 @@
<template>
<view class="u-album">
<view
class="u-album__row"
ref="u-album__row"
v-for="(arr, index) in showUrls"
:forComputedUse="albumWidth"
:key="index"
>
<view
class="u-album__row__wrapper"
v-for="(item, index1) in arr"
:key="index1"
:style="[imageStyle(index + 1, index1 + 1)]"
@tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''"
>
<image
:src="getSrc(item)"
:mode="
urls.length === 1
? imageHeight > 0
? singleMode
: 'widthFix'
: multipleMode
"
:style="[
{
width: imageWidth,
height: imageHeight
}
]"
></image>
<view
v-if="
showMore &&
urls.length > rowCount * showUrls.length &&
index === showUrls.length - 1 &&
index1 === showUrls[showUrls.length - 1].length - 1
"
class="u-album__row__wrapper__text"
>
<up-text
:text="`+${urls.length - maxCount}`"
color="#fff"
:size="multipleSize * 0.3"
align="center"
customStyle="justify-content: center"
></up-text>
</view>
</view>
</view>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, sleep } from '../../libs/function/index';
import test from '../../libs/function/test';
// #ifdef APP-NVUE
// weexKPIdom
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* Album 相册
* @description 本组件提供一个类似相册的功能让开发者开发起来更加得心应手减少重复的模板代码
* @tutorial https://ijry.github.io/uview-plus/components/album.html
*
* @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
* @property {String | Number} singleSize 单图时图片长边的长度 默认 180
* @property {String | Number} multipleSize 多图时图片边长 默认 70
* @property {String | Number} space 多图时图片水平和垂直之间的间隔 默认 6
* @property {String} singleMode 单图时图片缩放裁剪的模式 默认 'scaleToFill'
* @property {String} multipleMode 多图时图片缩放裁剪的模式 默认 'aspectFill'
* @property {String | Number} maxCount 取消按钮的提示文字 默认 9
* @property {Boolean} previewFullImage 是否可以预览图片 默认 true
* @property {String | Number} rowCount 每行展示图片数量如设置singleSize和multipleSize将会无效 默认 3
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 默认 true
*
* @event {Function} albumWidth 某些特殊的情况下需要让文字与相册的宽度相等这里事件的形式对外发送 回调参数 width
* @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
*/
export default {
name: 'u-album',
mixins: [mpMixin, mixin, props],
data() {
return {
//
singleWidth: 0,
//
singleHeight: 0,
//
singlePercent: 0.6
}
},
watch: {
urls: {
immediate: true,
handler(newVal) {
if (newVal.length === 1) {
this.getImageRect()
}
}
}
},
emits: ["albumWidth"],
computed: {
imageStyle() {
return (index1, index2) => {
const { space, rowCount, multipleSize, urls } = this,
{ addUnit, addStyle } = uni.$u,
rowLen = this.showUrls.length,
allLen = this.urls.length
const style = {
marginRight: addUnit(space),
marginBottom: addUnit(space)
}
//
if (index1 === rowLen) style.marginBottom = 0
//
if (
index2 === rowCount ||
(index1 === rowLen &&
index2 === this.showUrls[index1 - 1].length)
)
style.marginRight = 0
return style
}
},
//
showUrls() {
const arr = []
this.urls.map((item, index) => {
//
if (index + 1 <= this.maxCount) {
//
const itemIndex = Math.floor(index / this.rowCount)
//
if (!arr[itemIndex]) {
arr[itemIndex] = []
}
arr[itemIndex].push(item)
}
})
return arr
},
imageWidth() {
return addUnit(
this.urls.length === 1 ? this.singleWidth : this.multipleSize
)
},
imageHeight() {
return addUnit(
this.urls.length === 1 ? this.singleHeight : this.multipleSize
)
},
// computedurls
//
albumWidth() {
let width = 0
if (this.urls.length === 1) {
width = this.singleWidth
} else {
width =
this.showUrls[0].length * this.multipleSize +
this.space * (this.showUrls[0].length - 1)
}
this.$emit('albumWidth', width)
return width
}
},
methods: {
//
onPreviewTap(url) {
const urls = this.urls.map((item) => {
return this.getSrc(item)
})
uni.previewImage({
current: url,
urls
})
},
//
getSrc(item) {
return test.object(item)
? (this.keyName && item[this.keyName]) || item.src
: item
},
//
// download
// (singlePercent)
getImageRect() {
const src = this.getSrc(this.urls[0])
uni.getImageInfo({
src,
success: (res) => {
//
const isHorizotal = res.width >= res.height
this.singleWidth = isHorizotal
? this.singleSize
: (res.width / res.height) * this.singleSize
this.singleHeight = !isHorizotal
? this.singleSize
: (res.height / res.width) * this.singleWidth
},
fail: () => {
this.getComponentWidth()
}
})
},
//
async getComponentWidth() {
// dom
await sleep(30)
// #ifndef APP-NVUE
this.$uGetRect('.u-album__row').then((size) => {
this.singleWidth = size.width * this.singlePercent
})
// #endif
// #ifdef APP-NVUE
// ref="u-album__row"forthis.$refs['u-album__row']
const ref = this.$refs['u-album__row'][0]
ref &&
dom.getComponentRect(ref, (res) => {
this.singleWidth = res.size.width * this.singlePercent
})
// #endif
}
}
}
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
.u-album {
@include flex(column);
&__row {
@include flex(row);
flex-wrap: wrap;
&__wrapper {
position: relative;
&__text {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
@include flex(row);
justify-content: center;
align-items: center;
}
}
}
}
</style>

View File

@ -0,0 +1,45 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 显示文字
title: {
type: String,
default: () => defProps.alert.title
},
// 主题success/warning/info/error
type: {
type: String,
default: () => defProps.alert.type
},
// 辅助性文字
description: {
type: String,
default: () => defProps.alert.description
},
// 是否可关闭
closable: {
type: Boolean,
default: () => defProps.alert.closable
},
// 是否显示图标
showIcon: {
type: Boolean,
default: () => defProps.alert.showIcon
},
// 浅或深色调light-浅色dark-深色
effect: {
type: String,
default: () => defProps.alert.effect
},
// 文字是否居中
center: {
type: Boolean,
default: () => defProps.alert.center
},
// 字体大小
fontSize: {
type: [String, Number],
default: () => defProps.alert.fontSize
}
}
}

View File

@ -0,0 +1,249 @@
<template>
<u-transition
mode="fade"
:show="show"
>
<view
class="u-alert"
:class="[`u-alert--${type}--${effect}`]"
@tap.stop="clickHandler"
:style="[addStyle(customStyle)]"
>
<view
class="u-alert__icon"
v-if="showIcon"
>
<u-icon
:name="iconName"
size="18"
:color="iconColor"
></u-icon>
</view>
<view
class="u-alert__content"
:style="[{
paddingRight: closable ? '20px' : 0
}]"
>
<text
class="u-alert__content__title"
v-if="title"
:style="[{
fontSize: addUnit(fontSize),
textAlign: center ? 'center' : 'left'
}]"
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
>{{ title }}</text>
<text
class="u-alert__content__desc"
v-if="description"
:style="[{
fontSize: addUnit(fontSize),
textAlign: center ? 'center' : 'left'
}]"
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
>{{ description }}</text>
</view>
<view
class="u-alert__close"
v-if="closable"
@tap.stop="closeHandler"
>
<u-icon
name="close"
:color="iconColor"
size="15"
></u-icon>
</view>
</view>
</u-transition>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, addStyle } from '../../libs/function/index';
/**
* Alert 警告提示
* @description 警告提示展现需要关注的信息
* @tutorial https://ijry.github.io/uview-plus/components/alertTips.html
*
* @property {String} title 显示的文字
* @property {String} type 使用预设的颜色 默认 'warning'
* @property {String} description 辅助性文字颜色比title浅一点字号也小一点可选
* @property {Boolean} closable 关闭按钮(默认为叉号icon图标) 默认 false
* @property {Boolean} showIcon 是否显示左边的辅助图标 默认 false
* @property {String} effect 多图时图片缩放裁剪的模式 默认 'light'
* @property {Boolean} center 文字是否居中 默认 false
* @property {String | Number} fontSize 字体大小 默认 14
* @property {Object} customStyle 定义需要用到的外部样式
* @event {Function} click 点击组件时触发
* @example <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert>
*/
export default {
name: 'u-alert',
mixins: [mpMixin, mixin, props],
data() {
return {
show: true
}
},
computed: {
iconColor() {
return this.effect === 'light' ? this.type : '#fff'
},
//
iconName() {
switch (this.type) {
case 'success':
return 'checkmark-circle-fill';
break;
case 'error':
return 'close-circle-fill';
break;
case 'warning':
return 'error-circle-fill';
break;
case 'info':
return 'info-circle-fill';
break;
case 'primary':
return 'more-circle-fill';
break;
default:
return 'error-circle-fill';
}
}
},
emits: ["click"],
methods: {
addUnit,
addStyle,
//
clickHandler() {
this.$emit('click')
},
//
closeHandler() {
this.show = false
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-alert {
position: relative;
background-color: $u-primary;
padding: 8px 10px;
@include flex(row);
align-items: center;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
&--primary--dark {
background-color: $u-primary;
}
&--primary--light {
background-color: #ecf5ff;
}
&--error--dark {
background-color: $u-error;
}
&--error--light {
background-color: #FEF0F0;
}
&--success--dark {
background-color: $u-success;
}
&--success--light {
background-color: #f5fff0;
}
&--warning--dark {
background-color: $u-warning;
}
&--warning--light {
background-color: #FDF6EC;
}
&--info--dark {
background-color: $u-info;
}
&--info--light {
background-color: #f4f4f5;
}
&__icon {
margin-right: 5px;
}
&__content {
@include flex(column);
flex: 1;
&__title {
color: $u-main-color;
font-size: 14px;
font-weight: bold;
color: #fff;
margin-bottom: 2px;
}
&__desc {
color: $u-main-color;
font-size: 14px;
flex-wrap: wrap;
color: #fff;
}
}
&__title--dark,
&__desc--dark {
color: #FFFFFF;
}
&__text--primary--light,
&__text--primary--light {
color: $u-primary;
}
&__text--success--light,
&__text--success--light {
color: $u-success;
}
&__text--warning--light,
&__text--warning--light {
color: $u-warning;
}
&__text--error--light,
&__text--error--light {
color: $u-error;
}
&__text--info--light,
&__text--info--light {
color: $u-info;
}
&__close {
position: absolute;
top: 11px;
right: 10px;
}
}
</style>

View File

@ -0,0 +1,53 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 头像图片组
urls: {
type: Array,
default: () => defProps.avatarGroup.urls
},
// 最多展示的头像数量
maxCount: {
type: [String, Number],
default: () => defProps.avatarGroup.maxCount
},
// 头像形状
shape: {
type: String,
default: () => defProps.avatarGroup.shape
},
// 图片裁剪模式
mode: {
type: String,
default: () => defProps.avatarGroup.mode
},
// 超出maxCount时是否显示查看更多的提示
showMore: {
type: Boolean,
default: () => defProps.avatarGroup.showMore
},
// 头像大小
size: {
type: [String, Number],
default: () => defProps.avatarGroup.size
},
// 指定从数组的对象元素中读取哪个属性作为图片地址
keyName: {
type: String,
default: () => defProps.avatarGroup.keyName
},
// 头像之间的遮挡比例
gap: {
type: [String, Number],
validator(value) {
return value >= 0 && value <= 1
},
default: () => defProps.avatarGroup.gap
},
// 需额外显示的值
extraValue: {
type: [Number, String],
default: () => defProps.avatarGroup.extraValue
}
}
}

View File

@ -0,0 +1,110 @@
<template>
<view class="u-avatar-group">
<view
class="u-avatar-group__item"
v-for="(item, index) in showUrl"
:key="index"
:style="{
marginLeft: index === 0 ? 0 : addUnit(-size * gap)
}"
>
<u-avatar
:size="size"
:shape="shape"
:mode="mode"
:src="testObject(item) ? keyName && item[keyName] || item.url : item"
></u-avatar>
<view
class="u-avatar-group__item__show-more"
v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
@tap="clickHandler"
>
<up-text
color="#ffffff"
:size="size * 0.4"
:text="`+${extraValue || urls.length - showUrl.length}`"
align="center"
customStyle="justify-content: center"
></up-text>
</view>
</view>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit } from '../../libs/function/index';
import test from '../../libs/function/test';
/**
* AvatarGroup 头像组
* @description 本组件一般用于展示头像的地方如个人中心或者评论列表页的用户头像展示等场所
* @tutorial https://ijry.github.io/uview-plus/components/avatar.html
*
* @property {Array} urls 头像图片组 默认 []
* @property {String | Number} maxCount 最多展示的头像数量 默认 5
* @property {String} shape 头像形状 'circle' (默认) | 'square'
* @property {String} mode 图片裁剪模式默认 'scaleToFill'
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 默认 true
* @property {String | Number} size 头像大小 默认 40
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
* @property {String | Number} gap 头像之间的遮挡比例0.4代表遮挡40% 默认 0.5
* @property {String | Number} extraValue 需额外显示的值
* @event {Function} showMore 头像组更多点击
* @example <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
*/
export default {
name: 'u-avatar-group',
mixins: [mpMixin, mixin, props],
data() {
return {
}
},
computed: {
showUrl() {
return this.urls.slice(0, this.maxCount)
}
},
emits: ["showMore"],
methods: {
addUnit,
testObject: test.object,
clickHandler() {
this.$emit('showMore')
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-avatar-group {
@include flex;
&__item {
margin-left: -10px;
position: relative;
&--no-indent {
// 使:first-childnvue
margin-left: 0;
}
&__show-more {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
@include flex;
align-items: center;
justify-content: center;
border-radius: 100px;
}
}
}
</style>

View File

@ -0,0 +1,80 @@
import defProps from '../../libs/config/props.js';
import test from '../../libs/function/test';
export default {
props: {
// 头像图片路径(不能为相对路径)
src: {
type: String,
default: () => defProps.avatar.src
},
// 头像形状circle-圆形square-方形
shape: {
type: String,
default: () => defProps.avatar.shape
},
// 头像尺寸
size: {
type: [String, Number],
default: () => defProps.avatar.size
},
// 裁剪模式
mode: {
type: String,
default: () => defProps.avatar.mode
},
// 显示的文字
text: {
type: String,
default: () => defProps.avatar.text
},
// 背景色
bgColor: {
type: String,
default: () => defProps.avatar.bgColor
},
// 文字颜色
color: {
type: String,
default: () => defProps.avatar.color
},
// 文字大小
fontSize: {
type: [String, Number],
default: () => defProps.avatar.fontSize
},
// 显示的图标
icon: {
type: String,
default: () => defProps.avatar.icon
},
// 显示小程序头像只对百度微信QQ小程序有效
mpAvatar: {
type: Boolean,
default: () => defProps.avatar.mpAvatar
},
// 是否使用随机背景色
randomBgColor: {
type: Boolean,
default: () => defProps.avatar.randomBgColor
},
// 加载失败的默认头像(组件有内置默认图片)
defaultUrl: {
type: String,
default: () => defProps.avatar.defaultUrl
},
// 如果配置了randomBgColor为true且配置了此值则从默认的背景色数组中取出对应索引的颜色值取值0-19之间
colorIndex: {
type: [String, Number],
// 校验参数规则索引在0-19之间
validator(n) {
return test.range(n, [0, 19]) || n === ''
},
default: () => defProps.avatar.colorIndex
},
// 组件标识符
name: {
type: String,
default: () => defProps.avatar.name
}
}
}

View File

@ -0,0 +1,180 @@
<template>
<view
class="u-avatar"
:class="[`u-avatar--${shape}`]"
:style="[{
backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : random(0, 19)] : bgColor) : 'transparent',
width: addUnit(size),
height: addUnit(size),
}, addStyle(customStyle)]"
@tap="clickHandler"
>
<slot>
<!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU -->
<open-data
v-if="mpAvatar && allowMp"
type="userAvatarUrl"
:style="[{
width: addUnit(size),
height: addUnit(size)
}]"
/>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU -->
<template v-if="mpAvatar && allowMp"></template>
<!-- #endif -->
<u-icon
v-else-if="icon"
:name="icon"
:size="fontSize"
:color="color"
></u-icon>
<up-text
v-else-if="text"
:text="text"
:size="fontSize"
:color="color"
align="center"
customStyle="justify-content: center"
></up-text>
<image
class="u-avatar__image"
v-else
:class="[`u-avatar__image--${shape}`]"
:src="avatarUrl || defaultUrl"
:mode="mode"
@error="errorHandler"
:style="[{
width: addUnit(size),
height: addUnit(size)
}]"
></image>
</slot>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addStyle, addUnit, random } from '../../libs/function/index';
const 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://ijry.github.io/uview-plus/components/avatar.html
*
* @property {String} src 头像路径如加载失败将会显示默认头像(不能为相对路径)
* @property {String} shape 头像形状 circle (默认) | square
* @property {String | Number} size 头像尺寸可以为指定字符串(large, default, mini)或者数值 默认 40
* @property {String} mode 头像图片的裁剪类型与uni的image组件的mode参数一致如效果达不到需求可尝试传widthFix值 默认 'scaleToFill'
* @property {String} text 用文字替代图片级别优先于src
* @property {String} bgColor 背景颜色一般显示文字时用 默认 '#c0c4cc'
* @property {String} color 文字颜色 默认 '#ffffff'
* @property {String | Number} fontSize 文字大小 默认 18
* @property {String} icon 显示的图标
* @property {Boolean} mpAvatar 显示小程序头像只对百度微信QQ小程序有效 默认 false
* @property {Boolean} randomBgColor 是否使用随机背景色 默认 false
* @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片)
* @property {String | Number} colorIndex 如果配置了randomBgColor为true且配置了此值则从默认的背景色数组中取出对应索引的颜色值取值0-19之间
* @property {String} name 组件标识符 默认 'level'
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function} click 点击组件时触发 index: 用户传递的标识符
* @example <u-avatar :src="src" mode="square"></u-avatar>
*/
export default {
name: 'u-avatar',
mixins: [mpMixin, mixin, props],
data() {
return {
// randomBgColortrue
colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2',
'#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee',
'#73d1f1',
'#80a7dc'
],
avatarUrl: this.src,
allowMp: false
}
},
watch: {
// srcavatarUrlsrc
// props
src: {
immediate: true,
handler(newVal) {
this.avatarUrl = newVal
// srcerrorsrc''
if(!newVal) {
this.errorHandler()
}
}
}
},
computed: {
imageStyle() {
const style = {}
return style
}
},
created() {
this.init()
},
emits: ["click"],
methods: {
addStyle,
addUnit,
random,
init() {
// open-data
// uni.getUserInfo()
//
// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU
this.allowMp = true
// #endif
},
// name"/"
isImg() {
return this.src.indexOf('/') !== -1
},
//
errorHandler() {
this.avatarUrl = this.defaultUrl || base64Avatar
},
clickHandler() {
this.$emit('click', this.name)
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-avatar {
@include flex;
align-items: center;
justify-content: center;
&--circle {
border-radius: 100px;
}
&--square {
border-radius: 4px;
}
&__image {
&--circle {
border-radius: 100px;
overflow: hidden;
}
&--square {
border-radius: 4px;
}
}
}
</style>

View File

@ -0,0 +1,55 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 返回顶部的形状circle-圆形square-方形
mode: {
type: String,
default: () => defProps.backtop.mode
},
// 自定义图标
icon: {
type: String,
default: () => defProps.backtop.icon
},
// 提示文字
text: {
type: String,
default: () => defProps.backtop.text
},
// 返回顶部滚动时间
duration: {
type: [String, Number],
default: () => defProps.backtop.duration
},
// 滚动距离
scrollTop: {
type: [String, Number],
default: () => defProps.backtop.scrollTop
},
// 距离顶部多少距离显示单位px
top: {
type: [String, Number],
default: () => defProps.backtop.top
},
// 返回顶部按钮到底部的距离单位px
bottom: {
type: [String, Number],
default: () => defProps.backtop.bottom
},
// 返回顶部按钮到右边的距离单位px
right: {
type: [String, Number],
default: () => defProps.backtop.right
},
// 层级
zIndex: {
type: [String, Number],
default: () => defProps.backtop.zIndex
},
// 图标的样式,对象形式
iconStyle: {
type: Object,
default: () => defProps.backtop.iconStyle
}
}
}

View File

@ -0,0 +1,133 @@
<template>
<u-transition
mode="fade"
:customStyle="backTopStyle"
:show="show"
>
<view
class="u-back-top"
:style="[contentStyle]"
v-if="!$slots.default && !$slots.$default"
@click="backToTop"
>
<u-icon
:name="icon"
:custom-style="iconStyle"
></u-icon>
<text
v-if="text"
class="u-back-top__text"
>{{text}}</text>
</view>
<slot v-else />
</u-transition>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, addStyle, getPx, deepMerge, error } from '../../libs/function/index';
// #ifdef APP-NVUE
const dom = weex.requireModule('dom')
// #endif
/**
* backTop 返回顶部
* @description 本组件一个用于长页面滑动一定距离后出现返回顶部按钮方便快速返回顶部的场景
* @tutorial https://uview-plus.jiangruyi.com/components/backTop.html
*
* @property {String} mode 返回顶部的形状circle-圆形square-方形 默认 'circle'
* @property {String} icon 自定义图标 默认 'arrow-upward' 见官方文档示例
* @property {String} text 提示文字
* @property {String | Number} duration 返回顶部滚动时间 默认 100
* @property {String | Number} scrollTop 滚动距离 默认 0
* @property {String | Number} top 距离顶部多少距离显示单位px 默认 400
* @property {String | Number} bottom 返回顶部按钮到底部的距离单位px 默认 100
* @property {String | Number} right 返回顶部按钮到右边的距离单位px 默认 20
* @property {String | Number} zIndex 层级 默认 9
* @property {Object<Object>} iconStyle 图标的样式对象形式 默认 {color: '#909399',fontSize: '19px'}
* @property {Object} customStyle 定义需要用到的外部样式
*
* @example <u-back-top :scrollTop="scrollTop"></u-back-top>
*/
export default {
name: 'u-back-top',
mixins: [mpMixin, mixin, props],
computed: {
backTopStyle() {
//
const style = {
bottom: addUnit(this.bottom),
right: addUnit(this.right),
width: '40px',
height: '40px',
position: 'fixed',
zIndex: 10,
}
return style
},
show() {
return getPx(this.scrollTop) > getPx(this.top)
},
contentStyle() {
const style = {}
let radius = 0
//
if(this.mode === 'circle') {
radius = '100px'
} else {
radius = '4px'
}
// nvue
style.borderTopLeftRadius = radius
style.borderTopRightRadius = radius
style.borderBottomLeftRadius = radius
style.borderBottomRightRadius = radius
return deepMerge(style, addStyle(this.customStyle))
}
},
emits: ["click"],
methods: {
backToTop() {
// #ifdef APP-NVUE
if (!this.$parent.$refs['u-back-top']) {
error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`)
}
dom.scrollToElement(this.$parent.$refs['u-back-top'], {
offset: 0
})
// #endif
// #ifndef APP-NVUE
uni.pageScrollTo({
scrollTop: 0,
duration: this.duration
});
// #endif
this.$emit('click')
}
}
}
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
$u-back-top-flex:1 !default;
$u-back-top-height:100% !default;
$u-back-top-background-color:#E1E1E1 !default;
$u-back-top-tips-font-size:12px !default;
.u-back-top {
@include flex;
flex-direction: column;
align-items: center;
flex:$u-back-top-flex;
height: $u-back-top-height;
justify-content: center;
background-color: $u-back-top-background-color;
&__tips {
font-size:$u-back-top-tips-font-size;
transform: scale(0.8);
}
}
</style>

View File

@ -0,0 +1,78 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 是否显示圆点
isDot: {
type: Boolean,
default: () => defProps.badge.isDot
},
// 显示的内容
value: {
type: [Number, String],
default: () => defProps.badge.value
},
// 显示的内容
modelValue: {
type: [Number, String],
default: () => defProps.badge.modelValue
},
// 是否显示
show: {
type: Boolean,
default: () => defProps.badge.show
},
// 最大值,超过最大值会显示 '{max}+'
max: {
type: [Number, String],
default: () => defProps.badge.max
},
// 主题类型error|warning|success|primary
type: {
type: String,
default: () => defProps.badge.type
},
// 当数值为 0 时,是否展示 Badge
showZero: {
type: Boolean,
default: () => defProps.badge.showZero
},
// 背景颜色优先级比type高如设置type参数会失效
bgColor: {
type: [String, null],
default: () => defProps.badge.bgColor
},
// 字体颜色
color: {
type: [String, null],
default: () => defProps.badge.color
},
// 徽标形状circle-四角均为圆角horn-左下角为直角
shape: {
type: String,
default: () => defProps.badge.shape
},
// 设置数字的显示方式overflow|ellipsis|limit
// overflow会根据max字段判断超出显示`${max}+`
// ellipsis会根据max判断超出显示`${max}...`
// limit会依据1000作为判断条件超出1000显示`${value/1000}K`比如2.2k、3.34w最多保留2位小数
numberType: {
type: String,
default: () => defProps.badge.numberType
},
// 设置badge的位置偏移格式为 [x, y]也即设置的为top和right的值absolute为true时有效
offset: {
type: Array,
default: () => defProps.badge.offset
},
// 是否反转背景和字体颜色
inverted: {
type: Boolean,
default: () => defProps.badge.inverted
},
// 是否绝对定位
absolute: {
type: Boolean,
default: () => defProps.badge.absolute
}
}
}

View File

@ -0,0 +1,177 @@
<template>
<text
v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
:style="[addStyle(customStyle), badgeStyle]"
class="u-badge"
>{{ isDot ? '' :showValue }}</text>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addStyle, addUnit } from '../../libs/function/index';
/**
* badge 徽标数
* @description 该组件一般用于图标右上角显示未读的消息数量提示用户点击有圆点和圆包含文字两种形式
* @tutorial https://uview-plus.jiangruyi.com/components/badge.html
*
* @property {Boolean} isDot 是否显示圆点 默认 false
* @property {String | Number} value 显示的内容
* @property {Boolean} show 是否显示 默认 true
* @property {String | Number} max 最大值超过最大值会显示 '{max}+' 默认999
* @property {String} type 主题类型error|warning|success|primary 默认 'error'
* @property {Boolean} showZero 当数值为 0 是否展示 Badge 默认 false
* @property {String} bgColor 背景颜色优先级比type高如设置type参数会失效
* @property {String} color 字体颜色 默认 '#ffffff'
* @property {String} shape 徽标形状circle-四角均为圆角horn-左下角为直角 默认 'circle'
* @property {String} numberType 设置数字的显示方式overflow|ellipsis|limit 默认 'overflow'
* @property {Array}} offset 设置badge的位置偏移格式为 [x, y]也即设置的为top和right的值absolute为true时有效
* @property {Boolean} inverted 是否反转背景和字体颜色默认 false
* @property {Boolean} absolute 是否绝对定位默认 false
* @property {Object} customStyle 定义需要用到的外部样式
* @example <u-badge :type="type" :count="count"></u-badge>
*/
export default {
name: 'u-badge',
mixins: [mpMixin, props, mixin],
computed: {
// badge
boxStyle() {
let style = {};
return style;
},
//
badgeStyle() {
const style = {}
if(this.color) {
style.color = this.color
}
if (this.bgColor && !this.inverted) {
style.backgroundColor = this.bgColor
}
if (this.absolute) {
style.position = 'absolute'
// offset
if(this.offset.length) {
// toprightoffsetrighttop
const top = this.offset[0]
const right = this.offset[1] || top
style.top = addUnit(top)
style.right = addUnit(right)
}
}
return style
},
showValue() {
switch (this.numberType) {
case "overflow":
return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
break;
case "ellipsis":
return Number(this.value) > Number(this.max) ? "..." : this.value
break;
case "limit":
return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
1e3 * 100) / 100 + "k" : this.value
break;
default:
return Number(this.value)
}
},
},
methods: {
addStyle
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-badge-primary: $u-primary !default;
$u-badge-error: $u-error !default;
$u-badge-success: $u-success !default;
$u-badge-info: $u-info !default;
$u-badge-warning: $u-warning !default;
$u-badge-dot-radius: 100px !default;
$u-badge-dot-size: 8px !default;
$u-badge-dot-right: 4px !default;
$u-badge-dot-top: 0 !default;
$u-badge-text-font-size: 11px !default;
$u-badge-text-right: 10px !default;
$u-badge-text-padding: 2px 5px !default;
$u-badge-text-align: center !default;
$u-badge-text-color: #FFFFFF !default;
.u-badge {
border-top-right-radius: $u-badge-dot-radius;
border-top-left-radius: $u-badge-dot-radius;
border-bottom-left-radius: $u-badge-dot-radius;
border-bottom-right-radius: $u-badge-dot-radius;
@include flex;
line-height: $u-badge-text-font-size;
text-align: $u-badge-text-align;
font-size: $u-badge-text-font-size;
color: $u-badge-text-color;
&--dot {
height: $u-badge-dot-size;
width: $u-badge-dot-size;
}
&--inverted {
font-size: 13px;
}
&--not-dot {
padding: $u-badge-text-padding;
}
&--horn {
border-bottom-left-radius: 0;
}
&--primary {
background-color: $u-badge-primary;
}
&--primary--inverted {
color: $u-badge-primary;
}
&--error {
background-color: $u-badge-error;
}
&--error--inverted {
color: $u-badge-error;
}
&--success {
background-color: $u-badge-success;
}
&--success--inverted {
color: $u-badge-success;
}
&--info {
background-color: $u-badge-info;
}
&--info--inverted {
color: $u-badge-info;
}
&--warning {
background-color: $u-badge-warning;
}
&--warning--inverted {
color: $u-badge-warning;
}
}
</style>

View File

@ -0,0 +1,46 @@
$u-button-active-opacity:0.75 !default;
$u-button-loading-text-margin-left:4px !default;
$u-button-text-color: #FFFFFF !default;
$u-button-text-plain-error-color:$u-error !default;
$u-button-text-plain-warning-color:$u-warning !default;
$u-button-text-plain-success-color:$u-success !default;
$u-button-text-plain-info-color:$u-info !default;
$u-button-text-plain-primary-color:$u-primary !default;
.u-button {
&--active {
opacity: $u-button-active-opacity;
}
&--active--plain {
background-color: rgb(217, 217, 217);
}
&__loading-text {
margin-left:$u-button-loading-text-margin-left;
}
&__text,
&__loading-text {
color:$u-button-text-color;
}
&__text--plain--error {
color:$u-button-text-plain-error-color;
}
&__text--plain--warning {
color:$u-button-text-plain-warning-color;
}
&__text--plain--success{
color:$u-button-text-plain-success-color;
}
&__text--plain--info {
color:$u-button-text-plain-info-color;
}
&__text--plain--primary {
color:$u-button-text-plain-primary-color;
}
}

View File

@ -0,0 +1,153 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 是否细边框
hairline: {
type: Boolean,
default: () => defProps.button.hairline
},
// 按钮的预置样式infoprimaryerrorwarningsuccess
type: {
type: String,
default: () => defProps.button.type
},
// 按钮尺寸largenormalsmallmini
size: {
type: String,
default: () => defProps.button.size
},
// 按钮形状circle两边为半圆square带圆角
shape: {
type: String,
default: () => defProps.button.shape
},
// 按钮是否镂空
plain: {
type: Boolean,
default: () => defProps.button.plain
},
// 是否禁止状态
disabled: {
type: Boolean,
default: () => defProps.button.disabled
},
// 是否加载中
loading: {
type: Boolean,
default: () => defProps.button.loading
},
// 加载中提示文字
loadingText: {
type: [String, Number],
default: () => defProps.button.loadingText
},
// 加载状态图标类型
loadingMode: {
type: String,
default: () => defProps.button.loadingMode
},
// 加载图标大小
loadingSize: {
type: [String, Number],
default: () => defProps.button.loadingSize
},
// 开放能力具体请看uniapp稳定关于button组件部分说明
// https://uniapp.dcloud.io/component/button
openType: {
type: String,
default: () => defProps.button.openType
},
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
// 取值为submit提交表单reset重置表单
formType: {
type: String,
default: () => defProps.button.formType
},
// 打开 APP 时,向 APP 传递的参数open-type=launchApp时有效
// 只微信小程序、QQ小程序有效
appParameter: {
type: String,
default: () => defProps.button.appParameter
},
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
hoverStopPropagation: {
type: Boolean,
default: () => defProps.button.hoverStopPropagation
},
// 指定返回用户信息的语言zh_CN 简体中文zh_TW 繁体中文en 英文。只微信小程序有效
lang: {
type: String,
default: () => defProps.button.lang
},
// 会话来源open-type="contact"时有效。只微信小程序有效
sessionFrom: {
type: String,
default: () => defProps.button.sessionFrom
},
// 会话内消息卡片标题open-type="contact"时有效
// 默认当前标题,只微信小程序有效
sendMessageTitle: {
type: String,
default: () => defProps.button.sendMessageTitle
},
// 会话内消息卡片点击跳转小程序路径open-type="contact"时有效
// 默认当前分享路径,只微信小程序有效
sendMessagePath: {
type: String,
default: () => defProps.button.sendMessagePath
},
// 会话内消息卡片图片open-type="contact"时有效
// 默认当前页面截图,只微信小程序有效
sendMessageImg: {
type: String,
default: () => defProps.button.sendMessageImg
},
// 是否显示会话内消息卡片,设置此参数为 true用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
// 用户点击后可以快速发送小程序消息open-type="contact"时有效
showMessageCard: {
type: Boolean,
default: () => defProps.button.showMessageCard
},
// 额外传参参数用于小程序的data-xxx属性通过target.dataset.name获取
dataName: {
type: String,
default: () => defProps.button.dataName
},
// 节流,一定时间内只能触发一次
throttleTime: {
type: [String, Number],
default: () => defProps.button.throttleTime
},
// 按住后多久出现点击态,单位毫秒
hoverStartTime: {
type: [String, Number],
default: () => defProps.button.hoverStartTime
},
// 手指松开后点击态保留时间,单位毫秒
hoverStayTime: {
type: [String, Number],
default: () => defProps.button.hoverStayTime
},
// 按钮文字之所以通过props传入是因为slot传入的话
// nvue中无法控制文字的样式
text: {
type: [String, Number],
default: () => defProps.button.text
},
// 按钮图标
icon: {
type: String,
default: () => defProps.button.icon
},
// 按钮图标
iconColor: {
type: String,
default: () => defProps.button.icon
},
// 按钮颜色支持传入linear-gradient渐变色
color: {
type: String,
default: () => defProps.button.color
}
}
}

View File

@ -0,0 +1,503 @@
<template>
<!-- #ifndef APP-NVUE -->
<button
:hover-start-time="Number(hoverStartTime)"
:hover-stay-time="Number(hoverStayTime)"
: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"
@agreeprivacyauthorization="agreeprivacyauthorization"
:hover-class="!disabled && !loading ? 'u-button--active' : ''"
class="u-button u-reset-button"
:style="[baseColor, addStyle(customStyle)]"
@tap="clickHandler"
:class="bemClass"
>
<template v-if="loading">
<u-loading-icon
:mode="loadingMode"
:size="loadingSize * 1.15"
:color="loadingColor"
></u-loading-icon>
<text
class="u-button__loading-text"
:style="[{ fontSize: textSize + 'px' }]"
>{{ loadingText || text }}</text
>
</template>
<template v-else>
<u-icon
v-if="icon"
:name="icon"
:color="iconColorCom"
:size="textSize * 1.35"
:customStyle="{ marginRight: '2px' }"
></u-icon>
<slot>
<text
class="u-button__text"
:style="[{ fontSize: textSize + 'px' }]"
>{{ text }}</text
>
</slot>
</template>
</button>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view
:hover-start-time="Number(hoverStartTime)"
:hover-stay-time="Number(hoverStayTime)"
class="u-button"
:hover-class="
!disabled && !loading && !color && (plain || type === 'info')
? 'u-button--active--plain'
: !disabled && !loading && !plain
? 'u-button--active'
: ''
"
@tap="clickHandler"
:class="bemClass"
:style="[baseColor, addStyle(customStyle)]"
>
<template v-if="loading">
<u-loading-icon
:mode="loadingMode"
:size="loadingSize * 1.15"
:color="loadingColor"
></u-loading-icon>
<text
class="u-button__loading-text"
:style="[nvueTextStyle]"
:class="[plain && `u-button__text--plain--${type}`]"
>{{ loadingText || text }}</text
>
</template>
<template v-else>
<u-icon
v-if="icon"
:name="icon"
:color="iconColorCom"
:size="textSize * 1.35"
></u-icon>
<text
class="u-button__text"
:style="[
{
marginLeft: icon ? '2px' : 0,
},
nvueTextStyle,
]"
:class="[plain && `u-button__text--plain--${type}`]"
>{{ text }}</text
>
</template>
</view>
<!-- #endif -->
</template>
<script lang="ts">
import button from "../../libs/mixin/button";
import openType from "../../libs/mixin/openType";
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import props from "./props";
import { addStyle } from '../../libs/function/index';
import { throttle } from '../../libs/function/throttle';
import color from '../../libs/config/color';
/**
* button 按钮
* @description Button 按钮
* @tutorial https://ijry.github.io/uview-plus/components/button.html
*
* @property {Boolean} hairline 是否显示按钮的细边框 (默认 true )
* @property {String} type 按钮的预置样式infoprimaryerrorwarningsuccess (默认 'info' )
* @property {String} size 按钮尺寸largenormalmini 默认 normal
* @property {String} shape 按钮形状circle两边为半圆square带圆角 默认 'square'
* @property {Boolean} plain 按钮是否镂空背景色透明 默认 false
* @property {Boolean} disabled 是否禁用 默认 false
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台 ios 上为雪花Android上为圆圈) 默认 false
* @property {String | Number} loadingText 加载中提示文字
* @property {String} loadingMode 加载状态图标类型 默认 'spinner'
* @property {String | Number} loadingSize 加载图标大小 默认 15
* @property {String} openType 开放能力具体请看uniapp稳定关于button组件部分说明
* @property {String} formType 用于 <form> 组件点击分别会触发 <form> 组件的 submit/reset 事件
* @property {String} appParameter 打开 APP APP 传递的参数open-type=launchApp时有效 只微信小程序QQ小程序有效
* @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态微信小程序有效默认 true
* @property {String} lang 指定返回用户信息的语言zh_CN 简体中文zh_TW 繁体中文en 英文默认 en
* @property {String} sessionFrom 会话来源openType="contact"时有效
* @property {String} sendMessageTitle 会话内消息卡片标题openType="contact"时有效
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径openType="contact"时有效
* @property {String} sendMessageImg 会话内消息卡片图片openType="contact"时有效
* @property {Boolean} showMessageCard 是否显示会话内消息卡片设置此参数为 true用户进入客服会话会在右下角显示"可能要发送的小程序"提示用户点击后可以快速发送小程序消息openType="contact"时有效默认false
* @property {String} dataName 额外传参参数用于小程序的data-xxx属性通过target.dataset.name获取
* @property {String | Number} throttleTime 节流一定时间内只能触发一次 默认 0 )
* @property {String | Number} hoverStartTime 按住后多久出现点击态单位毫秒 默认 0 )
* @property {String | Number} hoverStayTime 手指松开后点击态保留时间单位毫秒 默认 200 )
* @property {String | Number} text 按钮文字之所以通过props传入是因为slot传入的话nvue中无法控制文字的样式
* @property {String} icon 按钮图标
* @property {String} iconColor 按钮图标颜色
* @property {String} color 按钮颜色支持传入linear-gradient渐变色
* @property {Object} customStyle 定义需要用到的外部样式
*
* @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 成功的回调
* @event {Function} agreeprivacyauthorization 用户同意隐私协议事件回调
* @example <u-button>月落</u-button>
*/
export default {
name: "u-button",
// #ifdef MP
mixins: [mpMixin, mixin, button, openType, props],
// #endif
// #ifndef MP
mixins: [mpMixin, mixin, props],
// #endif
data() {
return {};
},
computed: {
// bem
bemClass() {
// this.bemcomputedmixin
if (!this.color) {
return this.bem(
"button",
["type", "shape", "size"],
["disabled", "plain", "hairline"]
);
} else {
// nvuecolortypetype
return this.bem(
"button",
["shape", "size"],
["disabled", "plain", "hairline"]
);
}
},
loadingColor() {
if (this.plain) {
// colorcolor使type
return this.color
? this.color
: color[`u-${this.type}`];
}
if (this.type === "info") {
return "#c9c9c9";
}
return "rgb(200, 200, 200)";
},
iconColorCom() {
// colorcolor使
// u-iconcolor
if (this.iconColor) return this.iconColor;
if (this.plain) {
return this.color ? this.color : this.type;
} else {
return this.type === "info" ? "#000000" : "#ffffff";
}
},
baseColor() {
let style = {};
if (this.color) {
// color
style.color = this.plain ? this.color : "white";
if (!this.plain) {
// 使
style["background-color"] = this.color;
}
if (this.color.indexOf("gradient") !== -1) {
// backgroundImage
// weexborderWidth
// weex西
style.borderTopWidth = 0;
style.borderRightWidth = 0;
style.borderBottomWidth = 0;
style.borderLeftWidth = 0;
if (!this.plain) {
style.backgroundImage = this.color;
}
} else {
//
style.borderColor = this.color;
style.borderWidth = "1px";
style.borderStyle = "solid";
}
}
return style;
},
// nvuetext
nvueTextStyle() {
let style = {};
// color
if (this.type === "info") {
style.color = "#323233";
}
if (this.color) {
style.color = this.plain ? this.color : "white";
}
style.fontSize = this.textSize + "px";
return style;
},
//
textSize() {
let fontSize = 14,
{ size } = this;
if (size === "large") fontSize = 16;
if (size === "normal") fontSize = 14;
if (size === "small") fontSize = 12;
if (size === "mini") fontSize = 10;
return fontSize;
},
},
emits: ['click', 'getphonenumber', 'getuserinfo',
'error', 'opensetting', 'launchapp', 'agreeprivacyauthorization'],
methods: {
addStyle,
clickHandler() {
//
if (!this.disabled && !this.loading) {
// this.throttle
throttle(() => {
this.$emit("click");
}, this.throttleTime);
}
},
// uniapp
getphonenumber(res: any) {
this.$emit("getphonenumber", res);
},
getuserinfo(res: any) {
this.$emit("getuserinfo", res);
},
error(res: any) {
this.$emit("error", res);
},
opensetting(res: any) {
this.$emit("opensetting", res);
},
launchapp(res: any) {
this.$emit("launchapp", res);
},
agreeprivacyauthorization(res) {
this.$emit("agreeprivacyauthorization", res);
},
},
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
/* #ifndef APP-NVUE */
@import "./vue.scss";
/* #endif */
/* #ifdef APP-NVUE */
@import "./nvue.scss";
/* #endif */
$u-button-u-button-height: 40px !default;
$u-button-text-font-size: 15px !default;
$u-button-loading-text-font-size: 15px !default;
$u-button-loading-text-margin-left: 4px !default;
$u-button-large-width: 100% !default;
$u-button-large-height: 50px !default;
$u-button-normal-padding: 0 12px !default;
$u-button-large-padding: 0 15px !default;
$u-button-normal-font-size: 14px !default;
$u-button-small-min-width: 60px !default;
$u-button-small-height: 30px !default;
$u-button-small-padding: 0px 8px !default;
$u-button-mini-padding: 0px 8px !default;
$u-button-small-font-size: 12px !default;
$u-button-mini-height: 22px !default;
$u-button-mini-font-size: 10px !default;
$u-button-mini-min-width: 50px !default;
$u-button-disabled-opacity: 0.5 !default;
$u-button-info-color: #323233 !default;
$u-button-info-background-color: #fff !default;
$u-button-info-border-color: #ebedf0 !default;
$u-button-info-border-width: 1px !default;
$u-button-info-border-style: solid !default;
$u-button-success-color: #fff !default;
$u-button-success-background-color: $u-success !default;
$u-button-success-border-color: $u-button-success-background-color !default;
$u-button-success-border-width: 1px !default;
$u-button-success-border-style: solid !default;
$u-button-primary-color: #fff !default;
$u-button-primary-background-color: $u-primary !default;
$u-button-primary-border-color: $u-button-primary-background-color !default;
$u-button-primary-border-width: 1px !default;
$u-button-primary-border-style: solid !default;
$u-button-error-color: #fff !default;
$u-button-error-background-color: $u-error !default;
$u-button-error-border-color: $u-button-error-background-color !default;
$u-button-error-border-width: 1px !default;
$u-button-error-border-style: solid !default;
$u-button-warning-color: #fff !default;
$u-button-warning-background-color: $u-warning !default;
$u-button-warning-border-color: $u-button-warning-background-color !default;
$u-button-warning-border-width: 1px !default;
$u-button-warning-border-style: solid !default;
$u-button-block-width: 100% !default;
$u-button-circle-border-top-right-radius: 100px !default;
$u-button-circle-border-top-left-radius: 100px !default;
$u-button-circle-border-bottom-left-radius: 100px !default;
$u-button-circle-border-bottom-right-radius: 100px !default;
$u-button-square-border-top-right-radius: 3px !default;
$u-button-square-border-top-left-radius: 3px !default;
$u-button-square-border-bottom-left-radius: 3px !default;
$u-button-square-border-bottom-right-radius: 3px !default;
$u-button-icon-min-width: 1em !default;
$u-button-plain-background-color: #fff !default;
$u-button-hairline-border-width: 0.5px !default;
.u-button {
height: $u-button-u-button-height;
position: relative;
align-items: center;
justify-content: center;
@include flex;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
flex-direction: row;
&__text {
font-size: $u-button-text-font-size;
}
&__loading-text {
font-size: $u-button-loading-text-font-size;
margin-left: $u-button-loading-text-margin-left;
}
&--large {
/* #ifndef APP-NVUE */
width: $u-button-large-width;
/* #endif */
height: $u-button-large-height;
padding: $u-button-large-padding;
}
&--normal {
padding: $u-button-normal-padding;
font-size: $u-button-normal-font-size;
}
&--small {
/* #ifndef APP-NVUE */
min-width: $u-button-small-min-width;
/* #endif */
height: $u-button-small-height;
padding: $u-button-small-padding;
font-size: $u-button-small-font-size;
}
&--mini {
height: $u-button-mini-height;
font-size: $u-button-mini-font-size;
/* #ifndef APP-NVUE */
min-width: $u-button-mini-min-width;
/* #endif */
padding: $u-button-mini-padding;
}
&--disabled {
opacity: $u-button-disabled-opacity;
}
&--info {
color: $u-button-info-color;
background-color: $u-button-info-background-color;
border-color: $u-button-info-border-color;
border-width: $u-button-info-border-width;
border-style: $u-button-info-border-style;
}
&--success {
color: $u-button-success-color;
background-color: $u-button-success-background-color;
border-color: $u-button-success-border-color;
border-width: $u-button-success-border-width;
border-style: $u-button-success-border-style;
}
&--primary {
color: $u-button-primary-color;
background-color: $u-button-primary-background-color;
border-color: $u-button-primary-border-color;
border-width: $u-button-primary-border-width;
border-style: $u-button-primary-border-style;
}
&--error {
color: $u-button-error-color;
background-color: $u-button-error-background-color;
border-color: $u-button-error-border-color;
border-width: $u-button-error-border-width;
border-style: $u-button-error-border-style;
}
&--warning {
color: $u-button-warning-color;
background-color: $u-button-warning-background-color;
border-color: $u-button-warning-border-color;
border-width: $u-button-warning-border-width;
border-style: $u-button-warning-border-style;
}
&--block {
@include flex;
width: $u-button-block-width;
}
&--circle {
border-top-right-radius: $u-button-circle-border-top-right-radius;
border-top-left-radius: $u-button-circle-border-top-left-radius;
border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;
border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;
}
&--square {
border-bottom-left-radius: $u-button-square-border-top-right-radius;
border-bottom-right-radius: $u-button-square-border-top-left-radius;
border-top-left-radius: $u-button-square-border-bottom-left-radius;
border-top-right-radius: $u-button-square-border-bottom-right-radius;
}
&__icon {
/* #ifndef APP-NVUE */
min-width: $u-button-icon-min-width;
line-height: inherit !important;
vertical-align: top;
/* #endif */
}
&--plain {
background-color: $u-button-plain-background-color;
}
&--hairline {
border-width: $u-button-hairline-border-width !important;
}
}
</style>

View File

@ -0,0 +1,81 @@
// nvue下hover-class无效
$u-button-before-top:50% !default;
$u-button-before-left:50% !default;
$u-button-before-width:100% !default;
$u-button-before-height:100% !default;
$u-button-before-transform:translate(-50%, -50%) !default;
$u-button-before-opacity:0 !default;
$u-button-before-background-color:#000 !default;
$u-button-before-border-color:#000 !default;
$u-button-active-before-opacity:.15 !default;
$u-button-icon-margin-left:4px !default;
$u-button-plain-u-button-info-color:$u-info;
$u-button-plain-u-button-success-color:$u-success;
$u-button-plain-u-button-error-color:$u-error;
$u-button-plain-u-button-warning-color:$u-error;
.u-button {
width: 100%;
white-space: nowrap;
&__text {
white-space: nowrap;
line-height: 1;
}
&:before {
position: absolute;
top:$u-button-before-top;
left:$u-button-before-left;
width:$u-button-before-width;
height:$u-button-before-height;
border: inherit;
border-radius: inherit;
transform:$u-button-before-transform;
opacity:$u-button-before-opacity;
content: " ";
background-color:$u-button-before-background-color;
border-color:$u-button-before-border-color;
}
&--active {
&:before {
opacity: .15
}
}
&__icon+&__text:not(:empty),
&__loading-text {
margin-left:$u-button-icon-margin-left;
}
&--plain {
&.u-button--primary {
color: $u-primary;
}
}
&--plain {
&.u-button--info {
color:$u-button-plain-u-button-info-color;
}
}
&--plain {
&.u-button--success {
color:$u-button-plain-u-button-success-color;
}
}
&--plain {
&.u-button--error {
color:$u-button-plain-u-button-error-color;
}
}
&--plain {
&.u-button--warning {
color:$u-button-plain-u-button-warning-color;
}
}
}

View File

@ -0,0 +1,101 @@
<template>
<view class="u-calendar-header u-border-bottom">
<text
class="u-calendar-header__title"
v-if="showTitle"
>{{ title }}</text>
<text
class="u-calendar-header__subtitle"
v-if="showSubtitle"
>{{ subtitle }}</text>
<view class="u-calendar-header__weekdays">
<text class="u-calendar-header__weekdays__weekday"></text>
<text class="u-calendar-header__weekdays__weekday"></text>
<text class="u-calendar-header__weekdays__weekday"></text>
<text class="u-calendar-header__weekdays__weekday"></text>
<text class="u-calendar-header__weekdays__weekday"></text>
<text class="u-calendar-header__weekdays__weekday"></text>
<text class="u-calendar-header__weekdays__weekday"></text>
</view>
</view>
</template>
<script>
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
export default {
name: 'u-calendar-header',
mixins: [mpMixin, mixin],
props: {
//
title: {
type: String,
default: ''
},
//
subtitle: {
type: String,
default: ''
},
//
showTitle: {
type: Boolean,
default: true
},
//
showSubtitle: {
type: Boolean,
default: true
},
},
data() {
return {
}
},
methods: {
name() {
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-calendar-header {
padding-bottom: 4px;
&__title {
font-size: 16px;
color: $u-main-color;
text-align: center;
height: 42px;
line-height: 42px;
font-weight: bold;
}
&__subtitle {
font-size: 14px;
color: $u-main-color;
height: 40px;
text-align: center;
line-height: 40px;
font-weight: bold;
}
&__weekdays {
@include flex;
justify-content: space-between;
&__weekday {
font-size: 13px;
color: $u-main-color;
line-height: 30px;
flex: 1;
text-align: center;
}
}
}
</style>

View File

@ -0,0 +1,585 @@
<template>
<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
:ref="`u-calendar-month-${index}`" :id="`month-${index}`">
<text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}{{ item.month }}</text>
<view class="u-calendar-month__days">
<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
</view>
<view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
:style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
:class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
<text class="u-calendar-month__days__day__select__info"
:class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']"
:style="[textStyle(item1)]">{{ item1.day }}</text>
<text v-if="getBottomInfo(index, index1, item1)"
class="u-calendar-month__days__day__select__buttom-info"
:class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']"
:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
// #ifdef APP-NVUE
// nvue
const dom = uni.requireNativePlugin('dom')
// #endif
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, deepClone, toast, sleep } from '../../libs/function/index';
import { colorGradient } from '../../libs/function/colorGradient';
import test from '../../libs/function/test';
import defProps from '../../libs/config/props';
import dayjs from 'dayjs/esm/index'
export default {
name: 'u-calendar-month',
mixins: [mpMixin, mixin],
props: {
//
showMark: {
type: Boolean,
default: true
},
//
color: {
type: String,
default: '#3c9cff'
},
//
months: {
type: Array,
default: () => []
},
//
mode: {
type: String,
default: 'single'
},
//
rowHeight: {
type: [String, Number],
default: 58
},
// mode=multiple
maxCount: {
type: [String, Number],
default: Infinity
},
// mode=range
startText: {
type: String,
default: '开始'
},
// mode=range
endText: {
type: String,
default: '结束'
},
// modemultiplerange
defaultDate: {
type: [Array, String, Date],
default: null
},
//
minDate: {
type: [String, Number],
default: 0
},
//
maxDate: {
type: [String, Number],
default: 0
},
// maxDate
maxMonth: {
type: [String, Number],
default: 2
},
//
readonly: {
type: Boolean,
default: () => defProps.calendar.readonly
},
// mode = range
maxRange: {
type: [Number, String],
default: Infinity
},
// mode = range
rangePrompt: {
type: String,
default: ''
},
// mode = range
showRangePrompt: {
type: Boolean,
default: true
},
// mode = range
allowSameDay: {
type: Boolean,
default: false
}
},
data() {
return {
//
width: 0,
// item
item: {},
selected: []
}
},
watch: {
selectedChange: {
immediate: true,
handler(n) {
this.setDefaultDate()
}
}
},
computed: {
//
selectedChange() {
return [this.minDate, this.maxDate, this.defaultDate]
},
dayStyle(index1, index2, item) {
return (index1, index2, item) => {
const style = {}
let week = item.week
// 2
const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
//
// #ifdef APP-NVUE
style.width = addUnit(dayWidth)
// #endif
style.height = addUnit(this.rowHeight)
if (index2 === 0) {
// 0item
week = (week === 0 ? 7 : week) - 1
style.marginLeft = addUnit(week * dayWidth)
}
if (this.mode === 'range') {
// DCloudiOSbug
style.paddingLeft = 0
style.paddingRight = 0
style.paddingBottom = 0
style.paddingTop = 0
}
return style
}
},
daySelectStyle() {
return (index1, index2, item) => {
let date = dayjs(item.date).format("YYYY-MM-DD"),
style = {}
// dateselected0使dateSameincludes
if (this.selected.some(item => this.dateSame(item, date))) {
style.backgroundColor = this.color
}
if (this.mode === 'single') {
if (date === this.selected[0]) {
// nvue
style.borderTopLeftRadius = '3px'
style.borderBottomLeftRadius = '3px'
style.borderTopRightRadius = '3px'
style.borderBottomRightRadius = '3px'
}
} else if (this.mode === 'range') {
if (this.selected.length >= 2) {
const len = this.selected.length - 1
//
if (this.dateSame(date, this.selected[0])) {
style.borderTopLeftRadius = '3px'
style.borderBottomLeftRadius = '3px'
}
//
if (this.dateSame(date, this.selected[len])) {
style.borderTopRightRadius = '3px'
style.borderBottomRightRadius = '3px'
}
//
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
.selected[len]))) {
style.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]
// mark
style.opacity = 0.7
}
} else if (this.selected.length === 1) {
// DCloudiOSbug
// nvueiOSuni-appbug
style.borderTopLeftRadius = '3px'
style.borderBottomLeftRadius = '3px'
}
} else {
if (this.selected.some(item => this.dateSame(item, date))) {
style.borderTopLeftRadius = '3px'
style.borderBottomLeftRadius = '3px'
style.borderTopRightRadius = '3px'
style.borderBottomRightRadius = '3px'
}
}
return style
}
},
//
textStyle() {
return (item) => {
const date = dayjs(item.date).format("YYYY-MM-DD"),
style = {}
//
if (this.selected.some(item => this.dateSame(item, date))) {
style.color = '#ffffff'
}
if (this.mode === 'range') {
const len = this.selected.length - 1
//
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
.selected[len]))) {
style.color = this.color
}
}
return style
}
},
//
getBottomInfo() {
return (index1, index2, item) => {
const date = dayjs(item.date).format("YYYY-MM-DD")
const bottomInfo = item.bottomInfo
// 0
if (this.mode === 'range' && this.selected.length > 0) {
if (this.selected.length === 1) {
//
if (this.dateSame(date, this.selected[0])) return this.startText
else return bottomInfo
} else {
const len = this.selected.length - 1
// 2
if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
len === 1) {
// 2item
return `${this.startText}/${this.endText}`
} else if (this.dateSame(date, this.selected[0])) {
return this.startText
} else if (this.dateSame(date, this.selected[len])) {
return this.endText
} else {
return bottomInfo
}
}
} else {
return bottomInfo
}
}
}
},
mounted() {
this.init()
},
methods: {
init() {
//
this.$emit('monthSelected', this.selected)
this.$nextTick(() => {
//
// nvue$nextTick100%
sleep(10).then(() => {
this.getWrapperWidth()
this.getMonthRect()
})
})
},
//
dateSame(date1, date2) {
return dayjs(date1).isSame(dayjs(date2))
},
// nvuecssitem
getWrapperWidth() {
// #ifdef APP-NVUE
dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
this.width = res.size.width
})
// #endif
// #ifndef APP-NVUE
this.$uGetRect('.u-calendar-month-wrapper').then(size => {
this.width = size.width
})
// #endif
},
getMonthRect() {
// scroll-view
const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
`u-calendar-month-${index}`))
//
Promise.all(promiseAllArr).then(
sizes => {
let height = 1
const topArr = []
for (let i = 0; i < this.months.length; i++) {
// monthsscroll-view
topArr[i] = height
height += sizes[i].height
}
// this.months[i].top()monthtop使
this.$emit('updateMonthTop', topArr)
})
},
//
getMonthRectByPromise(el) {
// #ifndef APP-NVUE
// $uGetRectuViewhttps://ijry.github.io/uview-plus/js/getRect.html
// this.$uGetRectuni.$u.getRect
return new Promise(resolve => {
this.$uGetRect(`.${el}`).then(size => {
resolve(size)
})
})
// #endif
// #ifdef APP-NVUE
// nvue使dom
// promise使then
return new Promise(resolve => {
dom.getComponentRect(this.$refs[el][0], res => {
resolve(res.size)
})
})
// #endif
},
//
clickHandler(index1, index2, item) {
if (this.readonly) {
return;
}
this.item = item
const date = dayjs(item.date).format("YYYY-MM-DD")
if (item.disabled) return
//
let selected = deepClone(this.selected)
if (this.mode === 'single') {
//
selected = [date]
} else if (this.mode === 'multiple') {
if (selected.some(item => this.dateSame(item, date))) {
//
const itemIndex = selected.findIndex(item => item === date)
selected.splice(itemIndex, 1)
} else {
//
if (selected.length < this.maxCount) selected.push(date)
}
} else {
//
if (selected.length === 0 || selected.length >= 2) {
// 02
selected = [date]
} else if (selected.length === 1) {
//
const existsDate = selected[0]
//
if (dayjs(date).isBefore(existsDate)) {
selected = [date]
} else if (dayjs(date).isAfter(existsDate)) {
//
if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
if(this.rangePrompt) {
toast(this.rangePrompt)
} else {
toast(`选择天数不能超过 ${this.maxRange}`)
}
return
}
//
selected.push(date)
const startDate = selected[0]
const endDate = selected[1]
const arr = []
let i = 0
do {
//
arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
i++
//
} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
// computedarr
arr.push(endDate)
selected = arr
} else {
//
if (selected[0] === date && !this.allowSameDay) return
selected.push(date)
}
}
}
this.setSelected(selected)
},
//
setDefaultDate() {
if (!this.defaultDate) {
//
const selected = [dayjs().format("YYYY-MM-DD")]
return this.setSelected(selected, false)
}
let defaultDate = []
const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
if (this.mode === 'single') {
// Date
if (!test.array(this.defaultDate)) {
defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
} else {
defaultDate = [this.defaultDate[0]]
}
} else {
//
if (!test.array(this.defaultDate)) return
defaultDate = this.defaultDate
}
//
defaultDate = defaultDate.filter(item => {
return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
maxDate).add(1, 'day'))
})
this.setSelected(defaultDate, false)
},
setSelected(selected, event = true) {
this.selected = selected
event && this.$emit('monthSelected', this.selected,'tap')
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-calendar-month-wrapper {
margin-top: 4px;
}
.u-calendar-month {
&__title {
font-size: 14px;
line-height: 42px;
height: 42px;
color: $u-main-color;
text-align: center;
font-weight: bold;
}
&__days {
position: relative;
@include flex;
flex-wrap: wrap;
&__month-mark-wrapper {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
@include flex;
justify-content: center;
align-items: center;
&__text {
font-size: 155px;
color: rgba(231, 232, 234, 0.83);
}
}
&__day {
@include flex;
padding: 2px;
/* #ifndef APP-NVUE */
// vue使cssjs
width: calc(100% / 7);
box-sizing: border-box;
/* #endif */
&__select {
flex: 1;
@include flex;
align-items: center;
justify-content: center;
position: relative;
&__dot {
width: 7px;
height: 7px;
border-radius: 100px;
background-color: $u-error;
position: absolute;
top: 12px;
right: 7px;
}
&__buttom-info {
color: $u-content-color;
text-align: center;
position: absolute;
bottom: 5px;
font-size: 10px;
text-align: center;
left: 0;
right: 0;
&--selected {
color: #ffffff;
}
&--disabled {
color: #cacbcd;
}
}
&__info {
text-align: center;
font-size: 16px;
&--selected {
color: #ffffff;
}
&--disabled {
color: #cacbcd;
}
}
&--selected {
background-color: $u-primary;
@include flex;
justify-content: center;
align-items: center;
flex: 1;
border-radius: 3px;
}
&--range-selected {
opacity: 0.3;
border-radius: 0;
}
&--range-start-selected {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&--range-end-selected {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}
}
}
</style>

View File

@ -0,0 +1,145 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 日历顶部标题
title: {
type: String,
default: () => defProps.calendar.title
},
// 是否显示标题
showTitle: {
type: Boolean,
default: () => defProps.calendar.showTitle
},
// 是否显示副标题
showSubtitle: {
type: Boolean,
default: () => defProps.calendar.showSubtitle
},
// 日期类型选择single-选择单个日期multiple-可以选择多个日期range-选择日期范围
mode: {
type: String,
default: () => defProps.calendar.mode
},
// mode=range时第一个日期底部的提示文字
startText: {
type: String,
default: () => defProps.calendar.startText
},
// mode=range时最后一个日期底部的提示文字
endText: {
type: String,
default: () => defProps.calendar.endText
},
// 自定义列表
customList: {
type: Array,
default: () => defProps.calendar.customList
},
// 主题色,对底部按钮和选中日期有效
color: {
type: String,
default: () => defProps.calendar.color
},
// 最小的可选日期
minDate: {
type: [String, Number],
default: () => defProps.calendar.minDate
},
// 最大可选日期
maxDate: {
type: [String, Number],
default: () => defProps.calendar.maxDate
},
// 默认选中的日期mode为multiple或range是必须为数组格式
defaultDate: {
type: [Array, String, Date, null],
default: () => defProps.calendar.defaultDate
},
// mode=multiple时最多可选多少个日期
maxCount: {
type: [String, Number],
default: () => defProps.calendar.maxCount
},
// 日期行高
rowHeight: {
type: [String, Number],
default: () => defProps.calendar.rowHeight
},
// 日期格式化函数
formatter: {
type: [Function, null],
default: () => defProps.calendar.formatter
},
// 是否显示农历
showLunar: {
type: Boolean,
default: () => defProps.calendar.showLunar
},
// 是否显示月份背景色
showMark: {
type: Boolean,
default: () => defProps.calendar.showMark
},
// 确定按钮的文字
confirmText: {
type: String,
default: () => defProps.calendar.confirmText
},
// 确认按钮处于禁用状态时的文字
confirmDisabledText: {
type: String,
default: () => defProps.calendar.confirmDisabledText
},
// 是否显示日历弹窗
show: {
type: Boolean,
default: () => defProps.calendar.show
},
// 是否允许点击遮罩关闭日历
closeOnClickOverlay: {
type: Boolean,
default: () => defProps.calendar.closeOnClickOverlay
},
// 是否为只读状态,只读状态下禁止选择日期
readonly: {
type: Boolean,
default: () => defProps.calendar.readonly
},
// 是否展示确认按钮
showConfirm: {
type: Boolean,
default: () => defProps.calendar.showConfirm
},
// 日期区间最多可选天数默认无限制mode = range时有效
maxRange: {
type: [Number, String],
default: () => defProps.calendar.maxRange
},
// 范围选择超过最多可选天数时的提示文案mode = range时有效
rangePrompt: {
type: String,
default: () => defProps.calendar.rangePrompt
},
// 范围选择超过最多可选天数时是否展示提示文案mode = range时有效
showRangePrompt: {
type: Boolean,
default: () => defProps.calendar.showRangePrompt
},
// 是否允许日期范围的起止时间为同一天mode = range时有效
allowSameDay: {
type: Boolean,
default: () => defProps.calendar.allowSameDay
},
// 圆角值
round: {
type: [Boolean, String, Number],
default: () => defProps.calendar.round
},
// 最多展示月份数量
monthNum: {
type: [Number, String],
default: 3
}
}
}

View File

@ -0,0 +1,409 @@
<template>
<u-popup
:show="show"
mode="bottom"
closeable
@close="close"
:round="round"
:closeOnClickOverlay="closeOnClickOverlay"
>
<view class="u-calendar">
<uHeader
:title="title"
:subtitle="subtitle"
:showSubtitle="showSubtitle"
:showTitle="showTitle"
></uHeader>
<scroll-view
:style="{
height: addUnit(listHeight)
}"
scroll-y
@scroll="onScroll"
:scroll-top="scrollTop"
:scrollIntoView="scrollIntoView"
>
<uMonth
:color="color"
:rowHeight="rowHeight"
:showMark="showMark"
:months="months"
:mode="mode"
:maxCount="maxCount"
:startText="startText"
:endText="endText"
:defaultDate="defaultDate"
:minDate="innerMinDate"
:maxDate="innerMaxDate"
:maxMonth="monthNum"
:readonly="readonly"
:maxRange="maxRange"
:rangePrompt="rangePrompt"
:showRangePrompt="showRangePrompt"
:allowSameDay="allowSameDay"
ref="month"
@monthSelected="monthSelected"
@updateMonthTop="updateMonthTop"
></uMonth>
</scroll-view>
<slot name="footer" v-if="showConfirm">
<view class="u-calendar__confirm">
<u-button
shape="circle"
:text="
buttonDisabled ? confirmDisabledText : confirmText
"
:color="color"
@click="confirm"
:disabled="buttonDisabled"
></u-button>
</view>
</slot>
</view>
</u-popup>
</template>
<script>
import uHeader from './header.vue'
import uMonth from './month.vue'
import props from './props.js'
import util from './util.js'
import dayjs from 'dayjs/esm/index'
import Calendar from '../../libs/util/calendar.js'
import mpMixin from '../../libs/mixin/mpMixin.js'
import mixin from '../../libs/mixin/mixin.js'
import { addUnit, range, error, padZero } from '../../libs/function/index';
import test from '../../libs/function/test';
/**
* Calendar 日历
* @description 此组件用于单个选择日期范围选择日期等日历被包裹在底部弹起的容器中.
* @tutorial https://ijry.github.io/uview-plus/components/calendar.html
*
* @property {String} title 标题内容 (默认 日期选择 )
* @property {Boolean} showTitle 是否显示标题 (默认 true )
* @property {Boolean} showSubtitle 是否显示副标题 (默认 true )
* @property {String} mode 日期类型选择 single-选择单个日期multiple-可以选择多个日期range-选择日期范围 默认 'single' )
* @property {String} startText mode=range时第一个日期底部的提示文字 (默认 '开始' )
* @property {String} endText mode=range时最后一个日期底部的提示文字 (默认 '结束' )
* @property {Array} customList 自定义列表
* @property {String} color 主题色对底部按钮和选中日期有效 (默认 #3c9cff' )
* @property {String | Number} minDate 最小的可选日期 (默认 0 )
* @property {String | Number} maxDate 最大可选日期 (默认 0 )
* @property {Array | String| Date} defaultDate 默认选中的日期mode为multiple或range是必须为数组格式
* @property {String | Number} maxCount mode=multiple时最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER )
* @property {String | Number} rowHeight 日期行高 (默认 56 )
* @property {Function} formatter 日期格式化函数
* @property {Boolean} showLunar 是否显示农历 (默认 false )
* @property {Boolean} showMark 是否显示月份背景色 (默认 true )
* @property {String} confirmText 确定按钮的文字 (默认 '确定' )
* @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' )
* @property {Boolean} show 是否显示日历弹窗 (默认 false )
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false )
* @property {Boolean} readonly 是否为只读状态只读状态下禁止选择日期 (默认 false )
* @property {String | Number} maxRange 日期区间最多可选天数默认无限制mode = range时有效
* @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案mode = range时有效
* @property {Boolean} showRangePrompt 范围选择超过最多可选天数时是否展示提示文案mode = range时有效 (默认 true )
* @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天mode = range时有效 (默认 false )
* @property {Number|String} round 圆角值默认无圆角 (默认 0 )
* @property {Number|String} monthNum 最多展示的月份数量 (默认 3 )
*
* @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数
* @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件
* @example <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm">
</u-calendar>
* */
export default {
name: 'u-calendar',
mixins: [mpMixin, mixin, props],
components: {
uHeader,
uMonth
},
data() {
return {
//
months: [],
// index
monthIndex: 0,
//
listHeight: 0,
// month
selected: [],
scrollIntoView: '',
scrollIntoViewScroll: '',
scrollTop:0,
//
innerFormatter: (value) => value
}
},
watch: {
scrollIntoView: {
immediate: true,
handler(n) {
// console.log('scrollIntoView', n)
}
},
selectedChange: {
immediate: true,
handler(n) {
this.setMonth()
}
},
//
show: {
immediate: true,
handler(n) {
if (n) {
this.setMonth()
} else {
// scrollIntoView
// scrollIntoView
this.scrollIntoView = ''
}
}
}
},
computed: {
// maxDateminDate(2021-10-10)()dayjs
innerMaxDate() {
return test.number(this.maxDate)
? Number(this.maxDate)
: this.maxDate
},
innerMinDate() {
return test.number(this.minDate)
? Number(this.minDate)
: this.minDate
},
//
selectedChange() {
return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
},
subtitle() {
// this.months
if (this.months.length) {
return `${this.months[this.monthIndex].year}${
this.months[this.monthIndex].month
}`
} else {
return ''
}
},
buttonDisabled() {
// range1disabled
if (this.mode === 'range') {
if (this.selected.length <= 1) {
return true
} else {
return false
}
} else {
return false
}
}
},
mounted() {
this.start = Date.now()
this.init()
},
emits: ["confirm", "close"],
methods: {
addUnit,
// propsref
setFormatter(e) {
this.innerFormatter = e
},
// month
monthSelected(e,scene ='init') {
this.selected = e
if (!this.showConfirm) {
// 2
if (
this.mode === 'multiple' ||
this.mode === 'single' ||
(this.mode === 'range' && this.selected.length >= 2)
) {
if( scene === 'init'){
return
}
if( scene === 'tap') {
this.$emit('confirm', this.selected)
}
}
}
},
init() {
// maxDateminDate
if (
this.innerMaxDate &&
this.innerMinDate &&
new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()
) {
return error('maxDate不能小于minDate时间')
}
//
this.listHeight = this.rowHeight * 5 + 30
this.setMonth()
},
close() {
this.$emit('close')
},
//
confirm() {
if (!this.buttonDisabled) {
this.$emit('confirm', this.selected)
}
},
//
getMonths(minDate, maxDate) {
const minYear = dayjs(minDate).year()
const minMonth = dayjs(minDate).month() + 1
const maxYear = dayjs(maxDate).year()
const maxMonth = dayjs(maxDate).month() + 1
return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1
},
//
setMonth() {
//
const minDate = this.innerMinDate || dayjs().valueOf()
// 3
const maxDate =
this.innerMaxDate ||
dayjs(minDate)
.add(this.monthNum - 1, 'month')
.valueOf()
//
const months = range(
1,
this.monthNum,
this.getMonths(minDate, maxDate)
)
//
this.months = []
for (let i = 0; i < months; i++) {
this.months.push({
date: new Array(
dayjs(minDate).add(i, 'month').daysInMonth()
)
.fill(1)
.map((item, index) => {
// 1-31
let day = index + 1
// 0-60
const week = dayjs(minDate)
.add(i, 'month')
.date(day)
.day()
const date = dayjs(minDate)
.add(i, 'month')
.date(day)
.format('YYYY-MM-DD')
let bottomInfo = ''
if (this.showLunar) {
//
const lunar = Calendar.solar2lunar(
dayjs(date).year(),
dayjs(date).month() + 1,
dayjs(date).date()
)
bottomInfo = lunar.IDayCn
}
let config = {
day,
week,
// disabled
disabled:
dayjs(date).isBefore(
dayjs(minDate).format('YYYY-MM-DD')
) ||
dayjs(date).isAfter(
dayjs(maxDate).format('YYYY-MM-DD')
),
// formatter
date: new Date(date),
bottomInfo,
dot: false,
month:
dayjs(minDate).add(i, 'month').month() + 1
}
const formatter =
this.formatter || this.innerFormatter
return formatter(config)
}),
//
month: dayjs(minDate).add(i, 'month').month() + 1,
//
year: dayjs(minDate).add(i, 'month').year()
})
}
},
//
scrollIntoDefaultMonth(selected) {
//
const _index = this.months.findIndex(({
year,
month
}) => {
month = padZero(month)
return `${year}-${month}` === selected
})
if (_index !== -1) {
// #ifndef MP-WEIXIN
this.$nextTick(() => {
this.scrollIntoView = `month-${_index}`
this.scrollIntoViewScroll = this.scrollIntoView
})
// #endif
// #ifdef MP-WEIXIN
this.scrollTop = this.months[_index].top || 0;
// #endif
}
},
// scroll-view
onScroll(event) {
// 0scroll-view
const scrollTop = Math.max(0, event.detail.scrollTop)
//
for (let i = 0; i < this.months.length; i++) {
if (scrollTop >= (this.months[i].top || this.listHeight)) {
this.monthIndex = i
this.scrollIntoViewScroll = `month-${i}`
}
}
},
// top
updateMonthTop(topArr = []) {
// toponScroll
topArr.map((item, index) => {
this.months[index].top = item
})
//
if (!this.defaultDate) {
//
const selected = dayjs().format("YYYY-MM")
this.scrollIntoDefaultMonth(selected)
return
}
let selected = dayjs().format("YYYY-MM");
// Date
if (!test.array(this.defaultDate)) {
selected = dayjs(this.defaultDate).format("YYYY-MM")
} else {
selected = dayjs(this.defaultDate[0]).format("YYYY-MM");
}
this.scrollIntoDefaultMonth(selected)
}
}
}
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
.u-calendar {
&__confirm {
padding: 7px 18px;
}
}
</style>

View File

@ -0,0 +1,86 @@
import dayjs from 'dayjs/esm/index'
export default {
methods: {
// 设置月份数据
setMonth() {
// 月初是周几
const day = dayjs(this.date).date(1).day()
const start = day == 0 ? 6 : day - 1
// 本月天数
const days = dayjs(this.date).endOf('month').format('D')
// 上个月天数
const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D')
// 日期数据
const arr = []
// 清空表格
this.month = []
// 添加上月数据
arr.push(
...new Array(start).fill(1).map((e, i) => {
const day = prevDays - start + i + 1
return {
value: day,
disabled: true,
date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD')
}
})
)
// 添加本月数据
arr.push(
...new Array(days - 0).fill(1).map((e, i) => {
const day = i + 1
return {
value: day,
date: dayjs(this.date).date(day).format('YYYY-MM-DD')
}
})
)
// 添加下个月
arr.push(
...new Array(42 - days - start).fill(1).map((e, i) => {
const day = i + 1
return {
value: day,
disabled: true,
date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD')
}
})
)
// 分割数组
for (let n = 0; n < arr.length; n += 7) {
this.month.push(
arr.slice(n, n + 7).map((e, i) => {
e.index = i + n
// 自定义信息
const custom = this.customList.find((c) => c.date == e.date)
// 农历
if (this.lunar) {
const {
IDayCn,
IMonthCn
} = this.getLunar(e.date)
e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn
}
return {
...e,
...custom
}
})
)
}
}
}
}

View File

@ -0,0 +1,15 @@
// import defProps from '../../libs/config/props.js';
export default {
props: {
// 是否打乱键盘按键的顺序
random: {
type: Boolean,
default: false
},
// 输入一个中文后,是否自动切换到英文
autoChange: {
type: Boolean,
default: false
}
}
}

View File

@ -0,0 +1,315 @@
<template>
<view
class="u-keyboard"
@touchmove.stop.prevent="noop"
>
<view
v-for="(group, i) in abc ? engKeyBoardList : areaList"
:key="i"
class="u-keyboard__button"
:index="i"
:class="[i + 1 === 4 && 'u-keyboard__button--center']"
>
<view
v-if="i === 3"
class="u-keyboard__button__inner-wrapper"
>
<view
class="u-keyboard__button__inner-wrapper__left"
hover-class="u-hover-class"
:hover-stay-time="200"
@tap="changeCarInputMode"
>
<text
class="u-keyboard__button__inner-wrapper__left__lang"
:class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
></text>
<text class="u-keyboard__button__inner-wrapper__left__line">/</text>
<text
class="u-keyboard__button__inner-wrapper__left__lang"
:class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
></text>
</view>
</view>
<view
class="u-keyboard__button__inner-wrapper"
v-for="(item, j) in group"
:key="j"
>
<view
class="u-keyboard__button__inner-wrapper__inner"
:hover-stay-time="200"
@tap="carInputClick(i, j)"
hover-class="u-hover-class"
>
<text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text>
</view>
</view>
<view
v-if="i === 3"
@touchstart="backspaceClick"
@touchend="clearTimer"
class="u-keyboard__button__inner-wrapper"
>
<view
class="u-keyboard__button__inner-wrapper__right"
hover-class="u-hover-class"
:hover-stay-time="200"
>
<u-icon
size="28"
name="backspace"
color="#303133"
></u-icon>
</view>
</view>
</view>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { randomArray, sleep } from '../../libs/function/index';
/**
* keyboard 键盘组件
* @description 此为uView自定义的键盘面板内含了数字键盘车牌号键身份证号键盘3种模式都有可以打乱按键顺序的选项
* @tutorial https://uview-plus.jiangruyi.com/components/keyboard.html
* @property {Boolean} random 是否打乱键盘的顺序
* @event {Function} change 点击键盘触发
* @event {Function} backspace 点击退格键触发
* @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard>
*/
export default {
name: "u-keyboard",
mixins: [mpMixin, mixin, props],
data() {
return {
// abc=truebac=false
abc: false
};
},
computed: {
areaList() {
let data = [
'京',
'沪',
'粤',
'津',
'冀',
'豫',
'云',
'辽',
'黑',
'湘',
'皖',
'鲁',
'苏',
'浙',
'赣',
'鄂',
'桂',
'甘',
'晋',
'陕',
'蒙',
'吉',
'闽',
'贵',
'渝',
'川',
'青',
'琼',
'宁',
'挂',
'藏',
'港',
'澳',
'新',
'使',
'学'
];
let tmp = [];
//
if (this.random) data = 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 = 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;
}
},
emits: ["change", "backspace"],
methods: {
//
carInputClick(i, j) {
let value = '';
//
if (this.abc) value = this.engKeyBoardList[i][j];
else value = this.areaList[i][j];
//
if (!this.abc && this.autoChange) sleep(200).then(() => this.abc = true)
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/components.scss";
$u-car-keyboard-background-color: rgb(224, 228, 230) !default;
$u-car-keyboard-padding:6px 0 6px !default;
$u-car-keyboard-button-inner-width:64rpx !default;
$u-car-keyboard-button-inner-background-color:#FFFFFF !default;
$u-car-keyboard-button-height:80rpx !default;
$u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;
$u-car-keyboard-button-border-radius:4px !default;
$u-car-keyboard-button-inner-margin:8rpx 5rpx !default;
$u-car-keyboard-button-text-font-size:16px !default;
$u-car-keyboard-button-text-color:$u-main-color !default;
$u-car-keyboard-center-inner-margin: 0 4rpx !default;
$u-car-keyboard-special-button-width:134rpx !default;
$u-car-keyboard-lang-font-size:16px !default;
$u-car-keyboard-lang-color:$u-main-color !default;
$u-car-keyboard-active-color:$u-primary !default;
$u-car-keyboard-line-font-size:15px !default;
$u-car-keyboard-line-color:$u-main-color !default;
$u-car-keyboard-line-margin:0 1px !default;
$u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default;
.u-keyboard {
@include flex(column);
justify-content: space-around;
background-color: $u-car-keyboard-background-color;
align-items: stretch;
padding: $u-car-keyboard-padding;
&__button {
@include flex;
justify-content: center;
flex: 1;
/* #ifndef APP-NVUE */
/* #endif */
&__inner-wrapper {
box-shadow: $u-car-keyboard-button-inner-box-shadow;
margin: $u-car-keyboard-button-inner-margin;
border-radius: $u-car-keyboard-button-border-radius;
&__inner {
@include flex;
justify-content: center;
align-items: center;
width: $u-car-keyboard-button-inner-width;
background-color: $u-car-keyboard-button-inner-background-color;
height: $u-car-keyboard-button-height;
border-radius: $u-car-keyboard-button-border-radius;
&__text {
font-size: $u-car-keyboard-button-text-font-size;
color: $u-car-keyboard-button-text-color;
}
}
&__left,
&__right {
border-radius: $u-car-keyboard-button-border-radius;
width: $u-car-keyboard-special-button-width;
height: $u-car-keyboard-button-height;
background-color: $u-car-keyboard-u-hover-class-background-color;
@include flex;
justify-content: center;
align-items: center;
box-shadow: $u-car-keyboard-button-inner-box-shadow;
}
&__left {
&__line {
font-size: $u-car-keyboard-line-font-size;
color: $u-car-keyboard-line-color;
margin: $u-car-keyboard-line-margin;
}
&__lang {
font-size: $u-car-keyboard-lang-font-size;
color: $u-car-keyboard-lang-color;
&--active {
color: $u-car-keyboard-active-color;
}
}
}
}
}
}
.u-hover-class {
background-color: $u-car-keyboard-u-hover-class-background-color;
}
</style>

View File

@ -0,0 +1,15 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 分组标题
title: {
type: String,
default: () => defProps.cellGroup.title
},
// 是否显示外边框
border: {
type: Boolean,
default: () => defProps.cellGroup.border
}
}
}

View File

@ -0,0 +1,67 @@
<template>
<view :style="[addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
<view v-if="title" class="u-cell-group__title">
<slot name="title">
<text class="u-cell-group__title__text">{{ title }}</text>
</slot>
</view>
<view class="u-cell-group__wrapper">
<u-line v-if="border"></u-line>
<slot />
</view>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addStyle } from '../../libs/function/index';
/**
* cellGroup 单元格
* @description cell单元格一般用于一组列表的情况比如个人中心页设置页等
* @tutorial https://uview-plus.jiangruyi.com/components/cell.html
*
* @property {String} title 分组标题
* @property {Boolean} border 是否显示外边框 (默认 true )
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function} click 点击cell列表时触发
* @example <u-cell-group title="设置喜好">
*/
export default {
name: 'u-cell-group',
mixins: [mpMixin, mixin, props],
methods: {
addStyle
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-cell-group-title-padding: 16px 16px 8px !default;
$u-cell-group-title-font-size: 15px !default;
$u-cell-group-title-line-height: 16px !default;
$u-cell-group-title-color: $u-main-color !default;
.u-cell-group {
flex: 1;
&__title {
padding: $u-cell-group-title-padding;
&__text {
font-size: $u-cell-group-title-font-size;
line-height: $u-cell-group-title-line-height;
color: $u-cell-group-title-color;
}
}
&__wrapper {
position: relative;
}
}
</style>

View File

@ -0,0 +1,111 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 标题
title: {
type: [String, Number],
default: () => defProps.cell.title
},
// 标题下方的描述信息
label: {
type: [String, Number],
default: () => defProps.cell.label
},
// 右侧的内容
value: {
type: [String, Number],
default: () => defProps.cell.value
},
// 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
icon: {
type: String,
default: () => defProps.cell.icon
},
// 是否禁用cell
disabled: {
type: Boolean,
default: () => defProps.cell.disabled
},
// 是否显示下边框
border: {
type: Boolean,
default: () => defProps.cell.border
},
// 内容是否垂直居中(主要是针对右侧的value部分)
center: {
type: Boolean,
default: () => defProps.cell.center
},
// 点击后跳转的URL地址
url: {
type: String,
default: () => defProps.cell.url
},
// 链接跳转的方式内部使用的是uView封装的route方法可能会进行拦截操作
linkType: {
type: String,
default: () => defProps.cell.linkType
},
// 是否开启点击反馈(表现为点击时加上灰色背景)
clickable: {
type: Boolean,
default: () => defProps.cell.clickable
},
// 是否展示右侧箭头并开启点击反馈
isLink: {
type: Boolean,
default: () => defProps.cell.isLink
},
// 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
required: {
type: Boolean,
default: () => defProps.cell.required
},
// 右侧的图标箭头
rightIcon: {
type: String,
default: () => defProps.cell.rightIcon
},
// 右侧箭头的方向可选值为leftupdown
arrowDirection: {
type: String,
default: () => defProps.cell.arrowDirection
},
// 左侧图标样式
iconStyle: {
type: [Object, String],
default: () => {
return defProps.cell.iconStyle
}
},
// 右侧箭头图标的样式
rightIconStyle: {
type: [Object, String],
default: () => {
return defProps.cell.rightIconStyle
}
},
// 标题的样式
titleStyle: {
type: [Object, String],
default: () => {
return defProps.cell.titleStyle
}
},
// 单位元的大小可选值为large
size: {
type: String,
default: () => defProps.cell.size
},
// 点击cell是否阻止事件传播
stop: {
type: Boolean,
default: () => defProps.cell.stop
},
// 标识符cell被点击时返回
name: {
type: [Number, String],
default: () => defProps.cell.name
}
}
}

View File

@ -0,0 +1,246 @@
<template>
<view class="u-cell" :class="[customClass]" :style="[addStyle(customStyle)]"
:hover-class="(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''" :hover-stay-time="250"
@tap="clickHandler">
<view class="u-cell__body" :class="[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']">
<view class="u-cell__body__content">
<view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon">
<slot name="icon" v-if="$slots.icon">
</slot>
<u-icon v-else :name="icon"
:custom-style="iconStyle"
:size="size === 'large' ? 22 : 18"></u-icon>
</view>
<view class="u-cell__title">
<!-- 将slot与默认内容用if/else分开主要是因为微信小程序不支持slot嵌套传递这样才能解决collapse组件的slot不失效问题label暂时未用到 -->
<slot name="title" v-if="$slots.title || !title">
</slot>
<text v-else class="u-cell__title-text" :style="[titleTextStyle]"
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
<slot name="label">
<text class="u-cell__label" v-if="label"
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text>
</slot>
</view>
</view>
<slot name="value">
<text class="u-cell__value"
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']"
v-if="!testEmpty(value)">{{ value }}</text>
</slot>
<view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink"
:class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
<slot name="right-icon">
<u-icon v-if="rightIcon" :name="rightIcon" :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
:size="size === 'large' ? 18 : 16"></u-icon>
</slot>
</view>
<view class="u-cell__right-icon-wrap" v-if="$slots['righticon']"
:class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
<slot name="righticon">
</slot>
</view>
</view>
<u-line v-if="border"></u-line>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addStyle } from '../../libs/function/index';
import test from '../../libs/function/test';
/**
* cell 单元格
* @description cell单元格一般用于一组列表的情况比如个人中心页设置页等
* @tutorial https://uview-plus.jiangruyi.com/components/cell.html
* @property {String | Number} title 标题
* @property {String | Number} label 标题下方的描述信息
* @property {String | Number} value 右侧的内容
* @property {String} icon 左侧图标名称或者图片链接(本地文件建议使用绝对地址)
* @property {Boolean} disabled 是否禁用cell
* @property {Boolean} border 是否显示下边框 (默认 true )
* @property {Boolean} center 内容是否垂直居中(主要是针对右侧的value部分) (默认 false )
* @property {String} url 点击后跳转的URL地址
* @property {String} linkType 链接跳转的方式内部使用的是uView封装的route方法可能会进行拦截操作 (默认 'navigateTo' )
* @property {Boolean} clickable 是否开启点击反馈(表现为点击时加上灰色背景) 默认 false
* @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 默认 false
* @property {Boolean} required 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) 默认 false
* @property {String} rightIcon 右侧的图标箭头 默认 'arrow-right'
* @property {String} arrowDirection 右侧箭头的方向可选值为leftupdown
* @property {Object | String} rightIconStyle 右侧箭头图标的样式
* @property {Object | String} titleStyle 标题的样式
* @property {Object | String} iconStyle 左侧图标样式
* @property {String} size 单位元的大小可选值为 largenormalmini
* @property {Boolean} stop 点击cell是否阻止事件传播 (默认 true )
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function} click 点击cell列表时触发
* @example 该组件需要搭配cell-group组件使用见官方文档示例
*/
export default {
name: 'u-cell',
data() {
return {
}
},
mixins: [mpMixin, mixin, props],
computed: {
titleTextStyle() {
return addStyle(this.titleStyle)
}
},
emits: ['click'],
methods: {
addStyle,
testEmpty: test.empty,
// cell
clickHandler(e) {
if (this.disabled) return
this.$emit('click', {
name: this.name
})
// url(propsmixin)
this.openPage()
//
this.stop && this.preventEvent(e)
},
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-cell-padding: 13px 15px !default;
$u-cell-font-size: 15px !default;
$u-cell-line-height: 24px !default;
$u-cell-color: $u-main-color !default;
$u-cell-icon-size: 16px !default;
$u-cell-title-font-size: 15px !default;
$u-cell-title-line-height: 22px !default;
$u-cell-title-color: $u-main-color !default;
$u-cell-label-font-size: 12px !default;
$u-cell-label-color: $u-tips-color !default;
$u-cell-label-line-height: 18px !default;
$u-cell-value-font-size: 14px !default;
$u-cell-value-color: $u-content-color !default;
$u-cell-clickable-color: $u-bg-color !default;
$u-cell-disabled-color: #c8c9cc !default;
$u-cell-padding-top-large: 13px !default;
$u-cell-padding-bottom-large: 13px !default;
$u-cell-value-font-size-large: 15px !default;
$u-cell-label-font-size-large: 14px !default;
$u-cell-title-font-size-large: 16px !default;
$u-cell-left-icon-wrap-margin-right: 4px !default;
$u-cell-right-icon-wrap-margin-left: 4px !default;
$u-cell-title-flex:1 !default;
$u-cell-label-margin-top:5px !default;
.u-cell {
&__body {
@include flex();
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
padding: $u-cell-padding;
font-size: $u-cell-font-size;
color: $u-cell-color;
// line-height: $u-cell-line-height;
align-items: center;
&__content {
@include flex(row);
align-items: center;
flex: 1;
}
&--large {
padding-top: $u-cell-padding-top-large;
padding-bottom: $u-cell-padding-bottom-large;
}
}
&__left-icon-wrap,
&__right-icon-wrap {
@include flex();
align-items: center;
// height: $u-cell-line-height;
font-size: $u-cell-icon-size;
}
&__left-icon-wrap {
margin-right: $u-cell-left-icon-wrap-margin-right;
}
&__right-icon-wrap {
margin-left: $u-cell-right-icon-wrap-margin-left;
transition: transform 0.3s;
&--up {
transform: rotate(-90deg);
}
&--down {
transform: rotate(90deg);
}
}
&__title {
flex: $u-cell-title-flex;
&-text {
font-size: $u-cell-title-font-size;
line-height: $u-cell-title-line-height;
color: $u-cell-title-color;
&--large {
font-size: $u-cell-title-font-size-large;
}
}
}
&__label {
margin-top: $u-cell-label-margin-top;
font-size: $u-cell-label-font-size;
color: $u-cell-label-color;
line-height: $u-cell-label-line-height;
&--large {
font-size: $u-cell-label-font-size-large;
}
}
&__value {
text-align: right;
/* #ifndef APP-NVUE */
margin-left: auto;
/* #endif */
font-size: $u-cell-value-font-size;
line-height: $u-cell-line-height;
color: $u-cell-value-color;
&--large {
font-size: $u-cell-value-font-size-large;
}
}
&--clickable {
background-color: $u-cell-clickable-color;
}
&--disabled {
color: $u-cell-disabled-color;
/* #ifndef APP-NVUE */
cursor: not-allowed;
/* #endif */
}
&--center {
align-items: center;
}
}
</style>

View File

@ -0,0 +1,92 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 标识符
name: {
type: String,
default: () => defProps.checkboxGroup.name
},
// #ifdef VUE3
// 绑定的值
modelValue: {
type: Array,
default: () => defProps.checkboxGroup.value
},
// #endif
// #ifdef VUE2
// 绑定的值
value: {
type: Array,
default: () => defProps.checkboxGroup.value
},
// #endif
// 形状circle-圆形square-方形
shape: {
type: String,
default: () => defProps.checkboxGroup.shape
},
// 是否禁用全部checkbox
disabled: {
type: Boolean,
default: () => defProps.checkboxGroup.disabled
},
// 选中状态下的颜色如设置此值将会覆盖parent的activeColor值
activeColor: {
type: String,
default: () => defProps.checkboxGroup.activeColor
},
// 未选中的颜色
inactiveColor: {
type: String,
default: () => defProps.checkboxGroup.inactiveColor
},
// 整个组件的尺寸默认px
size: {
type: [String, Number],
default: () => defProps.checkboxGroup.size
},
// 布局方式row-横向column-纵向
placement: {
type: String,
default: () => defProps.checkboxGroup.placement
},
// label的字体大小px单位
labelSize: {
type: [String, Number],
default: () => defProps.checkboxGroup.labelSize
},
// label的字体颜色
labelColor: {
type: [String],
default: () => defProps.checkboxGroup.labelColor
},
// 是否禁止点击文本操作
labelDisabled: {
type: Boolean,
default: () => defProps.checkboxGroup.labelDisabled
},
// 图标颜色
iconColor: {
type: String,
default: () => defProps.checkboxGroup.iconColor
},
// 图标的大小单位px
iconSize: {
type: [String, Number],
default: () => defProps.checkboxGroup.iconSize
},
// 勾选图标的对齐方式left-左边right-右边
iconPlacement: {
type: String,
default: () => defProps.checkboxGroup.iconPlacement
},
// 竖向配列时,是否显示下划线
borderBottom: {
type: Boolean,
default: () => defProps.checkboxGroup.borderBottom
}
}
}

View File

@ -0,0 +1,133 @@
<template>
<view
class="u-checkbox-group"
:class="bemClass"
>
<slot></slot>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
/**
* checkboxGroup 复选框组
* @description 复选框组件一般用于需要多个选择的场景该组件功能完整使用方便
* @tutorial https://ijry.github.io/uview-plus/components/checkbox.html
* @property {String} name 标识符
* @property {Array} value 绑定的值
* @property {String} shape 形状circle-圆形square-方形 默认 'square'
* @property {Boolean} disabled 是否禁用全部checkbox 默认 false
* @property {String} activeColor 选中状态下的颜色如设置此值将会覆盖parent的activeColor值 默认 '#2979ff'
* @property {String} inactiveColor 未选中的颜色 默认 '#c8c9cc'
* @property {String | Number} size 整个组件的尺寸 单位px 默认 18
* @property {String} placement 布局方式row-横向column-纵向 默认 'row'
* @property {String | Number} labelSize label的字体大小px单位 默认 14
* @property {String} labelColor label的字体颜色 默认 '#303133'
* @property {Boolean} labelDisabled 是否禁止点击文本操作 (默认 false )
* @property {String} iconColor 图标颜色 默认 '#ffffff'
* @property {String | Number} iconSize 图标的大小单位px 默认 12
* @property {String} iconPlacement 勾选图标的对齐方式left-左边right-右边 默认 'left'
* @property {Boolean} borderBottom placement为row时是否显示下边框 默认 false
* @event {Function} change 任一个checkbox状态发生变化时触发回调为一个对象
* @event {Function} input 修改通过v-model绑定的值时触发回调为一个对象
* @example <u-checkbox-group></u-checkbox-group>
*/
export default {
name: 'u-checkbox-group',
mixins: [mpMixin, mixin,props],
computed: {
// computedu-checkbox
// parentDatawatch(u-checkbox-group)
//
parentData() {
return [
// #ifdef VUE2
this.value,
// #endif
// #ifdef VUE3
this.modelValue,
// #endif
this.disabled,
this.inactiveColor,
this.activeColor,
this.size,
this.labelDisabled,
this.shape,
this.iconSize,
this.borderBottom,
this.placement,
];
},
bemClass() {
// this.bemcomputedmixin
return this.bem('checkbox-group', ['placement'])
},
},
watch: {
//
parentData: {
handler() {
if (this.children.length) {
this.children.map((child) => {
// (u-checkbox)init()
typeof child.init === "function" && child.init();
});
}
},
deep: true,
},
},
data() {
return {
}
},
created() {
this.children = []
},
// #ifdef VUE3
emits: ['update:modelValue', 'change'],
// #endif
methods: {
// checkbox
unCheckedOther(childInstance) {
const values = []
this.children.map(child => {
// checkbox
if (child.isChecked) {
values.push(child.name)
}
})
//
this.$emit('change', values)
// v-model
// #ifdef VUE3
this.$emit("update:modelValue", values);
// #endif
// #ifdef VUE2
this.$emit("input", values);
// #endif
},
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-checkbox-group {
&--row {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-flow: row wrap;
}
&--column {
@include flex(column);
}
}
</style>

View File

@ -0,0 +1,75 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// checkbox的名称
name: {
type: [String, Number, Boolean],
default: () => defProps.checkbox.name
},
// 形状square为方形circle为圆型
shape: {
type: String,
default: () => defProps.checkbox.shape
},
// 整体的大小
size: {
type: [String, Number],
default: () => defProps.checkbox.size
},
// 是否默认选中
checked: {
type: Boolean,
default: () => defProps.checkbox.checked
},
// 是否禁用
disabled: {
type: [String, Boolean],
default: () => defProps.checkbox.disabled
},
// 选中状态下的颜色如设置此值将会覆盖parent的activeColor值
activeColor: {
type: String,
default: () => defProps.checkbox.activeColor
},
// 未选中的颜色
inactiveColor: {
type: String,
default: () => defProps.checkbox.inactiveColor
},
// 图标的大小单位px
iconSize: {
type: [String, Number],
default: () => defProps.checkbox.iconSize
},
// 图标颜色
iconColor: {
type: String,
default: () => defProps.checkbox.iconColor
},
// label提示文字因为nvue下直接slot进来的文字由于特殊的结构无法修改样式
label: {
type: [String, Number],
default: () => defProps.checkbox.label
},
// label的字体大小px单位
labelSize: {
type: [String, Number],
default: () => defProps.checkbox.labelSize
},
// label的颜色
labelColor: {
type: String,
default: () => defProps.checkbox.labelColor
},
// 是否禁止点击提示语选中复选框
labelDisabled: {
type: [String, Boolean],
default: () => defProps.checkbox.labelDisabled
},
// 是否独立使用
usedAlone: {
type: [Boolean],
default: () => false
}
}
}

View File

@ -0,0 +1,374 @@
<template>
<view
class="u-checkbox cursor-pointer"
:style="[checkboxStyle]"
@tap.stop="wrapperClickHandler"
:class="[`u-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']"
>
<view
class="u-checkbox__icon-wrap cursor-pointer"
@tap.stop="iconClickHandler"
:class="iconClasses"
:style="[iconWrapStyle]"
>
<slot name="icon">
<u-icon
class="u-checkbox__icon-wrap__icon"
name="checkbox-mark"
:size="elIconSize"
:color="elIconColor"
/>
</slot>
</view>
<text
@tap.stop="labelClickHandler"
:style="{
color: elDisabled ? elInactiveColor : elLabelColor,
fontSize: elLabelSize,
lineHeight: elLabelSize
}"
>{{label}}</text>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addStyle, addUnit, deepMerge, formValidate, error } from '../../libs/function/index';
import test from '../../libs/function/test';
/**
* checkbox 复选框
* @description 复选框组件一般用于需要多个选择的场景该组件功能完整使用方便
* @tutorial https://uview-plus.jiangruyi.com/components/checkbox.html
* @property {String | Number | Boolean} name checkbox组件的标示符
* @property {String} shape 形状square为方形circle为圆型
* @property {String | Number} size 整体的大小
* @property {Boolean} checked 是否默认选中
* @property {String | Boolean} disabled 是否禁用
* @property {String} activeColor 选中状态下的颜色如设置此值将会覆盖parent的activeColor值
* @property {String} inactiveColor 未选中的颜色
* @property {String | Number} iconSize 图标的大小单位px
* @property {String} iconColor 图标颜色
* @property {String | Number} label label提示文字因为nvue下直接slot进来的文字由于特殊的结构无法修改样式
* @property {String} labelColor label的颜色
* @property {String | Number} labelSize label的字体大小px单位
* @property {String | Boolean} labelDisabled 是否禁止点击提示语选中复选框
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function} change 任一个checkbox状态发生变化时触发回调为一个对象
* @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox>
*/
export default {
name: "u-checkbox",
mixins: [mpMixin, mixin, props],
data() {
return {
isChecked: false,
// computed使this.parent.shape
// 使
parentData: {
iconSize: 12,
labelDisabled: null,
disabled: null,
shape: 'square',
activeColor: null,
inactiveColor: null,
size: 18,
// #ifdef VUE2
value: null,
// #endif
// #ifdef VUE3
modelValue: null,
// #endif
iconColor: null,
placement: 'row',
borderBottom: false,
iconPlacement: 'left'
}
}
},
computed: {
// u-raios-group
elDisabled() {
return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;
},
// label
elLabelDisabled() {
return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :
false;
},
// size21px
elSize() {
return this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);
},
// 12px
elIconSize() {
return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);
},
//
elActiveColor() {
return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');
},
//
elInactiveColor() {
return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :
'#c8c9cc');
},
// label
elLabelColor() {
return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')
},
//
elShape() {
return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');
},
// label
elLabelSize() {
return addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :
'15'))
},
elIconColor() {
const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :
'#ffffff');
//
if (this.elDisabled) {
// disabledcheckboxelInactiveColor
return this.isChecked ? this.elInactiveColor : 'transparent'
} else {
return this.isChecked ? iconColor : 'transparent'
}
},
iconClasses() {
let classes = []
//
classes.push('u-checkbox__icon-wrap--' + this.elShape)
if (this.elDisabled) {
classes.push('u-checkbox__icon-wrap--disabled')
}
if (this.isChecked && this.elDisabled) {
classes.push('u-checkbox__icon-wrap--disabled--checked')
}
// ","
// #ifdef MP-ALIPAY || MP-TOUTIAO
classes = classes.join(' ')
// #endif
return classes
},
iconWrapStyle() {
// checkbox
const style = {}
style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff'
style.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor
style.width = addUnit(this.elSize)
style.height = addUnit(this.elSize)
//
if (!this.usedAlone) {
if (this.parentData.iconPlacement === 'right') {
style.marginRight = 0
}
}
return style
},
checkboxStyle() {
const style = {}
if (!this.usedAlone) {
if (this.parentData.borderBottom && this.parentData.placement === 'row') {
error('检测到您将borderBottom设置为true需要同时将u-checkbox-group的placement设置为column才有效')
}
//
if (this.parentData.borderBottom && this.parentData.placement === 'column') {
style.paddingBottom = '8px'
}
}
return deepMerge(style, addStyle(this.customStyle))
}
},
mounted() {
this.init()
},
emits: ["change"],
methods: {
init() {
if (!this.usedAlone) {
// provide/inject使created
this.updateParentData()
if (!this.parent) {
error('u-checkbox必须搭配u-checkbox-group组件使用')
}
}
// #ifdef VUE2
const value = this.parentData.value
// #endif
// #ifdef VUE3
const value = this.parentData.modelValue
// #endif
// u-checkbox-groupvaluearray
if (this.checked) {
this.isChecked = true
} else if (!this.usedAlone && test.array(value)) {
// this.name
this.isChecked = value.some(item => {
return item === this.name
})
}
},
updateParentData() {
this.getParentData('u-checkbox-group')
},
//
wrapperClickHandler(e) {
if (!this.usedAlone) {
this.parentData.iconPlacement === 'right' && this.iconClickHandler(e)
} else {
this.iconClickHandler(e)
}
},
//
iconClickHandler(e) {
this.preventEvent(e)
//
if (!this.elDisabled) {
this.setRadioCheckedStatus()
}
},
// label
labelClickHandler(e) {
this.preventEvent(e)
// label
if (!this.elLabelDisabled && !this.elDisabled) {
this.setRadioCheckedStatus()
}
},
emitEvent() {
this.$emit('change', this.isChecked)
// u-form
this.$nextTick(() => {
formValidate(this, 'change')
})
},
//
// checkedtrueu-checkbox
// u-checkboxcheckedfalse()
setRadioCheckedStatus() {
//
this.isChecked = !this.isChecked
this.emitEvent()
if (!this.usedAlone) {
typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)
}
}
},
watch:{
checked(){
this.isChecked = this.checked
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-checkbox-icon-wrap-margin-right:6px !default;
$u-checkbox-icon-wrap-font-size:6px !default;
$u-checkbox-icon-wrap-border-width:1px !default;
$u-checkbox-icon-wrap-border-color:#c8c9cc !default;
$u-checkbox-icon-wrap-icon-line-height:0 !default;
$u-checkbox-icon-wrap-circle-border-radius:100% !default;
$u-checkbox-icon-wrap-square-border-radius:3px !default;
$u-checkbox-icon-wrap-checked-color:#fff !default;
$u-checkbox-icon-wrap-checked-background-color:red !default;
$u-checkbox-icon-wrap-checked-border-color:#2979ff !default;
$u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default;
$u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default;
$u-checkbox-label-margin-left:5px !default;
$u-checkbox-label-margin-right:12px !default;
$u-checkbox-label-color:$u-content-color !default;
$u-checkbox-label-font-size:15px !default;
$u-checkbox-label-disabled-color:#c8c9cc !default;
.u-checkbox {
/* #ifndef APP-NVUE */
@include flex(row);
/* #endif */
overflow: hidden;
flex-direction: row;
align-items: center;
margin-bottom: 5px;
margin-top: 5px;
&-label--left {
flex-direction: row
}
&-label--right {
flex-direction: row-reverse;
justify-content: space-between
}
&__icon-wrap {
/* #ifndef APP-NVUE */
box-sizing: border-box;
// nvueborder-color
transition-property: border-color, background-color, color;
transition-duration: 0.2s;
/* #endif */
color: $u-content-color;
@include flex;
align-items: center;
justify-content: center;
color: transparent;
text-align: center;
margin-right: $u-checkbox-icon-wrap-margin-right;
font-size: $u-checkbox-icon-wrap-font-size;
border-width: $u-checkbox-icon-wrap-border-width;
border-color: $u-checkbox-icon-wrap-border-color;
border-style: solid;
/* #ifdef MP-TOUTIAO */
// 0
&__icon {
line-height: $u-checkbox-icon-wrap-icon-line-height;
}
/* #endif */
&--circle {
border-radius: $u-checkbox-icon-wrap-circle-border-radius;
}
&--square {
border-radius: $u-checkbox-icon-wrap-square-border-radius;
}
&--checked {
color: $u-checkbox-icon-wrap-checked-color;
background-color: $u-checkbox-icon-wrap-checked-background-color;
border-color: $u-checkbox-icon-wrap-checked-border-color;
}
&--disabled {
background-color: $u-checkbox-icon-wrap-disabled-background-color !important;
}
&--disabled--checked {
color: $u-checkbox-icon-wrap-disabled-checked-color !important;
}
}
&__label {
/* #ifndef APP-NVUE */
word-wrap: break-word;
/* #endif */
margin-left: $u-checkbox-label-margin-left;
margin-right: $u-checkbox-label-margin-right;
color: $u-checkbox-label-color;
font-size: $u-checkbox-label-font-size;
&--disabled {
color: $u-checkbox-label-disabled-color;
}
}
}
</style>

View File

@ -0,0 +1,9 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
percentage: {
type: [String, Number],
default: () => defProps.circleProgress.percentage
}
}
}

View File

@ -0,0 +1,201 @@
<template>
<view class="u-circle-progress">
<view class="u-circle-progress__left">
<view
class="u-circle-progress__left__circle"
:style="[leftSyle]"
ref="left-circle"
>
</view>
</view>
<view
class="u-circle-progress__right"
>
<view
class="u-circle-progress__right__circle"
ref="right-circle"
:style="[rightSyle]"
>
</view>
</view>
<view class="u-circle-progress__circle">
</view>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import {sleep } from '../../libs/function/index';
// #ifdef APP-NVUE
const animation = uni.requireNativePlugin('animation')
// #endif
/**
* CircleProgress 圆形进度条 TODO: 待完善
* @description 展示操作或任务的当前进度比如上传文件是一个圆形的进度环
* @tutorial https://ijry.github.io/uview-plus/components/circleProgress.html
* @property {String | Number} percentage 圆环进度百分比值为数值类型0-100 (默认 30 )
* @example
*/
export default {
name: 'u-circle-progress',
mixins: [mpMixin, mixin, props],
data() {
return {
leftBorderColor: 'rgb(200, 200, 200)',
rightBorderColor: 'rgb(200, 200, 200)',
}
},
computed: {
leftSyle() {
const style = {}
style.borderTopColor = this.leftBorderColor
style.borderRightColor = this.leftBorderColor
return style
},
rightSyle() {
const style = {}
style.borderLeftColor = this.rightBorderColor
style.borderBottomColor = this.rightBorderColor
return style
}
},
mounted() {
sleep().then(() => {
this.rightBorderColor = 'rgb(66, 185, 131)'
// this.init()
})
},
methods: {
init() {
animation.transition(this.$refs['right-circle'].ref, {
styles: {
transform: 'rotate(45deg)',
transformOrigin: 'center center'
},
}, () => {
this.rightBorderColor = 'rgb(66, 185, 131)'
// animation.transition(this.$refs['right-circle'].ref, {
// styles: {
// transform: 'rotate(225deg)',
// transformOrigin: 'center center'
// },
// duration: 3000,
// }, () => {
// animation.transition(this.$refs['left-circle'].ref, {
// styles: {
// transform: 'rotate(45deg)',
// transformOrigin: 'center center'
// },
// }, () => {
// this.leftBorderColor = 'rgb(66, 185, 131)'
// animation.transition(this.$refs['left-circle'].ref, {
// styles: {
// transform: 'rotate(225deg)',
// transformOrigin: 'center center'
// },
// duration: 1500,
// }, () => {
// })
// })
// })
})
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-circle-progress {
@include flex(row);
position: relative;
border-radius: 100px;
height: 100px;
width: 100px;
// transform: rotate(0deg);
// background-color: rgb(66, 185, 131);
background-color: rgb(200, 200, 200);
overflow: hidden;
justify-content: space-between;
&__circle {
border-radius: 100px;
height: 90px;
width: 90px;
transform: translate(-50%, -50%);
background-color: rgb(255, 255, 255);
left: 50px;
top: 50px;
position: absolute;
}
&__left {
position: absolute;
left: 0;
width: 50px;
height: 100px;
overflow: hidden;
box-sizing: border-box;
// background-color: rgb(66, 185, 131);
// background-color: rgb(200, 200, 200);
// transform-origin: left center;
&__circle {
box-sizing: border-box;
// background-color: red;
border-left-color: transparent;
border-bottom-color: transparent;
border-top-left-radius: 50px;
border-top-right-radius: 50px;
border-bottom-right-radius: 50px;
// border-left-color: rgb(66, 185, 131);
// border-bottom-color: rgb(66, 185, 131);
border-top-color: rgb(66, 185, 131);
border-right-color: rgb(66, 185, 131);
border-width: 5px;
width: 100px;
height: 100px;
transform: rotate(225deg);
// border-radius: 100px;
}
}
&__right {
position: absolute;
right: 0;
width: 50px;
height: 100px;
overflow: hidden;
&__circle {
position: absolute;
right: 0;
box-sizing: border-box;
// background-color: red;
border-top-color: transparent;
border-right-color: transparent;
border-top-left-radius: 50px;
border-bottom-left-radius: 50px;
border-bottom-right-radius: 50px;
// border-left-color: rgb(66, 185, 131);
// border-bottom-color: rgb(66, 185, 131);
border-left-color: rgb(200, 200, 200);
border-bottom-color: rgb(200, 200, 200);
border-width: 5px;
width: 100px;
height: 100px;
transform: rotate(45deg);
transform-origin: center center;
// border-radius: 100px;
}
}
}
</style>

View File

@ -0,0 +1,89 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 键盘弹起时,是否自动上推页面
adjustPosition: {
type: Boolean,
default: () => defProps.codeInput.adjustPosition
},
// 最大输入长度
maxlength: {
type: [String, Number],
default: () => defProps.codeInput.maxlength
},
// 是否用圆点填充
dot: {
type: Boolean,
default: () => defProps.codeInput.dot
},
// 显示模式box-盒子模式line-底部横线模式
mode: {
type: String,
default: () => defProps.codeInput.mode
},
// 是否细边框
hairline: {
type: Boolean,
default: () => defProps.codeInput.hairline
},
// 字符间的距离
space: {
type: [String, Number],
default: () => defProps.codeInput.space
},
// #ifdef VUE3
// 预置值
modelValue: {
type: [String, Number],
default: () => defProps.codeInput.value
},
// #endif
// #ifdef VUE2
// 预置值
value: {
type: [String, Number],
default: () => defProps.codeInput.value
},
// #endif
// 是否自动获取焦点
focus: {
type: Boolean,
default: () => defProps.codeInput.focus
},
// 字体是否加粗
bold: {
type: Boolean,
default: () => defProps.codeInput.bold
},
// 字体颜色
color: {
type: String,
default: () => defProps.codeInput.color
},
// 字体大小
fontSize: {
type: [String, Number],
default: () => defProps.codeInput.fontSize
},
// 输入框的大小,宽等于高
size: {
type: [String, Number],
default: () => defProps.codeInput.size
},
// 是否隐藏原生键盘如果想用自定义键盘的话需设置此参数为true
disabledKeyboard: {
type: Boolean,
default: () => defProps.codeInput.disabledKeyboard
},
// 边框和线条颜色
borderColor: {
type: String,
default: () => defProps.codeInput.borderColor
},
// 是否禁止输入"."符号
disabledDot: {
type: Boolean,
default: () => defProps.codeInput.disabledDot
}
}
}

View File

@ -0,0 +1,266 @@
<template>
<view class="u-code-input">
<view
class="u-code-input__item"
:style="[itemStyle(index)]"
v-for="(item, index) in codeLength"
:key="index"
>
<view
class="u-code-input__item__dot"
v-if="dot && codeArray.length > index"
></view>
<text
v-else
:style="{
fontSize: addUnit(fontSize),
fontWeight: bold ? 'bold' : 'normal',
color: color
}"
>{{codeArray[index]}}</text>
<view
class="u-code-input__item__line"
v-if="mode === 'line'"
:style="[lineStyle]"
></view>
<!-- #ifndef APP-PLUS -->
<view v-if="isFocus && codeArray.length === index" :style="{backgroundColor: color}" class="u-code-input__item__cursor"></view>
<!-- #endif -->
</view>
<input
:disabled="disabledKeyboard"
type="number"
:focus="focus"
:value="inputValue"
:maxlength="maxlength"
:adjustPosition="adjustPosition"
class="u-code-input__input"
@input="inputHandler"
:style="{
height: addUnit(size)
}"
@focus="isFocus = true"
@blur="isFocus = false"
/>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, getPx } from '../../libs/function/index';
/**
* CodeInput 验证码输入
* @description 该组件一般用于验证用户短信验证码的场景也可以结合uview-plus的键盘组件使用
* @tutorial https://ijry.github.io/uview-plus/components/codeInput.html
* @property {String | Number} maxlength 最大输入长度 默认 6
* @property {Boolean} dot 是否用圆点填充 默认 false
* @property {String} mode 显示模式box-盒子模式line-底部横线模式 默认 'box'
* @property {Boolean} hairline 是否细边框 默认 false
* @property {String | Number} space 字符间的距离 默认 10
* @property {String | Number} value 预置值
* @property {Boolean} focus 是否自动获取焦点 默认 false
* @property {Boolean} bold 字体和输入横线是否加粗 默认 false
* @property {String} color 字体颜色 默认 '#606266'
* @property {String | Number} fontSize 字体大小单位px 默认 18
* @property {String | Number} size 输入框的大小宽等于高 默认 35
* @property {Boolean} disabledKeyboard 是否隐藏原生键盘如果想用自定义键盘的话需设置此参数为true 默认 false
* @property {String} borderColor 边框和线条颜色 默认 '#c9cacc'
* @property {Boolean} disabledDot 是否禁止输入"."符号 默认 true
*
* @event {Function} change 输入内容发生改变时触发具体见上方说明 value当前输入的值
* @event {Function} finish 输入字符个数达maxlength值时触发见上方说明 value当前输入的值
* @example <u-code-input v-model="value4" :focus="true"></u-code-input>
*/
export default {
name: 'u-code-input',
mixins: [mpMixin, mixin, props],
data() {
return {
inputValue: '',
isFocus: this.focus
}
},
watch: {
// #ifdef VUE2
value: {
// #endif
// #ifdef VUE3
modelValue: {
// #endif
immediate: true,
handler(val) {
//
this.inputValue = String(val).substring(0, this.maxlength)
}
},
},
computed: {
// v-for
codeLength() {
return new Array(Number(this.maxlength))
},
// item
itemStyle() {
return index => {
const style = {
width: addUnit(this.size),
height: addUnit(this.size)
}
//
if (this.mode === 'box') {
// 0.5px
style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`
// 0
if (getPx(this.space) === 0) {
//
if (index === 0) {
style.borderTopLeftRadius = '3px'
style.borderBottomLeftRadius = '3px'
}
if (index === this.codeLength.length - 1) {
style.borderTopRightRadius = '3px'
style.borderBottomRightRadius = '3px'
}
//
if (index !== this.codeLength.length - 1) {
style.borderRight = 'none'
}
}
}
if (index !== this.codeLength.length - 1) {
// margin-right
style.marginRight = addUnit(this.space)
} else {
//
style.marginRight = 0
}
return style
}
},
// item
codeArray() {
return String(this.inputValue).split('')
},
// 线线
lineStyle() {
const style = {}
style.height = this.hairline ? '2px' : '4px'
style.width = addUnit(this.size)
// 线
style.backgroundColor = this.borderColor
return style
}
},
emits: ["change", 'finish', "update:modelValue"],
methods: {
addUnit,
//
inputHandler(e) {
const value = e.detail.value
this.inputValue = value
// .
if(this.disabledDot) {
this.$nextTick(() => {
this.inputValue = value.replace('.', '')
})
}
// maxlengthchangefinish
this.$emit('change', value)
// v-model
// #ifdef VUE3
this.$emit("update:modelValue", value);
// #endif
// #ifdef VUE2
this.$emit("input", value);
// #endif
//
if (String(value).length >= Number(this.maxlength)) {
this.$emit('finish', value)
}
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-code-input-cursor-width: 1px;
$u-code-input-cursor-height: 40%;
$u-code-input-cursor-animation-duration: 1s;
$u-code-input-cursor-animation-name: u-cursor-flicker;
.u-code-input {
@include flex;
position: relative;
overflow: hidden;
&__item {
@include flex;
justify-content: center;
align-items: center;
position: relative;
&__text {
font-size: 15px;
color: $u-content-color;
}
&__dot {
width: 7px;
height: 7px;
border-radius: 100px;
background-color: $u-content-color;
}
&__line {
position: absolute;
bottom: 0;
height: 4px;
border-radius: 100px;
width: 40px;
background-color: $u-content-color;
}
/* #ifndef APP-PLUS */
&__cursor {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
width: $u-code-input-cursor-width;
height: $u-code-input-cursor-height;
animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite;
}
/* #endif */
}
&__input {
// input
//
position: absolute;
left: -750rpx;
width: 1500rpx;
top: 0;
background-color: transparent;
text-align: left;
}
}
/* #ifndef APP-PLUS */
@keyframes u-cursor-flicker {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
/* #endif */
</style>

View File

@ -0,0 +1,35 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 倒计时总秒数
seconds: {
type: [String, Number],
default: () => defProps.code.seconds
},
// 尚未开始时提示
startText: {
type: String,
default: () => defProps.code.startText
},
// 正在倒计时中的提示
changeText: {
type: String,
default: () => defProps.code.changeText
},
// 倒计时结束时的提示
endText: {
type: String,
default: () => defProps.code.endText
},
// 是否在H5刷新或各端返回再进入时继续倒计时
keepRunning: {
type: Boolean,
default: () => defProps.code.keepRunning
},
// 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
uniqueKey: {
type: String,
default: () => defProps.code.uniqueKey
}
}
}

View File

@ -0,0 +1,137 @@
<template>
<view class="u-code">
<!-- 此组件功能由js完成无需写html逻辑 -->
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
/**
* Code 验证码输入框
* @description 考虑到用户实际发送验证码的场景可能是一个按钮也可能是一段文字提示语各有不同所以本组件 不提供界面显示只提供提示语由用户将提示语嵌入到具体的场景
* @tutorial https://ijry.github.io/uview-plus/components/code.html
* @property {String | Number} seconds 倒计时所需的秒数默认 60
* @property {String} startText 开始前的提示语见官网说明默认 '获取验证码'
* @property {String} changeText 倒计时期间的提示语必须带有字母"x"见官网说明默认 'X秒重新获取'
* @property {String} endText 倒计结束的提示语见官网说明默认 '重新获取'
* @property {Boolean} keepRunning 是否在H5刷新或各端返回再进入时继续倒计时 默认false
* @property {String} uniqueKey 为了区分多个页面或者一个页面多个倒计时组件本地存储的继续倒计时变了
*
* @event {Function} change 倒计时期间每秒触发一次
* @event {Function} start 开始倒计时触发
* @event {Function} end 结束倒计时触发
* @example <u-code ref="uCode" @change="codeChange" seconds="20"></u-code>
*/
export default {
name: "u-code",
mixins: [mpMixin, mixin,props],
data() {
return {
secNum: this.seconds,
timer: null,
canGetCode: true, //
}
},
mounted() {
this.checkKeepRunning()
},
watch: {
seconds: {
immediate: true,
handler(n) {
this.secNum = n
}
}
},
emits: ["start", "end", "change"],
methods: {
checkKeepRunning() {
// 退(H5)
let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'))
if(!lastTimestamp) return this.changeEvent(this.startText)
//
let nowTimestamp = Math.floor((+ new Date()) / 1000)
//
if(this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
//
this.secNum = lastTimestamp - nowTimestamp
//
uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp')
//
this.start()
} else {
//
this.changeEvent(this.startText)
}
},
//
start() {
//
if(this.timer) {
clearInterval(this.timer)
this.timer = null
}
this.$emit('start')
this.canGetCode = false
// setInterval1
this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
this.timer = setInterval(() => {
if (--this.secNum) {
// "x"
this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
} else {
clearInterval(this.timer)
this.timer = null
this.changeEvent(this.endText)
this.secNum = this.seconds
this.$emit('end')
this.canGetCode = true
}
}, 1000)
this.setTimeToStorage()
},
//
reset() {
this.canGetCode = true
clearInterval(this.timer)
this.secNum = this.seconds
this.changeEvent(this.endText)
},
changeEvent(text) {
this.$emit('change', text)
},
// H5
setTimeToStorage() {
if(!this.keepRunning) return
//
// 0
if(this.secNum > 0 && this.secNum <= this.seconds) {
// (+ new Date())1000
let nowTimestamp = Math.floor((+ new Date()) / 1000)
// => +
uni.setStorage({
key: this.uniqueKey + '_$uCountDownTimestamp',
data: nowTimestamp + Number(this.secNum)
})
}
}
},
//
// #ifdef VUE2
beforeDestroy() {
// #endif
// #ifdef VUE3
beforeUnmount() {
// #endif
this.setTimeToStorage()
clearTimeout(this.timer)
this.timer = null
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
</style>

View File

@ -0,0 +1,30 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 占父容器宽度的多少等分总分为12份
span: {
type: [String, Number],
default: () => defProps.col.span
},
// 指定栅格左侧的间隔数(总12栏)
offset: {
type: [String, Number],
default: () => defProps.col.offset
},
// 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
justify: {
type: String,
default: () => defProps.col.justify
},
// 垂直对齐方式可选值为top、center、bottom、stretch
align: {
type: String,
default: () => defProps.col.align
},
// 文字对齐方式
textAlign: {
type: String,
default: () => defProps.col.textAlign
}
}
}

View File

@ -0,0 +1,170 @@
<template>
<view
class="u-col"
ref="u-col"
:class="[
'u-col-' + span
]"
:style="[colStyle]"
@tap="clickHandler"
>
<slot></slot>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addStyle, addUnit, deepMerge, getPx } from '../../libs/function/index';
/**
* CodeInput 栅格系统的列
* @description 该组件一般用于Layout 布局 通过基础的 12 分栏迅速简便地创建布局
* @tutorial https://ijry.github.io/uview-plus/components/Layout.html
* @property {String | Number} span 栅格占据的列数总12等份 (默认 12 )
* @property {String | Number} offset 分栏左边偏移计算方式与span相同 (默认 0 )
* @property {String} justify 水平排列方式可选值为`start`(`flex-start`)`end`(`flex-end`)`center``around`(`space-around`)`between`(`space-between`) (默认 'start' )
* @property {String} align 垂直对齐方式可选值为topcenterbottomstretch (默认 'stretch' )
* @property {String} textAlign 文字水平对齐方式 (默认 'left' )
* @property {Object} customStyle 定义需要用到的外部样式
* @event {Function} click col被点击会阻止事件冒泡到row
* @example <u-col span="3" offset="3" > <view class="demo-layout bg-purple"></view> </u-col>
*/
export default {
name: 'u-col',
mixins: [mpMixin, mixin, props],
data() {
return {
width: 0,
parentData: {
gutter: 0
},
gridNum: 12
}
},
// options
options: {
virtualHost: true // Vue flex
},
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
},
colStyle() {
const style = {
// "padding: 0 10px"nvue
paddingLeft: addUnit(getPx(this.parentData.gutter)/2),
paddingRight: addUnit(getPx(this.parentData.gutter)/2),
alignItems: this.uAlignItem,
justifyContent: this.uJustify,
textAlign: this.textAlign,
// #ifndef APP-NVUE
// nvue使
flex: `0 0 ${100 / this.gridNum * this.span}%`,
marginLeft: 100 / 12 * this.offset + '%',
// #endif
// #ifdef APP-NVUE
// nvue使
width: addUnit(Math.floor(this.width / this.gridNum * Number(this.span))),
marginLeft: addUnit(Math.floor(this.width / this.gridNum * Number(this.offset))),
// #endif
}
return deepMerge(style, addStyle(this.customStyle))
}
},
mounted() {
this.init()
},
emits: ["click"],
methods: {
async init() {
// provide/inject使created
this.updateParentData()
this.width = await this.parent.getComponentWidth()
},
updateParentData() {
this.getParentData('u-row')
},
clickHandler(e) {
this.$emit('click');
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-col {
padding: 0;
/* #ifndef APP-NVUE */
box-sizing:border-box;
/* #endif */
/* #ifdef MP */
display: block;
/* #endif */
}
// nvue
/* #ifndef APP-NVUE */
.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);
}
/* #endif */
</style>

View File

@ -0,0 +1,60 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 标题
title: {
type: String,
default: () => defProps.collapseItem.title
},
// 标题右侧内容
value: {
type: String,
default: () => defProps.collapseItem.value
},
// 标题下方的描述信息
label: {
type: String,
default: () => defProps.collapseItem.label
},
// 是否禁用折叠面板
disabled: {
type: Boolean,
default: () => defProps.collapseItem.disabled
},
// 是否展示右侧箭头并开启点击反馈
isLink: {
type: Boolean,
default: () => defProps.collapseItem.isLink
},
// 是否开启点击反馈
clickable: {
type: Boolean,
default: () => defProps.collapseItem.clickable
},
// 是否显示内边框
border: {
type: Boolean,
default: () => defProps.collapseItem.border
},
// 标题的对齐方式
align: {
type: String,
default: () => defProps.collapseItem.align
},
// 唯一标识符
name: {
type: [String, Number],
default: () => defProps.collapseItem.name
},
// 标题左侧图片,可为绝对路径的图片或内置图标
icon: {
type: String,
default: () => defProps.collapseItem.icon
},
// 面板展开收起的过渡时间单位ms
duration: {
type: Number,
default: () => defProps.collapseItem.duration
}
}
}

View File

@ -0,0 +1,239 @@
<template>
<view class="u-collapse-item">
<u-cell
:title="$slots.title ? '' : title"
:value="value"
:label="label"
:icon="icon"
:isLink="isLink"
:clickable="clickable"
:border="parentData.border && showBorder"
@click="clickHandler"
:arrowDirection="expanded ? 'up' : 'down'"
:disabled="disabled"
>
<!-- 微信小程序不支持因为微信中不支持 <slot name="title" slot="title" />的写法 -->
<template #title>
<slot name="title">
<text v-if="!$slots.title && title">
{{title}}
</text>
</slot>
</template>
<template #icon>
<slot name="icon">
<u-icon v-if="!$slots.icon && icon" :size="22" :name="icon"></u-icon>
</slot>
</template>
<template #value>
<slot name="value">
<text v-if="!$slots.value && value">
{{value}}
</text>
</slot>
</template>
<template #right-icon>
<slot name="right-icon">
</slot>
</template>
</u-cell>
<view
class="u-collapse-item__content"
:animation="animationData"
ref="animation"
>
<view
class="u-collapse-item__content__text content-class"
:id="elId"
:ref="elId"
><slot /></view>
</view>
<u-line v-if="parentData.border"></u-line>
</view>
</template>
<script>
import props from './props.js';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { nextTick } from 'vue';
import { guid, sleep, error } from '../../libs/function/index';
import test from '../../libs/function/test';
// #ifdef APP-NVUE
const animation = uni.requireNativePlugin('animation')
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* collapseItem 折叠面板Item
* @description 通过折叠面板收纳内容区域搭配u-collapse使用
* @tutorial https://ijry.github.io/uview-plus/components/collapse.html
* @property {String} title 标题
* @property {String} value 标题右侧内容
* @property {String} label 标题下方的描述信息
* @property {Boolean} disbled 是否禁用折叠面板 ( 默认 false )
* @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 ( 默认 true )
* @property {Boolean} clickable 是否开启点击反馈 ( 默认 true )
* @property {Boolean} border 是否显示内边框 ( 默认 true )
* @property {String} align 标题的对齐方式 ( 默认 'left' )
* @property {String | Number} name 唯一标识符
* @property {String} icon 标题左侧图片可为绝对路径的图片或内置图标
* @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",
mixins: [mpMixin, mixin, props],
data() {
return {
elId: guid(),
// uni.createAnimation
animationData: {},
//
expanded: false,
// expandedbordercell线
showBorder: false,
//
animating: false,
// u-collapse
parentData: {
accordion: false,
border: false
}
};
},
watch: {
expanded(n) {
clearTimeout(this.timer)
this.timer = null
// expandedcell线
this.timer = setTimeout(() => {
this.showBorder = n
}, n ? 10 : 290)
}
},
mounted() {
this.init()
console.log('$slots', this.$slots)
},
methods: {
//
async init() {
//
this.updateParentData()
if (!this.parent) {
return error('u-collapse-item必须要搭配u-collapse组件使用')
}
const {
value,
accordion,
children = []
} = this.parent
if (accordion) {
if (test.array(value)) {
return error('手风琴模式下u-collapse组件的value参数不能为数组')
}
this.expanded = this.name == value
} else {
if (!test.array(value) && value !== null) {
return error('非手风琴模式下u-collapse组件的value参数必须为数组')
}
this.expanded = (value || []).some(item => item == this.name)
}
//
await nextTick()
this.setContentAnimate()
},
updateParentData() {
// mixin
this.getParentData('u-collapse')
},
async setContentAnimate() {
//
//
const rect = await this.queryRect()
const height = this.expanded ? rect.height : 0
this.animating = true
// #ifdef APP-NVUE
const ref = this.$refs['animation'].ref
animation.transition(ref, {
styles: {
height: height + 'px'
},
duration: this.duration,
// true
needLayout: true,
timingFunction: 'ease-in-out',
}, () => {
this.animating = false
})
// #endif
// #ifndef APP-NVUE
const animation = uni.createAnimation({
timingFunction: 'ease-in-out',
});
animation
.height(height)
.step({
duration: this.duration,
})
.step()
// animationData
this.animationData = animation.export()
//
sleep(this.duration).then(() => {
this.animating = false
})
// #endif
},
// collapsehead
clickHandler() {
if (this.disabled && this.animating) return
//
this.parent && this.parent.onChange(this)
},
//
queryRect() {
// #ifndef APP-NVUE
// $uGetRectuViewhttps://ijry.github.io/uview-plus/js/getRect.html
// this.$uGetRectuni.$u.getRect
return new Promise(resolve => {
this.$uGetRect(`#${this.elId}`).then(size => {
resolve(size)
})
})
// #endif
// #ifdef APP-NVUE
// nvue使dom
// promise使then
return new Promise(resolve => {
dom.getComponentRect(this.$refs[this.elId], res => {
resolve(res.size)
})
})
// #endif
}
},
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-collapse-item {
&__content {
overflow: hidden;
height: 0;
&__text {
padding: 12px 15px;
color: $u-content-color;
font-size: 14px;
line-height: 18px;
}
}
}
</style>

View File

@ -0,0 +1,20 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 当前展开面板的name非手风琴模式[<string | number>]手风琴模式string | number
value: {
type: [String, Number, Array, null],
default: () => defProps.collapse.value
},
// 是否手风琴模式
accordion: {
type: Boolean,
default: () => defProps.collapse.accordion
},
// 是否显示外边框
border: {
type: Boolean,
default: () => defProps.collapse.border
}
}
}

View File

@ -0,0 +1,91 @@
<template>
<view class="u-collapse">
<u-line v-if="border"></u-line>
<slot />
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
/**
* collapse 折叠面板
* @description 通过折叠面板收纳内容区域
* @tutorial https://ijry.github.io/uview-plus/components/collapse.html
* @property {String | Number | Array} value 当前展开面板的name非手风琴模式[<string | number>]手风琴模式string | number
* @property {Boolean} accordion 是否手风琴模式 默认 false
* @property {Boolean} border 是否显示外边框 ( 默认 true
* @event {Function} change 当前激活面板展开时触发(如果是手风琴模式参数activeNames类型为String否则为Array)
* @example <u-collapse></u-collapse>
*/
export default {
name: "u-collapse",
mixins: [mpMixin, mixin,props],
watch: {
needInit() {
this.init()
},
//
parentData() {
if (this.children.length) {
this.children.map(child => {
// (u-checkbox)updateParentData()
typeof(child.updateParentData) === 'function' && child.updateParentData()
})
}
}
},
created() {
this.children = []
},
computed: {
needInit() {
// computedaccordionvalue
// watchinit()
return [this.accordion, this.value]
}
},
emits: ["open", "close", "change"],
methods: {
//
init() {
this.children.map(child => {
child.init()
})
},
/**
* collapse-item被点击时触发由collapse统一处理各子组件的状态
* @param {Object} target 被操作的面板的实例
*/
onChange(target) {
let changeArr = []
this.children.map((child, index) => {
//
if (this.accordion) {
child.expanded = child === target ? !target.expanded : false
child.setContentAnimate()
} else {
if(child === target) {
child.expanded = !child.expanded
child.setContentAnimate()
}
}
// change
changeArr.push({
// nameindex
name: child.name || index,
status: child.expanded ? 'open' : 'close'
})
})
this.$emit('change', changeArr)
this.$emit(target.expanded ? 'open' : 'close', target.name)
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
</style>

View File

@ -0,0 +1,56 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 显示的内容,字符串
text: {
type: [Array],
default: () => defProps.columnNotice.text
},
// 是否显示左侧的音量图标
icon: {
type: String,
default: () => defProps.columnNotice.icon
},
// 通告模式link-显示右箭头closable-显示右侧关闭图标
mode: {
type: String,
default: () => defProps.columnNotice.mode
},
// 文字颜色,各图标也会使用文字颜色
color: {
type: String,
default: () => defProps.columnNotice.color
},
// 背景颜色
bgColor: {
type: String,
default: () => defProps.columnNotice.bgColor
},
// 字体大小单位px
fontSize: {
type: [String, Number],
default: () => defProps.columnNotice.fontSize
},
// 水平滚动时的滚动速度即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度
speed: {
type: [String, Number],
default: () => defProps.columnNotice.speed
},
// direction = row时是否使用步进形式滚动
step: {
type: Boolean,
default: () => defProps.columnNotice.step
},
// 滚动一个周期的时间长单位ms
duration: {
type: [String, Number],
default: () => defProps.columnNotice.duration
},
// 是否禁止用手滑动切换
// 目前HX2.6.11只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序
disableTouch: {
type: Boolean,
default: () => defProps.columnNotice.disableTouch
}
}
}

View File

@ -0,0 +1,165 @@
<template>
<view
class="u-notice"
@tap="clickHandler"
>
<slot name="icon">
<view
class="u-notice__left-icon"
v-if="icon"
>
<u-icon
:name="icon"
:color="color"
size="19"
></u-icon>
</view>
</slot>
<swiper
:disable-touch="disableTouch"
:vertical="step ? false : true"
circular
:interval="duration"
:autoplay="true"
class="u-notice__swiper"
@change="noticeChange"
>
<swiper-item
v-for="(item, index) in text"
:key="index"
class="u-notice__swiper__item"
>
<text
class="u-notice__swiper__item__text u-line-1"
:style="[textStyle]"
>{{ item }}</text>
</swiper-item>
</swiper>
<view
class="u-notice__right-icon"
v-if="['link', 'closable'].includes(mode)"
>
<u-icon
v-if="mode === 'link'"
name="arrow-right"
:size="17"
:color="color"
></u-icon>
<u-icon
v-if="mode === 'closable'"
name="close"
:size="16"
:color="color"
@click="close"
></u-icon>
</view>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, error } from '../../libs/function/index';
import test from '../../libs/function/test';
/**
* ColumnNotice 滚动通知中的垂直滚动 内部组件
* @description 该组件用于滚动通告场景是其中的垂直滚动方式
* @tutorial https://ijry.github.io/uview-plus/components/noticeBar.html
* @property {Array} text 显示的内容字符串
* @property {String} icon 是否显示左侧的音量图标 默认 'volume'
* @property {String} mode 通告模式link-显示右箭头closable-显示右侧关闭图标
* @property {String} color 文字颜色各图标也会使用文字颜色 默认 '#f9ae3d'
* @property {String} bgColor 背景颜色 默认 '#fdf6ec'
* @property {String | Number} fontSize 字体大小单位px 默认 14
* @property {String | Number} speed 水平滚动时的滚动速度即每秒滚动多少px(rpx)这有利于控制文字无论多少时都能有一个恒定的速度 默认 80
* @property {Boolean} step direction = row时是否使用步进形式滚动 默认 false
* @property {String | Number} duration 滚动一个周期的时间长单位ms 默认 1500
* @property {Boolean} disableTouch 是否禁止用手滑动切换 目前HX2.6.11只支持App 2.5.5+H5 2.5.5+支付宝小程序字节跳动小程序 默认 true
* @example
*/
export default {
mixins: [mpMixin, mixin, props],
watch: {
text: {
immediate: true,
handler(newValue, oldValue) {
if(!test.array(newValue)) {
error('noticebar组件direction为column时要求text参数为数组形式')
}
}
}
},
computed: {
//
textStyle() {
let style = {}
style.color = this.color
style.fontSize = addUnit(this.fontSize)
return style
},
//
vertical() {
if (this.mode == 'horizontal') return false
else return true
},
},
data() {
return {
index:0
}
},
emits: ["click", "close"],
methods: {
noticeChange(e){
this.index = e.detail.current
},
//
clickHandler() {
this.$emit('click', this.index)
},
//
close() {
this.$emit('close')
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-notice {
@include flex;
align-items: center;
justify-content: space-between;
&__left-icon {
align-items: center;
margin-right: 5px;
}
&__right-icon {
margin-left: 5px;
align-items: center;
}
&__swiper {
height: 16px;
@include flex;
align-items: center;
flex: 1;
&__item {
@include flex;
align-items: center;
overflow: hidden;
&__text {
font-size: 14px;
color: $u-warning;
}
}
}
}
</style>

View File

@ -0,0 +1,70 @@
<template>
<view @click="handleClick">
<slot>复制</slot>
</view>
</template>
<script>
export default {
name: "xy-copy",
props: {
content: {
type: String,
default: ''
},
alertStyle: {
type: String,
default: 'toast'
},
notice: {
type: String,
default: '复制成功'
}
},
emits: ['success'],
methods: {
handleClick() {
let content = this.content;
if (!content) {
uni.showToast({
title: '暂无',
icon: 'none',
duration: 2000,
});
return false;
}
content = typeof content === 'string' ? content : content.toString() //
/**
* 小程序端 app端的复制逻辑
*/
let that = this;
uni.setClipboardData({
data: content,
success: function() {
if (that.alertStyle == 'modal') {
uni.showModal({
title: '提示',
content: that.notice
});
} else {
uni.showToast({
title: that.notice,
icon: 'none'
});
}
that.$emit('success');
},
fail:function(){
uni.showToast({
title: '复制失败',
icon: 'none',
duration:3000,
});
}
});
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,25 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 倒计时时长单位ms
time: {
type: [String, Number],
default: () => defProps.countDown.time
},
// 时间格式DD-日HH-时mm-分ss-秒SSS-毫秒
format: {
type: String,
default: () => defProps.countDown.format
},
// 是否自动开始倒计时
autoStart: {
type: Boolean,
default: () => defProps.countDown.autoStart
},
// 是否展示毫秒倒计时
millisecond: {
type: Boolean,
default: () => defProps.countDown.millisecond
}
}
}

View File

@ -0,0 +1,171 @@
<template>
<view class="u-count-down">
<slot>
<text class="u-count-down__text">{{ formattedTime }}</text>
</slot>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import {
isSameSecond,
parseFormat,
parseTimeData
} from './utils';
/**
* u-count-down 倒计时
* @description 该组件一般使用于某个活动的截止时间上通过数字的变化给用户明确的时间感受提示用户进行某一个行为操作
* @tutorial https://uview-plus.jiangruyi.com/components/countDown.html
* @property {String | Number} time 倒计时时长单位ms 默认 0
* @property {String} format 时间格式DD-HH-mm-ss-SSS-毫秒 默认 'HH:mm:ss'
* @property {Boolean} autoStart 是否自动开始倒计时 默认 true
* @property {Boolean} millisecond 是否展示毫秒倒计时 默认 false
* @event {Function} finish 倒计时结束时触发
* @event {Function} change 倒计时变化时触发
* @event {Function} start 开始倒计时
* @event {Function} pause 暂停倒计时
* @event {Function} reset 重设倒计时 auto-start true重设后会自动开始倒计时
* @example <u-count-down :time="time"></u-count-down>
*/
export default {
name: 'u-count-down',
mixins: [mpMixin, mixin, props],
data() {
return {
timer: null,
// ()
timeData: parseTimeData(0),
// "03:23:21"
formattedTime: '0',
//
runing: false,
endTime: 0, //
remainTime: 0, //
}
},
watch: {
time(n) {
this.reset()
}
},
mounted() {
this.init()
},
emits: ["change", "finish"],
methods: {
init() {
this.reset()
},
//
start() {
if (this.runing) return
//
this.runing = true
// = +
this.endTime = Date.now() + this.remainTime
this.toTick()
},
//
toTick() {
if (this.millisecond) {
this.microTick()
} else {
this.macroTick()
}
},
macroTick() {
this.clearTimeout()
//
//
this.timer = setTimeout(() => {
//
const remain = this.getRemainTime()
//
if (!isSameSecond(remain, this.remainTime) || remain === 0) {
this.setRemainTime(remain)
}
// 0
if (this.remainTime !== 0) {
this.macroTick()
}
}, 30)
},
microTick() {
this.clearTimeout()
this.timer = setTimeout(() => {
this.setRemainTime(this.getRemainTime())
if (this.remainTime !== 0) {
this.microTick()
}
}, 50)
},
//
getRemainTime() {
// 0
return Math.max(this.endTime - Date.now(), 0)
},
//
setRemainTime(remain) {
this.remainTime = remain
//
const timeData = parseTimeData(remain)
this.$emit('change', timeData)
//
this.formattedTime = parseFormat(this.format, timeData)
//
if (remain <= 0) {
this.pause()
this.$emit('finish')
}
},
//
reset() {
this.pause()
this.remainTime = this.time
this.setRemainTime(this.remainTime)
if (this.autoStart) {
this.start()
}
},
//
pause() {
this.runing = false;
this.clearTimeout()
},
//
clearTimeout() {
clearTimeout(this.timer)
this.timer = null
}
},
// #ifdef VUE2
beforeDestroy() {
// #endif
// #ifdef VUE3
beforeUnmount() {
// #endif
this.clearTimeout()
}
}
</script>
<style
lang="scss"
scoped
>
@import "../../libs/css/components.scss";
$u-count-down-text-color:$u-content-color !default;
$u-count-down-text-font-size:15px !default;
$u-count-down-text-line-height:22px !default;
.u-count-down {
&__text {
color: $u-count-down-text-color;
font-size: $u-count-down-text-font-size;
line-height: $u-count-down-text-line-height;
}
}
</style>

View File

@ -0,0 +1,62 @@
// 补0如1 -> 01
function padZero(num, targetLength = 2) {
let str = `${num}`
while (str.length < targetLength) {
str = `0${str}`
}
return str
}
const SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
export function parseTimeData(time) {
const days = Math.floor(time / DAY)
const hours = Math.floor((time % DAY) / HOUR)
const minutes = Math.floor((time % HOUR) / MINUTE)
const seconds = Math.floor((time % MINUTE) / SECOND)
const milliseconds = Math.floor(time % SECOND)
return {
days,
hours,
minutes,
seconds,
milliseconds
}
}
export function parseFormat(format, timeData) {
let {
days,
hours,
minutes,
seconds,
milliseconds
} = timeData
// 如果格式化字符串中不存在DD(天),则将天的时间转为小时中去
if (format.indexOf('DD') === -1) {
hours += days * 24
} else {
// 对天补0
format = format.replace('DD', padZero(days))
}
// 其他同理于DD的格式化处理方式
if (format.indexOf('HH') === -1) {
minutes += hours * 60
} else {
format = format.replace('HH', padZero(hours))
}
if (format.indexOf('mm') === -1) {
seconds += minutes * 60
} else {
format = format.replace('mm', padZero(minutes))
}
if (format.indexOf('ss') === -1) {
milliseconds += seconds * 1000
} else {
format = format.replace('ss', padZero(seconds))
}
return format.replace('SSS', padZero(milliseconds, 3))
}
export function isSameSecond(time1, time2) {
return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
}

View File

@ -0,0 +1,60 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 开始的数值默认从0增长到某一个数
startVal: {
type: [String, Number],
default: () => defProps.countTo.startVal
},
// 要滚动的目标数值,必须
endVal: {
type: [String, Number],
default: () => defProps.countTo.endVal
},
// 滚动到目标数值的动画持续时间单位为毫秒ms
duration: {
type: [String, Number],
default: () => defProps.countTo.duration
},
// 设置数值后是否自动开始滚动
autoplay: {
type: Boolean,
default: () => defProps.countTo.autoplay
},
// 要显示的小数位数
decimals: {
type: [String, Number],
default: () => defProps.countTo.decimals
},
// 是否在即将到达目标数值的时候,使用缓慢滚动的效果
useEasing: {
type: Boolean,
default: () => defProps.countTo.useEasing
},
// 十进制分割
decimal: {
type: [String, Number],
default: () => defProps.countTo.decimal
},
// 字体颜色
color: {
type: String,
default: () => defProps.countTo.color
},
// 字体大小
fontSize: {
type: [String, Number],
default: () => defProps.countTo.fontSize
},
// 是否加粗字体
bold: {
type: Boolean,
default: () => defProps.countTo.bold
},
// 千位分隔符,类似金额的分割(¥23,321.05中的",")
separator: {
type: String,
default: () => defProps.countTo.separator
}
}
}

View File

@ -0,0 +1,189 @@
<template>
<text
class="u-count-num"
:style="{
fontSize: addUnit(fontSize),
fontWeight: bold ? 'bold' : 'normal',
color: color
}"
>{{ displayValue }}</text>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit } from '../../libs/function/index';
/**
* countTo 数字滚动
* @description 该组件一般用于需要滚动数字到某一个值的场景目标要求是一个递增的值
* @tutorial https://ijry.github.io/uview-plus/components/countTo.html
* @property {String | Number} startVal 开始的数值默认从0增长到某一个数默认 0
* @property {String | Number} endVal 要滚动的目标数值必须 默认 0
* @property {String | Number} duration 滚动到目标数值的动画持续时间单位为毫秒ms 默认 2000
* @property {Boolean} autoplay 设置数值后是否自动开始滚动 默认 true
* @property {String | Number} decimals 要显示的小数位数见官网说明默认 0
* @property {Boolean} useEasing 滚动结束时是否缓动结尾见官网说明默认 true
* @property {String} decimal 十进制分割 默认 "."
* @property {String} color 字体颜色 默认 '#606266' )
* @property {String | Number} fontSize 字体大小单位px 默认 22
* @property {Boolean} bold 字体是否加粗默认 false
* @property {String} separator 千位分隔符见官网说明
* @event {Function} end 数值滚动到目标值时触发
* @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to>
*/
export default {
name: 'u-count-to',
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 //
};
},
mixins: [mpMixin, mixin,props],
computed: {
countDown() {
return this.startVal > this.endVal;
}
},
watch: {
startVal() {
this.autoplay && this.start();
},
endVal() {
this.autoplay && this.start();
}
},
mounted() {
this.autoplay && this.start();
},
emits: ["end"],
methods: {
addUnit,
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() {
if (!this.remaining) return
this.startTime = 0;
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) || 0;
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/components.scss";
.u-count-num {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
text-align: center;
}
</style>

View File

@ -0,0 +1,144 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 是否显示input
hasInput: {
type: Boolean,
default: () => false
},
placeholder: {
type: String,
default: () => '请选择'
},
format: {
type: String,
default: () => ''
},
// 是否打开组件
show: {
type: Boolean,
default: () => defProps.datetimePicker.show
},
// 弹出的方向,可选值为 top bottom right left center
popupMode: {
type: String,
default: () => defProps.picker.popupMode
},
// 是否展示顶部的操作栏
showToolbar: {
type: Boolean,
default: () => defProps.datetimePicker.showToolbar
},
// #ifdef VUE2
// 绑定值
value: {
type: [String, Number],
default: () => defProps.datetimePicker.value
},
// #endif
// #ifdef VUE3
// 绑定值
modelValue: {
type: [String, Number],
default: () => defProps.datetimePicker.value
},
// #endif
// 顶部标题
title: {
type: String,
default: () => defProps.datetimePicker.title
},
// 展示格式mode=date为日期选择mode=time为时间选择mode=year-month为年月选择mode=datetime为日期时间选择
mode: {
type: String,
default: () => defProps.datetimePicker.mode
},
// 可选的最大时间
maxDate: {
type: Number,
// 最大默认值为后10年
default: () => defProps.datetimePicker.maxDate
},
// 可选的最小时间
minDate: {
type: Number,
// 最小默认值为前10年
default: () => defProps.datetimePicker.minDate
},
// 可选的最小小时仅mode=time有效
minHour: {
type: Number,
default: () => defProps.datetimePicker.minHour
},
// 可选的最大小时仅mode=time有效
maxHour: {
type: Number,
default: () => defProps.datetimePicker.maxHour
},
// 可选的最小分钟仅mode=time有效
minMinute: {
type: Number,
default: () => defProps.datetimePicker.minMinute
},
// 可选的最大分钟仅mode=time有效
maxMinute: {
type: Number,
default: () => defProps.datetimePicker.maxMinute
},
// 选项过滤函数
filter: {
type: [Function, null],
default: () => defProps.datetimePicker.filter
},
// 选项格式化函数
formatter: {
type: [Function, null],
default: () => defProps.datetimePicker.formatter
},
// 是否显示加载中状态
loading: {
type: Boolean,
default: () => defProps.datetimePicker.loading
},
// 各列中,单个选项的高度
itemHeight: {
type: [String, Number],
default: () => defProps.datetimePicker.itemHeight
},
// 取消按钮的文字
cancelText: {
type: String,
default: () => defProps.datetimePicker.cancelText
},
// 确认按钮的文字
confirmText: {
type: String,
default: () => defProps.datetimePicker.confirmText
},
// 取消按钮的颜色
cancelColor: {
type: String,
default: () => defProps.datetimePicker.cancelColor
},
// 确认按钮的颜色
confirmColor: {
type: String,
default: () => defProps.datetimePicker.confirmColor
},
// 每列中可见选项的数量
visibleItemCount: {
type: [String, Number],
default: () => defProps.datetimePicker.visibleItemCount
},
// 是否允许点击遮罩关闭选择器
closeOnClickOverlay: {
type: Boolean,
default: () => defProps.datetimePicker.closeOnClickOverlay
},
// 各列的默认索引
defaultIndex: {
type: Array,
default: () => defProps.datetimePicker.defaultIndex
}
}
}

View File

@ -0,0 +1,448 @@
<template>
<view v-if="hasInput">
<u-input
:placeholder="placeholder"
border="surround"
v-model="inputValue"
@click="showByClickInput = !showByClickInput"
></u-input>
</view>
<u-picker
ref="picker"
:show="show || (hasInput && showByClickInput)"
:popupMode="popupMode"
:closeOnClickOverlay="closeOnClickOverlay"
:columns="columns"
:title="title"
:itemHeight="itemHeight"
:showToolbar="showToolbar"
:visibleItemCount="visibleItemCount"
:defaultIndex="innerDefaultIndex"
:cancelText="cancelText"
:confirmText="confirmText"
:cancelColor="cancelColor"
:confirmColor="confirmColor"
@close="close"
@cancel="cancel"
@confirm="confirm"
@change="change"
>
</u-picker>
</template>
<script>
function times(n, iteratee) {
let index = -1
const result = Array(n < 0 ? 0 : n)
while (++index < n) {
result[index] = iteratee(index)
}
return result
}
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import dayjs from 'dayjs/esm/index';
import { range, error, padZero } from '../../libs/function/index';
import test from '../../libs/function/test';
/**
* DatetimePicker 时间日期选择器
* @description 此选择器用于时间日期
* @tutorial https://ijry.github.io/uview-plus/components/datetimePicker.html
* @property {Boolean} show 用于控制选择器的弹出与收起 ( 默认 false )
* @property {Boolean} showToolbar 是否显示顶部的操作栏 ( 默认 true )
* @property {String | Number} modelValue 绑定值
* @property {String} title 顶部标题
* @property {String} mode 展示格式 mode=date为日期选择mode=time为时间选择mode=year-month为年月选择mode=datetime为日期时间选择 ( 默认 datetime )
* @property {Number} maxDate 可选的最大时间 默认值为后10年
* @property {Number} minDate 可选的最小时间 默认值为前10年
* @property {Number} minHour 可选的最小小时仅mode=time有效 ( 默认 0 )
* @property {Number} maxHour 可选的最大小时仅mode=time有效 ( 默认 23 )
* @property {Number} minMinute 可选的最小分钟仅mode=time有效 ( 默认 0 )
* @property {Number} maxMinute 可选的最大分钟仅mode=time有效 ( 默认 59 )
* @property {Function} filter 选项过滤函数
* @property {Function} formatter 选项格式化函数
* @property {Boolean} loading 是否显示加载中状态 ( 默认 false )
* @property {String | Number} itemHeight 各列中单个选项的高度 ( 默认 44 )
* @property {String} cancelText 取消按钮的文字 ( 默认 '取消' )
* @property {String} confirmText 确认按钮的文字 ( 默认 '确认' )
* @property {String} cancelColor 取消按钮的颜色 ( 默认 '#909193' )
* @property {String} confirmColor 确认按钮的颜色 ( 默认 '#3c9cff' )
* @property {String | Number} visibleItemCount 每列中可见选项的数量 ( 默认 5 )
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭选择器 ( 默认 false )
* @property {Array} defaultIndex 各列的默认索引
* @event {Function} close 关闭选择器时触发
* @event {Function} confirm 点击确定按钮返回当前选择的值
* @event {Function} change 当选择值变化时触发
* @event {Function} cancel 点击取消按钮
* @example <u-datetime-picker :show="show" :value="value1" mode="datetime" ></u-datetime-picker>
*/
export default {
name: 'datetime-picker',
mixins: [mpMixin, mixin, props],
data() {
return {
// 便hasInputelement
inputValue: '', //
showByClickInput: false, // hasInput
columns: [],
innerDefaultIndex: [],
innerFormatter: (type, value) => value
}
},
watch: {
show(newValue, oldValue) {
if (newValue) {
this.updateColumnValue(this.innerValue)
}
},
// #ifdef VUE3
modelValue(newValue) {
this.init()
this.getInputValue()
},
// #endif
// #ifdef VUE2
value(newValue) {
this.init()
this.getInputValue()
},
// #endif
propsChange() {
this.init()
}
},
computed: {
//
propsChange() {
return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, ]
}
},
mounted() {
this.init()
},
// #ifdef VUE3
emits: ['close', 'cancel', 'confirm', 'change', 'update:modelValue'],
// #endif
methods: {
getInputValue(newValue) {
if (this.mode == 'time') {
this.inputValue = newValue
} else {
if (this.format) {
this.inputValue = dayjs(newValue).format(this.format)
} else {
let format = ''
switch (this.mode) {
case 'date':
format = 'YYYY-MM-DD'
break;
case 'year-month':
format = 'YYYY-MM'
break;
case 'datetime':
format = 'YYYY-MM-DD HH:mm'
break;
case 'time':
format = 'HH:mm'
break;
default:
break;
}
this.inputValue = dayjs(newValue).format(format)
}
}
},
init() {
// #ifdef VUE3
this.innerValue = this.correctValue(this.modelValue)
// #endif
// #ifdef VUE2
this.innerValue = this.correctValue(this.value)
// #endif
this.updateColumnValue(this.innerValue)
},
// propsref
setFormatter(e) {
this.innerFormatter = e
},
//
close() {
if (this.closeOnClickOverlay) {
this.$emit('close')
}
},
//
cancel() {
if (this.hasInput) {
this.showByClickInput = false
}
this.$emit('cancel')
},
//
confirm() {
this.$emit('confirm', {
value: this.innerValue,
mode: this.mode
})
// #ifdef VUE3
this.$emit('update:modelValue', this.innerValue)
// #endif
// #ifdef VUE2
this.$emit('input', this.innerValue)
// #endif
if (this.hasInput) {
this.getInputValue(this.innerValue)
this.showByClickInput = false
}
},
//,,
intercept(e,type){
let judge = e.match(/\d+/g)
//
if(judge.length>1){
error("请勿在过滤或格式化函数时添加数字")
return 0
}else if(type&&judge[0].length==4){//
return judge[0]
}else if(judge[0].length>2){
error("请勿在过滤或格式化函数时添加数字")
return 0
}else{
return judge[0]
}
},
//
change(e) {
const { indexs, values } = e
let selectValue = ''
if(this.mode === 'time') {
// value
selectValue = `${this.intercept(values[0][indexs[0]])}:${this.intercept(values[1][indexs[1]])}`
} else {
// '03'3'2019'2019
const year = parseInt(this.intercept(values[0][indexs[0]],'year'))
const month = parseInt(this.intercept(values[1][indexs[1]]))
let date = parseInt(values[2] ? this.intercept(values[2][indexs[2]]) : 1)
let hour = 0, minute = 0
//
const maxDate = dayjs(`${year}-${month}`).daysInMonth()
// year-monthdate11
if (this.mode === 'year-month') {
date = 1
}
// maxDate
date = Math.min(maxDate, date)
if (this.mode === 'datetime') {
hour = parseInt(this.intercept(values[3][indexs[3]]))
minute = parseInt(this.intercept(values[4][indexs[4]]))
}
//
selectValue = Number(new Date(year, month - 1, date, hour, minute))
}
//
selectValue = this.correctValue(selectValue)
this.innerValue = selectValue
this.updateColumnValue(selectValue)
// changevalue
this.$emit('change', {
value: selectValue,
// #ifndef MP-WEIXIN
// this
picker: this.$refs.picker,
// #endif
mode: this.mode
})
},
// 0
updateColumnValue(value) {
this.innerValue = value
this.updateColumns()
// ,u-picker
setTimeout(() => {
this.updateIndexs(value)
}, 0);
},
//
updateIndexs(value) {
let values = []
const formatter = this.formatter || this.innerFormatter
const padZero = padZero
if (this.mode === 'time') {
// time:
const timeArr = value.split(':')
// 使formatter
values = [formatter('hour', timeArr[0]), formatter('minute', timeArr[1])]
} else {
const date = new Date(value)
values = [
formatter('year', `${dayjs(value).year()}`),
// 0
formatter('month', padZero(dayjs(value).month() + 1))
]
if (this.mode === 'date') {
// date
values.push(formatter('day', padZero(dayjs(value).date())))
}
if (this.mode === 'datetime') {
// push
values.push(formatter('day', padZero(dayjs(value).date())), formatter('hour', padZero(dayjs(value).hour())), formatter('minute', padZero(dayjs(value).minute())))
}
}
//
const indexs = this.columns.map((column, index) => {
// -1
return Math.max(0, column.findIndex(item => item === values[index]))
})
this.innerDefaultIndex = indexs
},
//
updateColumns() {
const formatter = this.formatter || this.innerFormatter
// map0
const results = this.getOriginColumns().map((column) => column.values.map((value) => formatter(column.type, value)))
this.columns = results
},
getOriginColumns() {
//
const results = this.getRanges().map(({ type, range }) => {
let values = times(range[1] - range[0] + 1, (index) => {
let value = range[0] + index
value = type === 'year' ? `${value}` : padZero(value)
return value
})
//
if (this.filter) {
values = this.filter(type, values)
if (!values || (values && values.length == 0)) {
uni.showToast({
title: '日期filter结果不能为空',
icon: 'error',
mask: true
})
}
}
return { type, values }
})
return results
},
//
generateArray(start, end) {
return Array.from(new Array(end + 1).keys()).slice(start)
},
//
correctValue(value) {
const isDateMode = this.mode !== 'time'
if (isDateMode && !test.date(value)) {
// 使
value = this.minDate
} else if (!isDateMode && !value) {
//
value = `${padZero(this.minHour)}:${padZero(this.minMinute)}`
}
//
if (!isDateMode) {
if (String(value).indexOf(':') === -1) return error('时间错误请传递如12:24的格式')
let [hour, minute] = value.split(':')
//
hour = padZero(range(this.minHour, this.maxHour, Number(hour)))
minute = padZero(range(this.minMinute, this.maxMinute, Number(minute)))
return `${ hour }:${ minute }`
} else {
//
value = dayjs(value).isBefore(dayjs(this.minDate)) ? this.minDate : value
value = dayjs(value).isAfter(dayjs(this.maxDate)) ? this.maxDate : value
return value
}
},
//
getRanges() {
if (this.mode === 'time') {
return [
{
type: 'hour',
range: [this.minHour, this.maxHour],
},
{
type: 'minute',
range: [this.minMinute, this.maxMinute],
},
];
}
const { maxYear, maxDate, maxMonth, maxHour, maxMinute, } = this.getBoundary('max', this.innerValue);
const { minYear, minDate, minMonth, minHour, minMinute, } = this.getBoundary('min', this.innerValue);
const result = [
{
type: 'year',
range: [minYear, maxYear],
},
{
type: 'month',
range: [minMonth, maxMonth],
},
{
type: 'day',
range: [minDate, maxDate],
},
{
type: 'hour',
range: [minHour, maxHour],
},
{
type: 'minute',
range: [minMinute, maxMinute],
},
];
if (this.mode === 'date')
result.splice(3, 2);
if (this.mode === 'year-month')
result.splice(2, 3);
return result;
},
// minDatemaxDateminHourmaxHour
getBoundary(type, innerValue) {
const value = new Date(innerValue)
const boundary = new Date(this[`${type}Date`])
const year = dayjs(boundary).year()
let month = 1
let date = 1
let hour = 0
let minute = 0
if (type === 'max') {
month = 12
//
date = dayjs(value).daysInMonth()
hour = 23
minute = 59
}
// ()
if (dayjs(value).year() === year) {
month = dayjs(boundary).month() + 1
if (dayjs(value).month() + 1 === month) {
date = dayjs(boundary).date()
if (dayjs(value).date() === date) {
hour = dayjs(boundary).hour()
if (dayjs(value).hour() === hour) {
minute = dayjs(boundary).minute()
}
}
}
}
return {
[`${type}Year`]: year,
[`${type}Month`]: month,
[`${type}Date`]: date,
[`${type}Hour`]: hour,
[`${type}Minute`]: minute
}
},
},
}
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
</style>

View File

@ -0,0 +1,45 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 是否虚线
dashed: {
type: Boolean,
default: () => defProps.divider.dashed
},
// 是否细线
hairline: {
type: Boolean,
default: () => defProps.divider.hairline
},
// 是否以点替代文字优先于text字段起作用
dot: {
type: Boolean,
default: () => defProps.divider.dot
},
// 内容文本的位置left-左边center-中间right-右边
textPosition: {
type: String,
default: () => defProps.divider.textPosition
},
// 文本内容
text: {
type: [String, Number],
default: () => defProps.divider.text
},
// 文本大小
textSize: {
type: [String, Number],
default: () => defProps.divider.textSize
},
// 文本颜色
textColor: {
type: String,
default: () => defProps.divider.textColor
},
// 线条颜色
lineColor: {
type: String,
default: () => defProps.divider.lineColor
}
}
}

View File

@ -0,0 +1,121 @@
<template>
<view
class="u-divider"
:style="[addStyle(customStyle)]"
@tap="click"
>
<u-line
:color="lineColor"
:customStyle="leftLineStyle"
:hairline="hairline"
:dashed="dashed"
></u-line>
<text
v-if="dot"
class="u-divider__dot"
></text>
<text
v-else-if="text"
class="u-divider__text"
:style="[textStyle]"
>{{text}}</text>
<u-line
:color="lineColor"
:customStyle="rightLineStyle"
:hairline="hairline"
:dashed="dashed"
></u-line>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addStyle, addUnit } from '../../libs/function/index';
/**
* divider 分割线
* @description 区隔内容的分割线一般用于页面底部"没有更多"的提示
* @tutorial https://ijry.github.io/uview-plus/components/divider.html
* @property {Boolean} dashed 是否虚线 默认 false
* @property {Boolean} hairline 是否细线 默认 true
* @property {Boolean} dot 是否以点替代文字优先于text字段起作用 默认 false
* @property {String} textPosition 内容文本的位置left-左边center-中间right-右边 默认 'center'
* @property {String | Number} text 文本内容
* @property {String | Number} textSize 文本大小 默认 14
* @property {String} textColor 文本颜色 默认 '#909399'
* @property {String} lineColor 线条颜色 默认 '#dcdfe6'
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function} click divider组件被点击时触发
* @example <u-divider :color="color">锦瑟无端五十弦</u-divider>
*/
export default {
name:'u-divider',
mixins: [mpMixin, mixin, props],
computed: {
textStyle() {
const style = {}
style.fontSize = addUnit(this.textSize)
style.color = this.textColor
return style
},
// 线
leftLineStyle() {
const style = {}
//
if (this.textPosition === 'left') {
style.width = '80rpx'
} else {
style.flex = 1
}
return style
},
// 线
rightLineStyle() {
const style = {}
//
if (this.textPosition === 'right') {
style.width = '80rpx'
} else {
style.flex = 1
}
return style
}
},
emits: ["click"],
methods: {
addStyle,
// divider
click() {
this.$emit('click');
}
}
}
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
$u-divider-margin:15px 0 !default;
$u-divider-text-margin:0 15px !default;
$u-divider-dot-font-size:12px !default;
$u-divider-dot-margin:0 12px !default;
$u-divider-dot-color: #c0c4cc !default;
.u-divider {
@include flex;
flex-direction: row;
align-items: center;
margin: $u-divider-margin;
&__text {
margin: $u-divider-text-margin;
}
&__dot {
font-size: $u-divider-dot-font-size;
margin: $u-divider-dot-margin;
color: $u-divider-dot-color;
}
}
</style>

View File

@ -0,0 +1,46 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// #ifdef VUE3
// 当前选中项的value值
modelValue: {
type: [Number, String, Array],
default: ''
},
// #endif
// #ifdef VUE2
// 当前选中项的value值
value: {
type: [Number, String, Array],
default: ''
},
// #endif
// 菜单项标题
title: {
type: [String, Number],
default: ''
},
// 选项数据如果传入了默认slot此参数无效
options: {
type: Array,
default() {
return []
}
},
// 是否禁用此菜单项
disabled: {
type: Boolean,
default: false
},
// 下拉弹窗的高度
height: {
type: [Number, String],
default: 'auto'
},
// 点击遮罩是否可以收起弹窗
closeOnClickOverlay: {
type: Boolean,
default: true
}
}
}

View File

@ -0,0 +1,121 @@
<template>
<view class="u-dropdown-item" v-if="active" @touchmove.stop.prevent="() => {}" @tap.stop.prevent="() => {}">
<block v-if="!$slots.default && !$slots.$default">
<scroll-view class="u-dropdown-item__scroll" scroll-y="true" :style="{
height: addUnit(height)
}">
<view class="u-dropdown-item__options">
<up-cell-group>
<up-cell @click="cellClick(item.value)" :arrow="false" :title="item.label" v-for="(item, index) in options"
:key="index" :title-style="{
color: modelValue == item.value ? activeColor : inactiveColor
}">
<up-icon v-if="modelValue == item.value" name="checkbox-mark" :color="activeColor" size="32"></up-icon>
</up-cell>
</up-cell-group>
</view>
</scroll-view>
</block>
<slot v-else />
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, $parent } from '../../libs/function/index';
/**
* dropdown-item 下拉菜单
* @description 该组件一般用于向下展开菜单同时可切换多个选项卡的场景
* @tutorial https://ijry.github.io/uview-plus/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',
mixins: [mpMixin, mixin, props],
options: {
styleIsolation: 'shared',
},
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;
},
emits: ['update:modelValue', 'change'],
methods: {
addUnit,
init() {
// u-dropdown
let parent = $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
// #ifdef VUE2
this.$emit('input', value);
// #endif
// #ifdef VUE3
this.$emit('update:modelValue', value);
// #endif
// (u-dropdown)
this.parent.close();
// value
this.$emit('change', value);
}
},
mounted() {
this.init();
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/components.scss";
.u-dropdown-item__scroll {
background: #ffffff;
}
</style>

View File

@ -0,0 +1,60 @@
import defProps from '../../libs/config/props.js';
export default {
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
},
// 标题菜单的高度
height: {
type: [Number, String],
default: 40
},
// 是否显示下边框
borderBottom: {
type: Boolean,
default: false
},
// 标题的字体大小
titleSize: {
type: [Number, String],
default: 14
},
// 下拉出来的内容部分的圆角值
borderRadius: {
type: [Number, String],
default: 0
},
// 菜单右侧的icon图标
menuIcon: {
type: String,
default: 'arrow-down'
},
// 菜单右侧图标的大小
menuIconSize: {
type: [Number, String],
default: 14
}
}
}

View File

@ -0,0 +1,254 @@
<template>
<view class="u-dropdown">
<view class="u-dropdown__menu" :style="{
height: 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 u-flex-row">
<text class="u-dropdown__menu__item__text" :style="{
color: item.disabled ? '#c0c4cc' : (index === current || highlightIndex == index) ? activeColor : inactiveColor,
fontSize: 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="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: 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>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, sys} from '../../libs/function/index';
/**
* dropdown 下拉菜单
* @description 该组件一般用于向下展开菜单同时可切换多个选项卡的场景
* @tutorial https://ijry.github.io/uview-plus/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',
mixins: [mpMixin, mixin, props],
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 ${addUnit(this.borderRadius)} ${addUnit(this.borderRadius)}`;
return style;
}
},
created() {
// (u-dropdown-item)thisdata
this.children = [];
},
mounted() {
this.getContentHeight();
},
emits: ['open', 'close'],
methods: {
addUnit,
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) {
// popup使
if (this.contentHeight < 1) this.getContentHeight()
//
// 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
//
// sys()uview-plus
let windowHeight = 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/components.scss";
.u-dropdown {
flex: 1;
width: 100%;
position: relative;
&__menu {
@include flex;
position: relative;
z-index: 11;
height: 80rpx;
&__item {
flex: 1;
@include flex;
justify-content: center;
align-items: center;
.u-flex-row {
flex-direction: row;
}
&__text {
font-size: 28rpx;
color: $u-content-color;
}
&__arrow {
margin-left: 6rpx;
transition: transform .3s;
align-items: center;
@include flex;
&--rotate {
transform: rotate(180deg);
}
}
}
}
&__content {
position: absolute;
z-index: 8;
width: 100%;
left: 0px;
bottom: 0;
overflow: hidden;
&__mask {
position: absolute;
z-index: 9;
background: rgba(0, 0, 0, .3);
width: 100%;
left: 0;
top: 0;
bottom: 0;
}
&__popup {
position: relative;
z-index: 10;
transition: all 0.3s;
transform: translate3D(0, -100%, 0);
overflow: hidden;
}
}
}
</style>

View File

@ -0,0 +1,60 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 内置图标名称,或图片路径,建议绝对路径
icon: {
type: String,
default: () => defProps.empty.icon
},
// 提示文字
text: {
type: String,
default: () => defProps.empty.text
},
// 文字颜色
textColor: {
type: String,
default: () => defProps.empty.textColor
},
// 文字大小
textSize: {
type: [String, Number],
default: () => defProps.empty.textSize
},
// 图标的颜色
iconColor: {
type: String,
default: () => defProps.empty.iconColor
},
// 图标的大小
iconSize: {
type: [String, Number],
default: () => defProps.empty.iconSize
},
// 选择预置的图标类型
mode: {
type: String,
default: () => defProps.empty.mode
},
// 图标宽度单位px
width: {
type: [String, Number],
default: () => defProps.empty.width
},
// 图标高度单位px
height: {
type: [String, Number],
default: () => defProps.empty.height
},
// 是否显示组件
show: {
type: Boolean,
default: () => defProps.empty.show
},
// 组件距离上一个元素之间的距离默认px单位
marginTop: {
type: [String, Number],
default: () => defProps.empty.marginTop
}
}
}

View File

@ -0,0 +1,133 @@
<template>
<view
class="u-empty"
:style="[emptyStyle]"
v-if="show"
>
<u-icon
v-if="!isSrc"
:name="mode === 'message' ? 'chat' : `empty-${mode}`"
:size="iconSize"
:color="iconColor"
margin-top="14"
></u-icon>
<image
v-else
:style="{
width: addUnit(width),
height: addUnit(height),
}"
:src="icon"
mode="widthFix"
></image>
<text
class="u-empty__text"
:style="[textStyle]"
>{{text ? text : icons[mode]}}</text>
<view class="u-empty__wrap" v-if="$slots.default || $slots.$default">
<slot />
</view>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, addStyle, deepMerge } from '../../libs/function/index';
/**
* empty 内容为空
* @description 该组件用于需要加载内容但是加载的第一页数据就为空提示一个"没有内容"的场景 我们精心挑选了十几个场景的图标方便您使用
* @tutorial https://ijry.github.io/uview-plus/components/empty.html
* @property {String} icon 内置图标名称或图片路径建议绝对路径
* @property {String} text 提示文字
* @property {String} textColor 文字颜色 (默认 '#c0c4cc' )
* @property {String | Number} textSize 文字大小 默认 14
* @property {String} iconColor 图标的颜色 默认 '#c0c4cc'
* @property {String | Number} iconSize 图标的大小 默认 90
* @property {String} mode 选择预置的图标类型 默认 'data'
* @property {String | Number} width 图标宽度单位px 默认 160
* @property {String | Number} height 图标高度单位px 默认 160
* @property {Boolean} show 是否显示组件 默认 true
* @property {String | Number} marginTop 组件距离上一个元素之间的距离默认px单位 默认 0
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function} click 点击组件时触发
* @event {Function} close 点击关闭按钮时触发
* @example <u-empty text="所谓伊人,在水一方" mode="list"></u-empty>
*/
export default {
name: "u-empty",
mixins: [mpMixin, mixin, props],
data() {
return {
icons: {
car: '购物车为空',
page: '页面不存在',
search: '没有搜索结果',
address: '没有收货地址',
wifi: '没有WiFi',
order: '订单为空',
coupon: '没有优惠券',
favor: '暂无收藏',
permission: '无权限',
history: '无历史记录',
news: '无新闻列表',
message: '消息列表为空',
list: '列表为空',
data: '数据为空',
comment: '暂无评论',
}
}
},
computed: {
//
emptyStyle() {
const style = {}
style.marginTop = addUnit(this.marginTop)
// customStylemixinprops
return deepMerge(addStyle(this.customStyle), style)
},
//
textStyle() {
const style = {}
style.color = this.textColor
style.fontSize = addUnit(this.textSize)
return style
},
// icon
isSrc() {
return this.icon.indexOf('/') >= 0
}
},
methods: {
addUnit
}
}
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
$u-empty-text-margin-top:20rpx !default;
$u-empty-slot-margin-top:20rpx !default;
.u-empty {
@include flex;
flex-direction: column;
justify-content: center;
align-items: center;
&__text {
@include flex;
justify-content: center;
align-items: center;
margin-top: $u-empty-text-margin-top;
}
}
.u-slot-wrap {
@include flex;
justify-content: center;
align-items: center;
margin-top:$u-empty-slot-margin-top;
}
</style>

View File

@ -0,0 +1,54 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// input的label提示语
label: {
type: String,
default: () => defProps.formItem.label
},
// 绑定的值
prop: {
type: String,
default: () => defProps.formItem.prop
},
// 绑定的规则
rule: {
type: String,
default: () => defProps.formItem.rule
},
// 是否显示表单域的下划线边框
borderBottom: {
type: [String, Boolean],
default: () => defProps.formItem.borderBottom
},
// label的位置left-左边top-上边
labelPosition: {
type: String,
default: () => defProps.formItem.labelPosition
},
// label的宽度单位px
labelWidth: {
type: [String, Number],
default: () => defProps.formItem.labelWidth
},
// 右侧图标
rightIcon: {
type: String,
default: () => defProps.formItem.rightIcon
},
// 左侧图标
leftIcon: {
type: String,
default: () => defProps.formItem.leftIcon
},
// 是否显示左边的必填星号只作显示用具体校验必填的逻辑请在rules中配置
required: {
type: Boolean,
default: () => defProps.formItem.required
},
leftIconStyle: {
type: [String, Object],
default: () => defProps.formItem.leftIconStyle,
}
}
}

View File

@ -0,0 +1,245 @@
<template>
<view class="u-form-item">
<view
class="u-form-item__body"
@tap="clickHandler"
:style="[addStyle(customStyle), {
flexDirection: (labelPosition || parentData.labelPosition) === 'left' ? 'row' : 'column'
}]"
>
<!-- 微信小程序中将一个参数设置空字符串结果会变成字符串"true" -->
<slot name="label">
<!-- {{required}} -->
<view
class="u-form-item__body__left"
v-if="required || leftIcon || label"
:style="{
width: addUnit(labelWidth || parentData.labelWidth),
marginBottom: parentData.labelPosition === 'left' ? 0 : '5px',
}"
>
<!-- 为了块对齐 -->
<view class="u-form-item__body__left__content">
<!-- nvue不支持伪元素before -->
<text
v-if="required"
class="u-form-item__body__left__content__required"
>*</text>
<view
class="u-form-item__body__left__content__icon"
v-if="leftIcon"
>
<u-icon
:name="leftIcon"
:custom-style="leftIconStyle"
></u-icon>
</view>
<text
class="u-form-item__body__left__content__label"
:style="[parentData.labelStyle, {
justifyContent: parentData.labelAlign === 'left' ? 'flex-start' : parentData.labelAlign === 'center' ? 'center' : 'flex-end'
}]"
>{{ label }}</text>
</view>
</view>
</slot>
<view class="u-form-item__body__right">
<view class="u-form-item__body__right__content">
<view class="u-form-item__body__right__content__slot">
<slot />
</view>
<view
class="item__body__right__content__icon"
v-if="$slots.right"
>
<slot name="right" />
</view>
</view>
</view>
</view>
<slot name="error">
<text
v-if="!!message && parentData.errorType === 'message'"
class="u-form-item__body__right__message"
:style="{
marginLeft: addUnit(parentData.labelPosition === 'top' ? 0 : (labelWidth || parentData.labelWidth))
}"
>{{ message }}</text>
</slot>
<u-line
v-if="borderBottom"
:color="message && parentData.errorType === 'border-bottom' ? color.error : propsLine.color"
:customStyle="`margin-top: ${message && parentData.errorType === 'message' ? '5px' : 0}`"
></u-line>
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import defProps from '../../libs/config/props.js';
import color from '../../libs/config/color';
import { addStyle, addUnit, getProperty, setProperty, error } from '../../libs/function/index';
/**
* Form 表单
* @description 此组件一般用于表单场景可以配置Input输入框Select弹出框进行表单验证等
* @tutorial https://ijry.github.io/uview-plus/components/form.html
* @property {String} label input的label提示语
* @property {String} prop 绑定的值
* @property {String} rule 绑定的规则
* @property {String | Boolean} borderBottom 是否显示表单域的下划线边框
* @property {String | Number} labelWidth label的宽度单位px
* @property {String} rightIcon 右侧图标
* @property {String} leftIcon 左侧图标
* @property {String | Object} leftIconStyle 左侧图标的样式
* @property {Boolean} required 是否显示左边的必填星号只作显示用具体校验必填的逻辑请在rules中配置 (默认 false )
*
* @example <u-form-item label="姓名" prop="userInfo.name" borderBottom ref="item1"></u-form-item>
*/
export default {
name: 'u-form-item',
mixins: [mpMixin, mixin, props],
data() {
return {
//
message: '',
parentData: {
//
labelPosition: 'left',
//
labelAlign: 'left',
//
labelStyle: {},
//
labelWidth: 45,
//
errorType: 'message'
}
}
},
// u-form
computed: {
propsLine() {
return defProps.line
}
},
mounted() {
this.init()
},
emits: ["click"],
methods: {
addStyle,
addUnit,
color,
init() {
//
this.updateParentData()
if (!this.parent) {
error('u-form-item需要结合u-form组件使用')
}
},
//
updateParentData() {
// mixin
this.getParentData('u-form');
},
// u-form-item
clearValidate() {
this.message = null
},
//
resetField() {
//
const value = getProperty(this.parent.originalModel, this.prop)
// u-formmodelprop
setProperty(this.parent.model, this.prop, value)
//
this.message = null
},
//
clickHandler() {
this.$emit('click')
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-form-item {
@include flex(column);
font-size: 14px;
color: $u-main-color;
&__body {
@include flex;
padding: 10px 0;
&__left {
@include flex;
align-items: center;
&__content {
position: relative;
@include flex;
align-items: center;
padding-right: 10rpx;
flex: 1;
&__icon {
margin-right: 8rpx;
}
&__required {
position: absolute;
left: -9px;
color: $u-error;
line-height: 20px;
font-size: 20px;
top: 3px;
}
&__label {
@include flex;
align-items: center;
flex: 1;
color: $u-main-color;
font-size: 15px;
}
}
}
&__right {
flex: 1;
&__content {
@include flex;
align-items: center;
flex: 1;
&__slot {
flex: 1;
/* #ifndef MP */
@include flex;
align-items: center;
/* #endif */
}
&__icon {
margin-left: 10rpx;
color: $u-light-color;
font-size: 30rpx;
}
}
&__message {
font-size: 12px;
line-height: 12px;
color: $u-error;
}
}
}
}
</style>

View File

@ -0,0 +1,46 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 当前form的需要验证字段的集合
model: {
type: Object,
default: () => defProps.form.model
},
// 验证规则
rules: {
type: [Object, Function, Array],
default: () => defProps.form.rules
},
// 有错误时的提示方式message-提示信息toast-进行toast提示
// border-bottom-下边框呈现红色none-无提示
errorType: {
type: String,
default: () => defProps.form.errorType
},
// 是否显示表单域的下划线边框
borderBottom: {
type: Boolean,
default: () => defProps.form.borderBottom
},
// label的位置left-左边top-上边
labelPosition: {
type: String,
default: () => defProps.form.labelPosition
},
// label的宽度单位px
labelWidth: {
type: [String, Number],
default: () => defProps.form.labelWidth
},
// lable字体的对齐方式
labelAlign: {
type: String,
default: () => defProps.form.labelAlign
},
// lable的样式对象形式
labelStyle: {
type: Object,
default: () => defProps.form.labelStyle
}
}
}

View File

@ -0,0 +1,218 @@
<template>
<view class="u-form">
<slot />
</view>
</template>
<script>
import props from "./props.js";
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import Schema from "../../libs/util/async-validator";
import { toast, getProperty, setProperty, deepClone, error } from '../../libs/function/index';
import test from '../../libs/function/test';
//
Schema.warning = function() {};
/**
* Form 表单
* @description 此组件一般用于表单场景可以配置Input输入框Select弹出框进行表单验证等
* @tutorial https://ijry.github.io/uview-plus/components/form.html
* @property {Object} model 当前form的需要验证字段的集合
* @property {Object | Function | Array} rules 验证规则
* @property {String} errorType 错误的提示方式见上方说明 ( 默认 message )
* @property {Boolean} borderBottom 是否显示表单域的下划线边框 ( 默认 true
* @property {String} labelPosition 表单域提示文字的位置left-左侧top-上方 ( 默认 'left'
* @property {String | Number} labelWidth 提示文字的宽度单位px ( 默认 45
* @property {String} labelAlign lable字体的对齐方式 ( 默认 left'
* @property {Object} labelStyle lable的样式对象形式
* @example <up-formlabelPosition="left" :model="model1" :rules="rules" ref="form1"></up-form>
*/
export default {
name: "u-form",
mixins: [mpMixin, mixin, props],
provide() {
return {
uForm: this,
};
},
data() {
return {
formRules: {},
//
validator: {},
// modelresetFields使
originalModel: null,
};
},
watch: {
//
rules: {
immediate: true,
handler(n) {
this.setRules(n);
},
},
// u-form-item
propsChange(n) {
if (this.children?.length) {
this.children.map((child) => {
// (u-form-item)updateParentData()
typeof child.updateParentData == "function" &&
child.updateParentData();
});
}
},
// model
model: {
immediate: true,
handler(n) {
if (!this.originalModel) {
this.originalModel = deepClone(n);
}
},
},
},
computed: {
propsChange() {
return [
this.errorType,
this.borderBottom,
this.labelPosition,
this.labelWidth,
this.labelAlign,
this.labelStyle,
];
},
},
created() {
// formu-form-item
// data
this.children = [];
},
methods: {
//
setRules(rules) {
//
if (Object.keys(rules).length === 0) return;
if (process.env.NODE_ENV === 'development' && Object.keys(this.model).length === 0) {
error('设置rulesmodel必须设置如果已经设置请刷新页面。');
return;
};
this.formRules = rules;
// Validator
this.validator = new Schema(rules);
},
// u-form-itemu-form-itemresetField()
resetFields() {
this.resetModel();
},
// model
resetModel(obj) {
// u-form-itempropmodel
this.children.map((child) => {
const prop = child?.prop;
const value = getProperty(this.originalModel, prop);
setProperty(this.model, prop, value);
});
},
//
clearValidate(props) {
props = [].concat(props);
this.children.map((child) => {
// u-form-itempropprops
if (props[0] === undefined || props.includes(child.prop)) {
child.message = null;
}
});
},
//
async validateField(value, callback, event = null) {
// $nextTickmodel
this.$nextTick(() => {
// form-item
const errorsRes = [];
//
value = [].concat(value);
// childrenform-item
this.children.map((child) => {
// form-item
const childErrors = [];
if (value.includes(child.prop)) {
// 'a.b.c'
const propertyVal = getProperty(
this.model,
child.prop
);
//
const propertyChain = child.prop.split(".");
const propertyName =
propertyChain[propertyChain.length - 1];
const rule = this.formRules[child.rule || child.prop];
//
if (!rule) return;
// rule
const rules = [].concat(rule);
// rules
for (let i = 0; i < rules.length; i++) {
const ruleItem = rules[i];
// u-form-item
const trigger = [].concat(ruleItem?.trigger);
// form-item
if (event && !trigger.includes(event)) continue;
//
const validator = new Schema({
[propertyName]: ruleItem,
});
validator.validate({
[propertyName]: propertyVal,
},
(errors, fields) => {
if (test.array(errors)) {
errorsRes.push(...errors);
childErrors.push(...errors);
}
child.message =
childErrors[0]?.message ? childErrors[0].message : null;
}
);
}
}
});
//
typeof callback === "function" && callback(errorsRes);
});
},
//
validate(callback) {
//
if (process.env.NODE_ENV === 'development' && Object.keys(this.formRules).length === 0) {
error('未设置rules请看文档说明如果已经设置请刷新页面。');
return;
}
return new Promise((resolve, reject) => {
// $nextTickmodelvalidate
this.$nextTick(() => {
// form-itempropvalidateField
const formItemProps = this.children.map(
(item) => item.prop
);
this.validateField(formItemProps, (errors) => {
if(errors.length) {
// toast
this.errorType === 'toast' && toast(errors[0].message)
reject(errors)
} else {
resolve(true)
}
});
});
});
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,25 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 背景颜色默认transparent
bgColor: {
type: String,
default: () => defProps.gap.bgColor
},
// 分割槽高度单位px默认30
height: {
type: [String, Number],
default: () => defProps.gap.height
},
// 与上一个组件的距离
marginTop: {
type: [String, Number],
default: () => defProps.gap.marginTop
},
// 与下一个组件的距离
marginBottom: {
type: [String, Number],
default: () => defProps.gap.marginBottom
}
}
}

View File

@ -0,0 +1,41 @@
<template>
<view class="u-gap" :style="[gapStyle]"></view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addStyle, addUnit, deepMerge } from '../../libs/function/index.js';
/**
* gap 间隔槽
* @description 该组件一般用于内容块之间的用一个灰色块隔开的场景方便用户风格统一减少工作量
* @tutorial https://ijry.github.io/uview-plus/components/gap.html
* @property {String} bgColor 背景颜色 默认 'transparent'
* @property {String | Number} height 分割槽高度单位px 默认 20
* @property {String | Number} marginTop 与前一个组件的距离单位px 默认 0
* @property {String | Number} marginBottom 与后一个组件的距离单位px 默认 0
* @property {Object} customStyle 定义需要用到的外部样式
*
* @example <u-gap height="80" bg-color="#bbb"></u-gap>
*/
export default {
name: "u-gap",
mixins: [mpMixin, mixin, props],
computed: {
gapStyle() {
const style = {
backgroundColor: this.bgColor,
height: addUnit(this.height),
marginTop: addUnit(this.marginTop),
marginBottom: addUnit(this.marginBottom),
}
return deepMerge(style, addStyle(this.customStyle))
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
</style>

View File

@ -0,0 +1,15 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 宫格的name
name: {
type: [String, Number, null],
default: () => defProps.gridItem.name
},
// 背景颜色
bgColor: {
type: String,
default: () => defProps.gridItem.bgColor
}
}
}

View File

@ -0,0 +1,232 @@
<template>
<!-- #ifndef APP-NVUE -->
<view
v-if="parentData.col > 0"
class="u-grid-item"
hover-class="u-grid-item--hover-class"
:hover-stay-time="200"
@tap="clickHandler"
:class="classes"
:style="[itemStyle]"
>
<slot />
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view
class="u-grid-item"
:hover-stay-time="200"
@tap="clickHandler"
:class="classes"
:style="[itemStyle]"
>
<slot />
</view>
<!-- #endif -->
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addStyle, deepMerge } from '../../libs/function/index';
/**
* gridItem 提示
* @description 宫格组件一般用于同时展示多个同类项目的场景可以给宫格的项目设置徽标组件(badge)或者图标等也可以扩展为左右滑动的轮播形式搭配u-grid使用
* @tutorial https://ijry.github.io/uview-plus/components/grid.html
* @property {String | Number} name 宫格的name ( 默认 null )
* @property {String} bgColor 宫格的背景颜色 默认 'transparent'
* @property {Object} customStyle 自定义样式对象形式
* @event {Function} click 点击宫格触发
* @example <u-grid-item></u-grid-item>
*/
export default {
name: "u-grid-item",
mixins: [mpMixin, mixin, props],
data() {
return {
parentData: {
col: 0, //
border: true, //
},
// #ifdef APP-NVUE
width: 0, // nvuevuecomputed
// #endif
// #ifdef MP-TOUTIAO
width: '100%',
// #endif
classes: [], //
};
},
mounted() {
this.init()
},
emits: ['click'],
// options
// #ifdef MP-WEIXIN
options: {
virtualHost: true ,//Vue flex
},
// #endif
computed: {
// #ifndef APP-NVUE || MP-TOUTIAO
// vuecomputed
width() {
if (this.parentData.col > 0) {
return 100 / Number(this.parentData.col) + '%'
} else {
return 0;
}
},
// #endif
itemStyle() {
const style = {
background: this.bgColor,
width: this.width
}
return deepMerge(style, addStyle(this.customStyle))
}
},
methods: {
init() {
// u-gridchildren
// item
uni.$on('$uGridItem', () => {
this.gridItemClasses()
})
//
this.updateParentData()
// #ifdef APP-NVUE
// nvue
this.$nextTick(function(){
this.getItemWidth()
})
// #endif
// grid-item
uni.$emit('$uGridItem')
this.gridItemClasses()
},
//
updateParentData() {
// mixin
this.getParentData('u-grid');
},
clickHandler() {
let name = this.name
// namechildrenthis
const children = this.parent?.children
if(children && this.name === null) {
name = children.findIndex(child => child === this)
}
//
this.parent && this.parent.childClick(name)
this.$emit('click', name)
},
async getItemWidth() {
// nvue使使
let width = 0
if(this.parent) {
// item
const parentWidth = await this.getParentWidth()
width = parentWidth / Number(this.parentData.col) + 'px'
}
this.width = width
},
//
getParentWidth() {
// #ifdef APP-NVUE
// promiseawait
const dom = uni.requireNativePlugin('dom')
return new Promise(resolve => {
// ref
dom.getComponentRect(this.parent.$refs['u-grid'], res => {
resolve(res.size.width)
})
})
// #endif
},
gridItemClasses() {
if(this.parentData.border) {
let classes = []
this.parent.children.map((child, index) =>{
if(this === child) {
const len = this.parent.children.length
// 沿child2
if((index + 1) % this.parentData.col !== 0 && index + 1 !== len) {
classes.push('u-border-right')
}
//
// 0
const lessNum = len % this.parentData.col === 0 ? this.parentData.col : len % this.parentData.col
// child
if(index < len - lessNum) {
classes.push('u-border-bottom')
}
}
})
// ","
// #ifdef MP-ALIPAY || MP-TOUTIAO
classes = classes.join(' ')
// #endif
this.classes = classes
}
}
},
// #ifdef VUE2
beforeDestroy() {
// #endif
// #ifdef VUE3
beforeUnmount() {
// #endif
//
uni.$off('$uGridItem')
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-grid-item-hover-class-opcatiy:.5 !default;
$u-grid-item-margin-top:1rpx !default;
$u-grid-item-border-right-width:0.5px !default;
$u-grid-item-border-bottom-width:0.5px !default;
$u-grid-item-border-right-color:$u-border-color !default;
$u-grid-item-border-bottom-color:$u-border-color !default;
.u-grid-item {
align-items: center;
justify-content: center;
position: relative;
flex-direction: column;
/* #ifndef APP-NVUE */
box-sizing: border-box;
display: flex;
/* #endif */
/* #ifdef MP */
position: relative;
float: left;
/* #endif */
/* #ifdef MP-WEIXIN */
margin-top:$u-grid-item-margin-top;
/* #endif */
&--hover-class {
opacity:$u-grid-item-hover-class-opcatiy;
}
}
/* #ifdef APP-NVUE */
// nvueapp.vue
.u-border-right {
border-right-width:$u-grid-item-border-right-width;
border-color: $u-grid-item-border-right-color;
}
.u-border-bottom {
border-bottom-width:$u-grid-item-border-bottom-width;
border-color:$u-grid-item-border-bottom-color;
}
/* #endif */
</style>

View File

@ -0,0 +1,20 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 分成几列
col: {
type: [String, Number],
default: () => defProps.grid.col
},
// 是否显示边框
border: {
type: Boolean,
default: () => defProps.grid.border
},
// 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右
align: {
type: String,
default: () => defProps.grid.align
}
}
}

View File

@ -0,0 +1,113 @@
<template>
<view
class="u-grid"
ref='u-grid'
:style="[gridStyle]"
>
<slot />
</view>
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addStyle, deepMerge } from '../../libs/function/index';
/**
* grid 宫格布局
* @description 宫格组件一般用于同时展示多个同类项目的场景可以给宫格的项目设置徽标组件(badge)或者图标等也可以扩展为左右滑动的轮播形式
* @tutorial https://ijry.github.io/uview-plus/components/grid.html
* @property {String | Number} col 宫格的列数默认 3
* @property {Boolean} border 是否显示宫格的边框默认 false
* @property {String} align 宫格对齐方式表现为数量少的时候靠左居中还是靠右 默认 'left'
* @property {Object} customStyle 定义需要用到的外部样式
* @event {Function} click 点击宫格触发
* @example <u-grid :col="3" @click="click"></u-grid>
*/
export default {
name: 'u-grid',
mixins: [mpMixin, mixin, props],
data() {
return {
index: 0,
width: 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 deepMerge(style, addStyle(this.customStyle));
}
},
emits: ['click'], //
// 20240409virtualHostcreated
// #ifdef MP-WEIXIN
options: {
// virtualHost: true ,//Vue flex
},
// #endif
methods: {
// u-grid-itemu-grid
childClick(name) {
this.$emit('click', name)
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-grid-width:100% !default;
.u-grid {
/* #ifdef MP */
width: $u-grid-width;
position: relative;
box-sizing: border-box;
overflow: hidden;
display: block;
/* #endif */
justify-content: center;
@include flex;
flex-wrap: wrap;
align-items: center;
// uni-app使flex,nvue/uvueflex
// 使grid使20240409uni-appvirtualHostbug
/* #ifdef MP-TOUTIAO */
display: grid;
grid-template-columns: repeat(v-bind(col), 1fr);
/* #endif */
}
</style>

View File

@ -0,0 +1,214 @@
export default {
'uicon-level': '\ue693',
'uicon-column-line': '\ue68e',
'uicon-checkbox-mark': '\ue807',
'uicon-folder': '\ue7f5',
'uicon-movie': '\ue7f6',
'uicon-star-fill': '\ue669',
'uicon-star': '\ue65f',
'uicon-phone-fill': '\ue64f',
'uicon-phone': '\ue622',
'uicon-apple-fill': '\ue881',
'uicon-chrome-circle-fill': '\ue885',
'uicon-backspace': '\ue67b',
'uicon-attach': '\ue632',
'uicon-cut': '\ue948',
'uicon-empty-car': '\ue602',
'uicon-empty-coupon': '\ue682',
'uicon-empty-address': '\ue646',
'uicon-empty-favor': '\ue67c',
'uicon-empty-permission': '\ue686',
'uicon-empty-news': '\ue687',
'uicon-empty-search': '\ue664',
'uicon-github-circle-fill': '\ue887',
'uicon-rmb': '\ue608',
'uicon-person-delete-fill': '\ue66a',
'uicon-reload': '\ue788',
'uicon-order': '\ue68f',
'uicon-server-man': '\ue6bc',
'uicon-search': '\ue62a',
'uicon-fingerprint': '\ue955',
'uicon-more-dot-fill': '\ue630',
'uicon-scan': '\ue662',
'uicon-share-square': '\ue60b',
'uicon-map': '\ue61d',
'uicon-map-fill': '\ue64e',
'uicon-tags': '\ue629',
'uicon-tags-fill': '\ue651',
'uicon-bookmark-fill': '\ue63b',
'uicon-bookmark': '\ue60a',
'uicon-eye': '\ue613',
'uicon-eye-fill': '\ue641',
'uicon-mic': '\ue64a',
'uicon-mic-off': '\ue649',
'uicon-calendar': '\ue66e',
'uicon-calendar-fill': '\ue634',
'uicon-trash': '\ue623',
'uicon-trash-fill': '\ue658',
'uicon-play-left': '\ue66d',
'uicon-play-right': '\ue610',
'uicon-minus': '\ue618',
'uicon-plus': '\ue62d',
'uicon-info': '\ue653',
'uicon-info-circle': '\ue7d2',
'uicon-info-circle-fill': '\ue64b',
'uicon-question': '\ue715',
'uicon-error': '\ue6d3',
'uicon-close': '\ue685',
'uicon-checkmark': '\ue6a8',
'uicon-android-circle-fill': '\ue67e',
'uicon-android-fill': '\ue67d',
'uicon-ie': '\ue87b',
'uicon-IE-circle-fill': '\ue889',
'uicon-google': '\ue87a',
'uicon-google-circle-fill': '\ue88a',
'uicon-setting-fill': '\ue872',
'uicon-setting': '\ue61f',
'uicon-minus-square-fill': '\ue855',
'uicon-plus-square-fill': '\ue856',
'uicon-heart': '\ue7df',
'uicon-heart-fill': '\ue851',
'uicon-camera': '\ue7d7',
'uicon-camera-fill': '\ue870',
'uicon-more-circle': '\ue63e',
'uicon-more-circle-fill': '\ue645',
'uicon-chat': '\ue620',
'uicon-chat-fill': '\ue61e',
'uicon-bag-fill': '\ue617',
'uicon-bag': '\ue619',
'uicon-error-circle-fill': '\ue62c',
'uicon-error-circle': '\ue624',
'uicon-close-circle': '\ue63f',
'uicon-close-circle-fill': '\ue637',
'uicon-checkmark-circle': '\ue63d',
'uicon-checkmark-circle-fill': '\ue635',
'uicon-question-circle-fill': '\ue666',
'uicon-question-circle': '\ue625',
'uicon-share': '\ue631',
'uicon-share-fill': '\ue65e',
'uicon-shopping-cart': '\ue621',
'uicon-shopping-cart-fill': '\ue65d',
'uicon-bell': '\ue609',
'uicon-bell-fill': '\ue640',
'uicon-list': '\ue650',
'uicon-list-dot': '\ue616',
'uicon-zhihu': '\ue6ba',
'uicon-zhihu-circle-fill': '\ue709',
'uicon-zhifubao': '\ue6b9',
'uicon-zhifubao-circle-fill': '\ue6b8',
'uicon-weixin-circle-fill': '\ue6b1',
'uicon-weixin-fill': '\ue6b2',
'uicon-twitter-circle-fill': '\ue6ab',
'uicon-twitter': '\ue6aa',
'uicon-taobao-circle-fill': '\ue6a7',
'uicon-taobao': '\ue6a6',
'uicon-weibo-circle-fill': '\ue6a5',
'uicon-weibo': '\ue6a4',
'uicon-qq-fill': '\ue6a1',
'uicon-qq-circle-fill': '\ue6a0',
'uicon-moments-circel-fill': '\ue69a',
'uicon-moments': '\ue69b',
'uicon-qzone': '\ue695',
'uicon-qzone-circle-fill': '\ue696',
'uicon-baidu-circle-fill': '\ue680',
'uicon-baidu': '\ue681',
'uicon-facebook-circle-fill': '\ue68a',
'uicon-facebook': '\ue689',
'uicon-car': '\ue60c',
'uicon-car-fill': '\ue636',
'uicon-warning-fill': '\ue64d',
'uicon-warning': '\ue694',
'uicon-clock-fill': '\ue638',
'uicon-clock': '\ue60f',
'uicon-edit-pen': '\ue612',
'uicon-edit-pen-fill': '\ue66b',
'uicon-email': '\ue611',
'uicon-email-fill': '\ue642',
'uicon-minus-circle': '\ue61b',
'uicon-minus-circle-fill': '\ue652',
'uicon-plus-circle': '\ue62e',
'uicon-plus-circle-fill': '\ue661',
'uicon-file-text': '\ue663',
'uicon-file-text-fill': '\ue665',
'uicon-pushpin': '\ue7e3',
'uicon-pushpin-fill': '\ue86e',
'uicon-grid': '\ue673',
'uicon-grid-fill': '\ue678',
'uicon-play-circle': '\ue647',
'uicon-play-circle-fill': '\ue655',
'uicon-pause-circle-fill': '\ue654',
'uicon-pause': '\ue8fa',
'uicon-pause-circle': '\ue643',
'uicon-eye-off': '\ue648',
'uicon-eye-off-outline': '\ue62b',
'uicon-gift-fill': '\ue65c',
'uicon-gift': '\ue65b',
'uicon-rmb-circle-fill': '\ue657',
'uicon-rmb-circle': '\ue677',
'uicon-kefu-ermai': '\ue656',
'uicon-server-fill': '\ue751',
'uicon-coupon-fill': '\ue8c4',
'uicon-coupon': '\ue8ae',
'uicon-integral': '\ue704',
'uicon-integral-fill': '\ue703',
'uicon-home-fill': '\ue964',
'uicon-home': '\ue965',
'uicon-hourglass-half-fill': '\ue966',
'uicon-hourglass': '\ue967',
'uicon-account': '\ue628',
'uicon-plus-people-fill': '\ue626',
'uicon-minus-people-fill': '\ue615',
'uicon-account-fill': '\ue614',
'uicon-thumb-down-fill': '\ue726',
'uicon-thumb-down': '\ue727',
'uicon-thumb-up': '\ue733',
'uicon-thumb-up-fill': '\ue72f',
'uicon-lock-fill': '\ue979',
'uicon-lock-open': '\ue973',
'uicon-lock-opened-fill': '\ue974',
'uicon-lock': '\ue97a',
'uicon-red-packet-fill': '\ue690',
'uicon-photo-fill': '\ue98b',
'uicon-photo': '\ue98d',
'uicon-volume-off-fill': '\ue659',
'uicon-volume-off': '\ue644',
'uicon-volume-fill': '\ue670',
'uicon-volume': '\ue633',
'uicon-red-packet': '\ue691',
'uicon-download': '\ue63c',
'uicon-arrow-up-fill': '\ue6b0',
'uicon-arrow-down-fill': '\ue600',
'uicon-play-left-fill': '\ue675',
'uicon-play-right-fill': '\ue676',
'uicon-rewind-left-fill': '\ue679',
'uicon-rewind-right-fill': '\ue67a',
'uicon-arrow-downward': '\ue604',
'uicon-arrow-leftward': '\ue601',
'uicon-arrow-rightward': '\ue603',
'uicon-arrow-upward': '\ue607',
'uicon-arrow-down': '\ue60d',
'uicon-arrow-right': '\ue605',
'uicon-arrow-left': '\ue60e',
'uicon-arrow-up': '\ue606',
'uicon-skip-back-left': '\ue674',
'uicon-skip-forward-right': '\ue672',
'uicon-rewind-right': '\ue66f',
'uicon-rewind-left': '\ue671',
'uicon-arrow-right-double': '\ue68d',
'uicon-arrow-left-double': '\ue68c',
'uicon-wifi-off': '\ue668',
'uicon-wifi': '\ue667',
'uicon-empty-data': '\ue62f',
'uicon-empty-history': '\ue684',
'uicon-empty-list': '\ue68b',
'uicon-empty-page': '\ue627',
'uicon-empty-order': '\ue639',
'uicon-man': '\ue697',
'uicon-woman': '\ue69c',
'uicon-man-add': '\ue61c',
'uicon-man-add-fill': '\ue64c',
'uicon-man-delete': '\ue61a',
'uicon-man-delete-fill': '\ue66a',
'uicon-zh': '\ue70a',
'uicon-en': '\ue692'
}

View File

@ -0,0 +1,90 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 图标类名
name: {
type: String,
default: () => defProps.icon.name
},
// 图标颜色,可接受主题色
color: {
type: String,
default: () => defProps.icon.color
},
// 字体大小单位px
size: {
type: [String, Number],
default: () => defProps.icon.size
},
// 是否显示粗体
bold: {
type: Boolean,
default: () => defProps.icon.bold
},
// 点击图标的时候传递事件出去的index用于区分点击了哪一个
index: {
type: [String, Number],
default: () => defProps.icon.index
},
// 触摸图标时的类名
hoverClass: {
type: String,
default: () => defProps.icon.hoverClass
},
// 自定义扩展前缀,方便用户扩展自己的图标库
customPrefix: {
type: String,
default: () => defProps.icon.customPrefix
},
// 图标右边或者下面的文字
label: {
type: [String, Number],
default: () => defProps.icon.label
},
// label的位置只能右边或者下边
labelPos: {
type: String,
default: () => defProps.icon.labelPos
},
// label的大小
labelSize: {
type: [String, Number],
default: () => defProps.icon.labelSize
},
// label的颜色
labelColor: {
type: String,
default: () => defProps.icon.labelColor
},
// label与图标的距离
space: {
type: [String, Number],
default: () => defProps.icon.space
},
// 图片的mode
imgMode: {
type: String,
default: () => defProps.icon.imgMode
},
// 用于显示图片小图标时,图片的宽度
width: {
type: [String, Number],
default: () => defProps.icon.width
},
// 用于显示图片小图标时,图片的高度
height: {
type: [String, Number],
default: () => defProps.icon.height
},
// 用于解决某些情况下,让图标垂直居中的用途
top: {
type: [String, Number],
default: () => defProps.icon.top
},
// 是否阻止事件传播
stop: {
type: Boolean,
default: () => defProps.icon.stop
}
}
}

View File

@ -0,0 +1,242 @@
<template>
<view
class="u-icon"
@tap="clickHandler"
:class="['u-icon--' + labelPos]"
>
<image
class="u-icon__img"
v-if="isImg"
:src="name"
:mode="imgMode"
:style="[imgStyle, addStyle(customStyle)]"
></image>
<text
v-else
class="u-icon__icon"
:class="uClasses"
:style="[iconStyle, addStyle(customStyle)]"
:hover-class="hoverClass"
>{{icon}}</text>
<!-- 这里进行空字符串判断如果仅仅是v-if="label"可能会出现传递0的时候结果也无法显示 -->
<text
v-if="label !== ''"
class="u-icon__label"
:style="{
color: labelColor,
fontSize: addUnit(labelSize),
marginLeft: labelPos == 'right' ? addUnit(space) : 0,
marginTop: labelPos == 'bottom' ? addUnit(space) : 0,
marginRight: labelPos == 'left' ? addUnit(space) : 0,
marginBottom: labelPos == 'top' ? addUnit(space) : 0,
}"
>{{ label }}</text>
</view>
</template>
<script>
// #ifdef APP-NVUE
// nvueweexdom
// https://weex.apache.org/zh/docs/modules/dom.html#addrule
const fontUrl = 'https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf'
const domModule = weex.requireModule('dom')
domModule.addRule('fontFace', {
'fontFamily': "uicon-iconfont",
'src': `url('${fontUrl}')`
})
// #endif
// unicode
import icons from './icons'
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, addStyle } from '../../libs/function/index';
import config from '../../libs/config/config';
/**
* icon 图标
* @description 基于字体的图标集包含了大多数常见场景的图标
* @tutorial https://ijry.github.io/uview-plus/components/icon.html
* @property {String} name 图标名称见示例图标集
* @property {String} color 图标颜色,可接受主题色 默认 color['u-content-color']
* @property {String | Number} size 图标字体大小单位px 默认 '16px'
* @property {Boolean} bold 是否显示粗体 默认 false
* @property {String | Number} index 点击图标的时候传递事件出去的index用于区分点击了哪一个
* @property {String} hoverClass 图标按下去的样式类用法同uni的view组件的hoverClass参数详情见官网
* @property {String} customPrefix 自定义扩展前缀方便用户扩展自己的图标库 默认 'uicon'
* @property {String | Number} label 图标右侧的label文字
* @property {String} labelPos label相对于图标的位置只能right或bottom 默认 'right'
* @property {String | Number} labelSize label字体大小单位px 默认 '15px'
* @property {String} labelColor 图标右侧的label文字颜色 默认 color['u-content-color']
* @property {String | Number} space label与图标的距离单位px 默认 '3px'
* @property {String} imgMode 图片的mode
* @property {String | Number} width 显示图片小图标时的宽度
* @property {String | Number} height 显示图片小图标时的高度
* @property {String | Number} top 图标在垂直方向上的定位 用于解决某些情况下让图标垂直居中的用途 默认 0
* @property {Boolean} stop 是否阻止事件传播 默认 false
* @property {Object} customStyle icon的样式对象形式
* @event {Function} click 点击图标时触发
* @event {Function} touchstart 事件触摸时触发
* @example <u-icon name="photo" color="#2979ff" size="28"></u-icon>
*/
export default {
name: 'u-icon',
data() {
return {
}
},
emits: ['click'],
mixins: [mpMixin, mixin, props],
computed: {
uClasses() {
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 && 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: addUnit(this.size),
lineHeight: addUnit(this.size),
fontWeight: this.bold ? 'bold' : 'normal',
//
top: addUnit(this.top)
}
//
if (this.color && !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 ? addUnit(this.width) : addUnit(this.size)
style.height = this.height ? addUnit(this.height) : addUnit(this.size)
return style
},
//
icon() {
// 使name
if (this.customPrefix !== "uicon") return "";
// nameunicode
return icons['uicon-' + this.name] || this.name
}
},
methods: {
addStyle,
addUnit,
clickHandler(e) {
this.$emit('click', this.index)
//
this.stop && this.preventEvent(e)
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
//
$u-icon-primary: $u-primary !default;
$u-icon-success: $u-success !default;
$u-icon-info: $u-info !default;
$u-icon-warning: $u-warning !default;
$u-icon-error: $u-error !default;
$u-icon-label-line-height:1 !default;
/* #ifndef APP-NVUE */
// nvue
@font-face {
font-family: 'uicon-iconfont';
src: url('https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf') format('truetype');
}
/* #endif */
.u-icon {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
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 {
font-family: uicon-iconfont;
position: relative;
@include flex;
align-items: center;
&--primary {
color: $u-icon-primary;
}
&--success {
color: $u-icon-success;
}
&--error {
color: $u-icon-error;
}
&--warning {
color: $u-icon-warning;
}
&--info {
color: $u-icon-info;
}
}
&__img {
/* #ifndef APP-NVUE */
height: auto;
will-change: transform;
/* #endif */
}
&__label {
/* #ifndef APP-NVUE */
line-height: $u-icon-label-line-height;
/* #endif */
}
}
</style>

View File

@ -0,0 +1,85 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 图片地址
src: {
type: String,
default: () => defProps.image.src
},
// 裁剪模式
mode: {
type: String,
default: () => defProps.image.mode
},
// 宽度,单位任意
width: {
type: [String, Number],
default: () => defProps.image.width
},
// 高度,单位任意
height: {
type: [String, Number],
default: () => defProps.image.height
},
// 图片形状circle-圆形square-方形
shape: {
type: String,
default: () => defProps.image.shape
},
// 圆角,单位任意
radius: {
type: [String, Number],
default: () => defProps.image.radius
},
// 是否懒加载微信小程序、App、百度小程序、字节跳动小程序
lazyLoad: {
type: Boolean,
default: () => defProps.image.lazyLoad
},
// 开启长按图片显示识别微信小程序码菜单
showMenuByLongpress: {
type: Boolean,
default: () => defProps.image.showMenuByLongpress
},
// 加载中的图标,或者小图片
loadingIcon: {
type: String,
default: () => defProps.image.loadingIcon
},
// 加载失败的图标,或者小图片
errorIcon: {
type: String,
default: () => defProps.image.errorIcon
},
// 是否显示加载中的图标或者自定义的slot
showLoading: {
type: Boolean,
default: () => defProps.image.showLoading
},
// 是否显示加载错误的图标或者自定义的slot
showError: {
type: Boolean,
default: () => defProps.image.showError
},
// 是否需要淡入效果
fade: {
type: Boolean,
default: () => defProps.image.fade
},
// 只支持网络资源,只对微信小程序有效
webp: {
type: Boolean,
default: () => defProps.image.webp
},
// 过渡时间单位ms
duration: {
type: [String, Number],
default: () => defProps.image.duration
},
// 背景颜色,用于深色页面加载图片时,为了和背景色融合
bgColor: {
type: String,
default: () => defProps.image.bgColor
}
}
}

View File

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

View File

@ -0,0 +1,30 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 列表锚点文本内容
text: {
type: [String, Number],
default: () => defProps.indexAnchor.text
},
// 列表锚点文字颜色
color: {
type: String,
default: () => defProps.indexAnchor.color
},
// 列表锚点文字大小单位默认px
size: {
type: [String, Number],
default: () => defProps.indexAnchor.size
},
// 列表锚点背景颜色
bgColor: {
type: String,
default: () => defProps.indexAnchor.bgColor
},
// 列表锚点高度单位默认px
height: {
type: [String, Number],
default: () => defProps.indexAnchor.height
}
}
}

View File

@ -0,0 +1,95 @@
<template>
<!-- #ifdef APP-NVUE -->
<header>
<!-- #endif -->
<view
class="u-index-anchor u-border-bottom"
:ref="`u-index-anchor-${text}`"
:style="{
height: addUnit(height),
backgroundColor: bgColor
}"
>
<text
class="u-index-anchor__text"
:style="{
fontSize: addUnit(size),
color: color
}"
>{{ text }}</text>
</view>
<!-- #ifdef APP-NVUE -->
</header>
<!-- #endif -->
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { addUnit, $parent, error } from '../../libs/function/index';
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* IndexAnchor 列表锚点
* @description
* @tutorial https://uview-plus.jiangruyi.com/components/indexList.html
* @property {String | Number} text 列表锚点文本内容
* @property {String} color 列表锚点文字颜色 ( 默认 '#606266' )
* @property {String | Number} size 列表锚点文字大小单位默认px ( 默认 14 )
* @property {String} bgColor 列表锚点背景颜色 ( 默认 '#dedede' )
* @property {String | Number} height 列表锚点高度单位默认px ( 默认 32 )
* @example <u-index-anchor :text="indexList[index]"></u-index-anchor>
*/
export default {
name: 'u-index-anchor',
mixins: [mpMixin, mixin, props],
data() {
return {
}
},
mounted() {
this.init()
},
methods: {
addUnit,
init() {
// parent
const indexList = $parent.call(this, 'u-index-list')
if (!indexList) {
return error('u-index-anchor必须要搭配u-index-list组件使用')
}
// u-index-list
indexList.anchors.push(this)
const indexListItem = $parent.call(this, 'u-index-item')
// #ifndef APP-NVUE
// nvueu-index-anchoru-index-item
if (!indexListItem) {
return error('u-index-anchor必须要搭配u-index-item组件使用')
}
// u-index-itemidanchortextnvuescroll-view
indexListItem.id = this.text.charCodeAt(0)
// #endif
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-index-anchor {
position: sticky;
top: 0;
@include flex;
align-items: center;
padding-left: 15px;
z-index: 1;
&__text {
@include flex;
align-items: center;
}
}
</style>

View File

@ -0,0 +1,6 @@
// import defProps from '../../libs/config/props.js';
export default {
props: {
}
}

View File

@ -0,0 +1,90 @@
<template>
<!-- #ifdef APP-NVUE -->
<cell ref="u-index-item">
<!-- #endif -->
<view
class="u-index-item"
:id="`u-index-item-${id}`"
:class="[`u-index-item-${id}`]"
>
<slot />
</view>
<!-- #ifdef APP-NVUE -->
</cell>
<!-- #endif -->
</template>
<script>
import props from './props';
import mpMixin from '../../libs/mixin/mpMixin';
import mixin from '../../libs/mixin/mixin';
import { sleep, error } from '../../libs/function/index';
// #ifdef APP-NVUE
// weexKPIdom
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* IndexItem
* @description
* @tutorial https://uview-plus.jiangruyi.com/components/indexList.html
* @property {String}
* @event {Function}
* @example
*/
export default {
name: 'u-index-item',
mixins: [mpMixin, mixin, props],
data() {
return {
//
top: 0,
height: 0,
id: ''
}
},
created() {
// u-index-anchor
this.anchor = {}
},
mounted() {
this.init()
},
methods: {
init() {
// parent
this.getParentData('u-index-list')
if (!this.parent) {
return error('u-index-item必须要搭配u-index-list组件使用')
}
sleep().then(() =>{
this.getIndexItemRect().then(size => {
// childrentop
this.top = Math.ceil(size.top)
this.height = Math.ceil(size.height)
})
})
},
getIndexItemRect() {
return new Promise(resolve => {
// #ifndef APP-NVUE
this.$uGetRect('.u-index-item').then(size => {
resolve(size)
})
// #endif
// #ifdef APP-NVUE
const ref = this.$refs['u-index-item']
dom.getComponentRect(ref, res => {
resolve(res.size)
})
// #endif
})
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
</style>

View File

@ -0,0 +1,30 @@
import defProps from '../../libs/config/props.js';
export default {
props: {
// 右边锚点非激活的颜色
inactiveColor: {
type: String,
default: () => defProps.indexList.inactiveColor
},
// 右边锚点激活的颜色
activeColor: {
type: String,
default: () => defProps.indexList.activeColor
},
// 索引字符列表,数组形式
indexList: {
type: Array,
default: () => defProps.indexList.indexList
},
// 是否开启锚点自动吸顶
sticky: {
type: Boolean,
default: () => defProps.indexList.sticky
},
// 自定义导航栏的高度
customNavHeight: {
type: [String, Number],
default: () => defProps.indexList.customNavHeight
}
}
}

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