This commit is contained in:
陆亚狮 2025-04-07 09:50:02 +08:00
parent 601f9fe054
commit f6e6ae8c9d
484 changed files with 61041 additions and 1 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
node_modules
uni_modules
.history

View File

@ -0,0 +1,193 @@
## 为减小组件包的大小默认组件包中不包含编辑、latex 公式等扩展功能,需要使用扩展功能的请参考下方的 插件扩展 栏的说明
## 功能介绍
- 全端支持(含 `v3、NVUE`
- 支持丰富的标签(包括 `table`、`video`、`svg` 等)
- 支持丰富的事件效果(自动预览图片、链接处理等)
- 支持设置占位图(加载中、出错时、预览时)
- 支持锚点跳转、长按复制等丰富功能
- 支持大部分 *html* 实体
- 丰富的插件(关键词搜索、内容编辑、`latex` 公式等)
- 效率高、容错性强且轻量化
查看 [功能介绍](https://jin-yufeng.gitee.io/mp-html/#/overview/feature) 了解更多
## 使用方法
- `uni_modules` 方式
1. 点击右上角的 `使用 HBuilder X 导入插件` 按钮直接导入项目或点击 `下载插件 ZIP` 按钮下载插件包并解压到项目的 `uni_modules/mp-html` 目录下
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<!-- 不需要引入,可直接使用 -->
<mp-html :content="html" />
```
```javascript
export default {
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
3. 需要更新版本时在 `HBuilder X` 中右键 `uni_modules/mp-html` 目录选择 `从插件市场更新` 即可
- 源码方式
1. 从 [github](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 或 [gitee](https://gitee.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 下载源码
插件市场的 **非 uni_modules 版本** 无法更新,不建议从插件市场获取
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<mp-html :content="html" />
```
```javascript
import mpHtml from '@/components/mp-html/mp-html'
export default {
// HBuilderX 2.5.5+ 可以通过 easycom 自动引入
components: {
mpHtml
},
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
- npm 方式
1. 在项目根目录下执行
```bash
npm install mp-html
```
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<mp-html :content="html" />
```
```javascript
import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html'
export default {
// 不可省略
components: {
mpHtml
},
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
3. 需要更新版本时执行以下命令即可
```bash
npm update mp-html
```
使用 *cli* 方式运行的项目,通过 *npm* 方式引入时,需要在 *vue.config.js* 中配置 *transpileDependencies*,详情可见 [#330](https://github.com/jin-yufeng/mp-html/issues/330#issuecomment-913617687)
如果在 **nvue** 中使用还要将 `dist/uni-app/static` 目录下的内容拷贝到项目的 `static` 目录下,否则无法运行
查看 [快速开始](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart) 了解更多
## 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|:---:|:---:|:---:|---|
| container-style | String | | 容器的样式([2.1.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v210) |
| content | String | | 用于渲染的 html 字符串 |
| copy-link | Boolean | true | 是否允许外部链接被点击时自动复制 |
| domain | String | | 主域名(用于链接拼接) |
| error-img | String | | 图片出错时的占位图链接 |
| lazy-load | Boolean | false | 是否开启图片懒加载 |
| loading-img | String | | 图片加载过程中的占位图链接 |
| pause-video | Boolean | true | 是否在播放一个视频时自动暂停其他视频 |
| preview-img | Boolean | true | 是否允许图片被点击时自动预览 |
| scroll-table | Boolean | false | 是否给每个表格添加一个滚动层使其能单独横向滚动 |
| selectable | Boolean | false | 是否开启文本长按复制 |
| set-title | Boolean | true | 是否将 title 标签的内容设置到页面标题 |
| show-img-menu | Boolean | true | 是否允许图片被长按时显示菜单 |
| tag-style | Object | | 设置标签的默认样式 |
| use-anchor | Boolean | false | 是否使用锚点链接 |
查看 [属性](https://jin-yufeng.gitee.io/mp-html/#/basic/prop) 了解更多
## 组件事件
| 名称 | 触发时机 |
|:---:|---|
| load | dom 树加载完毕时 |
| ready | 图片加载完毕时 |
| error | 发生渲染错误时 |
| imgtap | 图片被点击时 |
| linktap | 链接被点击时 |
| play | 音视频播放时 |
查看 [事件](https://jin-yufeng.gitee.io/mp-html/#/basic/event) 了解更多
## api
组件实例上提供了一些 `api` 方法可供调用
| 名称 | 作用 |
|:---:|---|
| in | 将锚点跳转的范围限定在一个 scroll-view 内 |
| navigateTo | 锚点跳转 |
| getText | 获取文本内容 |
| getRect | 获取富文本内容的位置和大小 |
| setContent | 设置富文本内容 |
| imgList | 获取所有图片的数组 |
| pauseMedia | 暂停播放音视频([2.2.2+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v222) |
| setPlaybackRate | 设置音视频播放速率([2.4.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v240) |
查看 [api](https://jin-yufeng.gitee.io/mp-html/#/advanced/api) 了解更多
## 插件扩展
除基本功能外,本组件还提供了丰富的扩展,可按照需要选用
| 名称 | 作用 |
|:---:|---|
| audio | 音乐播放器 |
| editable | 富文本 **编辑**[示例项目](https://mp-html.oss-cn-hangzhou.aliyuncs.com/editable.zip) |
| emoji | 解析 emoji |
| highlight | 代码块高亮显示 |
| markdown | 渲染 markdown |
| search | 关键词搜索 |
| style | 匹配 style 标签中的样式 |
| txv-video | 使用腾讯视频 |
| img-cache | 图片缓存 by [@PentaTea](https://github.com/PentaTea) |
| latex | 渲染 latex 公式 by [@Zeng-J](https://github.com/Zeng-J) |
从插件市场导入的包中 **不含有** 扩展插件,使用插件需通过微信小程序 `富文本插件` 获取或参考以下方法进行打包:
1. 获取完整组件包
```bash
npm install mp-html
```
2. 编辑 `tools/config.js` 中的 `plugins` 项,选择需要的插件
3. 生成新的组件包
`node_modules/mp-html` 目录下执行
```bash
npm install
npm run build:uni-app
```
4. 拷贝 `dist/uni-app` 中的内容到项目根目录
查看 [插件](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin) 了解更多
## 关于 nvue
`nvue` 使用原生渲染,不支持部分 `css` 样式,为实现和 `html` 相同的效果,组件内部通过 `web-view` 进行渲染,性能上差于原生,根据 `weex` 官方建议,`web` 标签仅应用在非常规的降级场景。因此,如果通过原生的方式(如 `richtext`)能够满足需要,则不建议使用本组件,如果有较多的富文本内容,则可以直接使用 `vue` 页面
由于渲染方式与其他端不同,有以下限制:
1. 不支持 `lazy-load` 属性
2. 视频不支持全屏播放
3. 如果在 `flex-direction: row` 的容器中使用,需要给组件设置宽度或设置 `flex: 1` 占满剩余宽度
`nvue` 模式下,[此问题](https://ask.dcloud.net.cn/question/119678) 修复前,不支持通过 `uni_modules` 引入,需要本地引入(将 [dist/uni-app](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 中的内容拷贝到项目根目录下)
## 立即体验
![富文本插件](https://mp-html.oss-cn-hangzhou.aliyuncs.com/qrcode.jpg)
## 问题反馈
遇到问题时,请先查阅 [常见问题](https://jin-yufeng.gitee.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题
可通过 [issue](https://github.com/jin-yufeng/mp-html/issues/new/choose) 、插件问答或发送邮件到 [mp_html@126.com](mailto:mp_html@126.com) 提问,不建议在评论区提问(不方便回复)
提问请严格按照 [issue 模板](https://github.com/jin-yufeng/mp-html/issues/new/choose) ,描述清楚使用环境、`html` 内容或可复现的 `demo` 项目以及复现方式,对于 **描述不清**、**无法复现** 或重复的问题将不予回复
欢迎加入 `QQ` 交流群:
群1已满`699734691`
群2`778239129`
查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多

View File

@ -0,0 +1,129 @@
## v2.4.22023-05-14
1. `A` `editable` 插件支持修改文字颜色 [详细](https://github.com/jin-yufeng/mp-html/issues/254)
2. `F` 修复了 `svg` 中有 `style` 不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/505)
3. `F` 修复了使用旧版编译器可能报错 `Bad attr nodes` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/472)
4. `F` 修复了 `app` 端可能出现无法读取 `lazyLoad` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/513)
5. `F` 修复了 `editable` 插件在点击换图时未拼接 `domain` 的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/497) by [@TwoKe945](https://github.com/TwoKe945)
6. `F` 修复了 `latex` 插件部分情况下不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/515)
7. `F` 修复了 `editable` 插件点击音视频时其他标签框不消失的问题
## v2.4.12022-12-25
1. `F` 修复了没有图片时 `ready` 事件可能不触发的问题
2. `F` 修复了加载过程中可能出现 `Root label not found` 错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/470)
3. `F` 修复了 `audio` 插件退出页面可能会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/457)
4. `F` 修复了 `vue3` 运行到 `app``HBuilder X 3.6.10` 以上报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/480)
5. `F` 修复了 `nvue` 端链接中包含 `%22` 时可能无法显示的问题
6. `F` 修复了 `vue3` 使用 `highlight` 插件可能报错的问题
## v2.4.02022-08-27
1. `A` 增加了 [setPlaybackRate](https://jin-yufeng.gitee.io/mp-html/#/advanced/api#setPlaybackRate) 的 `api`,可以设置音视频的播放速率 [详细](https://github.com/jin-yufeng/mp-html/issues/452)
2. `A` 示例小程序代码开源 [详细](https://github.com/jin-yufeng/mp-html-demo)
3. `U` 优化 `ready` 事件触发时机,未设置懒加载的情况下基本可以准确触发 [详细](https://github.com/jin-yufeng/mp-html/issues/195)
4. `U` `highlight` 插件在编辑状态下不进行高亮处理,便于编辑
5. `F` 修复了 `flex` 布局下图片大小可能不正确的问题
6. `F` 修复了 `selectable` 属性没有设置 `force` 也可能出现渲染异常的问题
7. `F` 修复了表格中的图片大小可能不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/448)
8. `F` 修复了含有合并单元格的表格可能无法设置竖直对齐的问题
9. `F` 修复了 `editable` 插件在 `scroll-view` 中使用时工具条位置可能不正确的问题
10. `F` 修复了 `vue3` 使用 [search](advanced/plugin#search) 插件可能导致错误换行的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/449)
## v2.3.22022-08-13
1. `A` 增加 [latex](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#latex) 插件,可以渲染数学公式 [详细](https://github.com/jin-yufeng/mp-html/pull/447) by [@Zeng-J](https://github.com/Zeng-J)
2. `U` 优化根节点下有很多标签的长内容渲染速度
3. `U` `highlight` 插件适配 `lang-xxx` 格式
4. `F` 修复了 `table` 标签设置 `border` 属性后可能无法修改边框样式的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/439) by [@zouxingjie](https://github.com/zouxingjie)
5. `F` 修复了 `editable` 插件输入连续空格无效的问题
6. `F` 修复了 `vue3` 图片设置 `inline` 会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/438)
7. `F` 修复了 `vue3` 使用 `table` 可能报错的问题
## v2.3.12022-05-20
1. `U` `app` 端支持使用本地图片
2. `U` 优化了微信小程序 `selectable` 属性在 `ios` 端的处理 [详细](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable)
3. `F` 修复了 `editable` 插件不在顶部时 `tooltip` 位置可能错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/430)
4. `F` 修复了 `vue3` 运行到微信小程序可能报错丢失内容的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/414)
5. `F` 修复了 `vue3` 部分标签可能被错误换行的问题
6. `F` 修复了 `editable` 插件 `app` 端插入视频无法预览的问题
## v2.3.02022-04-01
1. `A` 增加了 `play` 事件,音视频播放时触发,可用于与页面其他音视频进行互斥播放 [详细](basic/event#play)
2. `U` `show-img-menu` 属性支持控制预览时是否长按弹出菜单
3. `U` 优化 `wxs` 处理,提高渲染性能 [详细](https://developers.weixin.qq.com/community/develop/article/doc/0006cc2b204740f601bd43fa25a413)
4. `U` `video` 标签支持 `object-fit` 属性
5. `U` 增加支持一些常用实体编码 [详细](https://github.com/jin-yufeng/mp-html/issues/418)
6. `F` 修复了图片仅设置高度可能不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/410)
7. `F` 修复了 `video` 标签高度设置为 `auto` 不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/411)
8. `F` 修复了使用 `grid` 布局时可能样式错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/413)
9. `F` 修复了含有合并单元格的表格部分情况下显示异常的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/417)
10. `F` 修复了 `editable` 插件连续插入内容时顺序不正确的问题
11. `F` 修复了 `uni-app``vue3` 使用 `audio` 插件报错的问题
12. `F` 修复了 `uni-app``highlight` 插件使用自定义的 `prism.min.js` 报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/416)
## v2.2.22022-02-26
1. `A` 增加了 [pauseMedia](https://jin-yufeng.gitee.io/mp-html/#/advanced/api#pauseMedia) 的 `api`,可用于暂停播放音视频 [详细](https://github.com/jin-yufeng/mp-html/issues/317)
2. `U` 优化了长内容的加载速度
3. `U` 适配 `vue3` [#389](https://github.com/jin-yufeng/mp-html/issues/389)、[#398](https://github.com/jin-yufeng/mp-html/pull/398) by [@zhouhuafei](https://github.com/zhouhuafei)、[#400](https://github.com/jin-yufeng/mp-html/issues/400)
4. `F` 修复了小程序端图片高度设置为百分比时可能不显示的问题
5. `F` 修复了 `highlight` 插件部分情况下可能显示不完整的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/403)
## v2.2.12021-12-24
1. `A` `editable` 插件增加上下移动标签功能
2. `U` `editable` 插件支持在文本中间光标处插入内容
3. `F` 修复了 `nvue` 端设置 `margin` 后可能导致高度不正确的问题
4. `F` 修复了 `highlight` 插件使用压缩版的 `prism.css` 可能导致背景失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/367)
5. `F` 修复了编辑状态下使用 `emoji` 插件内容为空时可能报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/371)
6. `F` 修复了使用 `editable` 插件后将 `selectable` 属性设置为 `force` 不生效的问题
## v2.2.02021-10-12
1. `A` 增加 `customElements` 配置项,便于添加自定义功能性标签 [详细](https://github.com/jin-yufeng/mp-html/issues/350)
2. `A` `editable` 插件增加切换音视频自动播放状态的功能 [详细](https://github.com/jin-yufeng/mp-html/pull/341) by [@leeseett](https://github.com/leeseett)
3. `A` `editable` 插件删除媒体标签时触发 `remove` 事件,便于删除已上传的文件
4. `U` `editable` 插件 `insertImg` 方法支持同时插入多张图片 [详细](https://github.com/jin-yufeng/mp-html/issues/342)
5. `U` `editable` 插入图片和音视频时支持拼接 `domian` 主域名
6. `F` 修复了内部链接参数中包含 `://` 时被认为是外部链接的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/356)
7. `F` 修复了部分 `svg` 标签名或属性名大小写不正确时不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/351)
8. `F` 修复了 `nvue` 页面运行到非 `app` 平台时可能样式错误的问题
## v2.1.52021-08-13
1. `A` 增加支持标签的 `dir` 属性
2. `F` 修复了 `ruby` 标签文字与拼音没有居中对齐的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/325)
3. `F` 修复了音视频标签内有 `a` 标签时可能无法播放的问题
4. `F` 修复了 `externStyle` 中的 `class` 名包含下划线或数字时可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326)
5. `F` 修复了 `h5` 端引入 `externStyle` 可能不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326)
## v2.1.42021-07-14
1. `F` 修复了 `rt` 标签无法设置样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/318)
2. `F` 修复了表格中有单元格同时合并行和列时可能显示不正确的问题
3. `F` 修复了 `app` 端无法关闭图片长按菜单的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/322)
4. `F` 修复了 `editable` 插件只能添加图片链接不能修改的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/312) by [@leeseett](https://github.com/leeseett)
## v2.1.32021-06-12
1. `A` `editable` 插件增加 `insertTable` 方法
2. `U` `editable` 插件支持编辑表格中的空白单元格 [详细](https://github.com/jin-yufeng/mp-html/issues/310)
3. `F` 修复了 `externStyle` 中使用伪类可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/298)
4. `F` 修复了多个组件同时使用时 `tag-style` 属性时可能互相影响的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/305) by [@woodguoyu](https://github.com/woodguoyu)
5. `F` 修复了包含 `linearGradient``svg` 可能无法显示的问题
6. `F` 修复了编译到头条小程序时可能报错的问题
7. `F` 修复了 `nvue` 端不触发 `click` 事件的问题
8. `F` 修复了 `editable` 插件尾部插入时无法撤销的问题
9. `F` 修复了 `editable` 插件的 `insertHtml` 方法只能在末尾插入的问题
10. `F` 修复了 `editable` 插件插入音频不显示的问题
## v2.1.22021-04-24
1. `A` 增加了 [img-cache](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#img-cache) 插件,可以在 `app` 端缓存图片 [详细](https://github.com/jin-yufeng/mp-html/issues/292) by [@PentaTea](https://github.com/PentaTea)
2. `U` 支持通过 `container-style` 属性设置 `white-space` 来保留连续空格和换行符 [详细](https://jin-yufeng.gitee.io/mp-html/#/question/faq#space)
3. `U` 代码风格符合 [standard](https://standardjs.com) 标准
4. `U` `editable` 插件编辑状态下支持预览视频 [详细](https://github.com/jin-yufeng/mp-html/issues/286)
5. `F` 修复了 `svg` 标签内嵌 `svg` 时无法显示的问题
6. `F` 修复了编译到支付宝和头条小程序时部分区域不可复制的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/291)
## v2.1.12021-04-09
1. 修复了对 `p` 标签设置 `tag-style` 可能不生效的问题
2. 修复了 `svg` 标签中的文本无法显示的问题
3. 修复了使用 `editable` 插件编辑表格时可能报错的问题
4. 修复了使用 `highlight` 插件运行到头条小程序时可能没有样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/280)
5. 修复了使用 `editable` 插件 `editable` 属性为 `false` 时会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/284)
6. 修复了 `style` 插件连续子选择器失效的问题
7. 修复了 `editable` 插件无法修改图片和字体大小的问题
## v2.1.0.22021-03-21
修复了 `nvue` 端使用可能报错的问题
## v2.1.02021-03-20
1. `A` 增加了 [container-style](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#container-style) 属性 [详细](https://gitee.com/jin-yufeng/mp-html/pulls/1)
2. `A` 增加支持 `strike` 标签
3. `A` `editable` 插件增加 `placeholder` 属性 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable)
4. `A` `editable` 插件增加 `insertHtml` 方法 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable)
5. `U` 外部样式支持标签名选择器 [详细](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart#setting)
6. `F` 修复了 `nvue` 端部分情况下可能不显示的问题
## v2.0.52021-03-12
1. `U` [linktap](https://jin-yufeng.gitee.io/mp-html/#/basic/event#linktap) 事件增加返回内部文本内容 `innerText` [详细](https://github.com/jin-yufeng/mp-html/issues/271)
2. `U` [selectable](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable) 属性设置为 `force` 时能够在微信 `iOS` 端生效(文本块会变成 `inline-block` [详细](https://github.com/jin-yufeng/mp-html/issues/267)
3. `F` 修复了部分情况下竖向无法滚动的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/182)
4. `F` 修复了多次修改富文本数据时部分内容可能不显示的问题
5. `F` 修复了 [腾讯视频](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#txv-video) 插件可能无法播放的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/265)
6. `F` 修复了 [highlight](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#highlight) 插件没有设置高亮语言时没有应用默认样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/276) by [@fuzui](https://github.com/fuzui)

View File

@ -0,0 +1,498 @@
<template>
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
<slot v-if="!nodes[0]" />
<!-- #ifndef APP-PLUS-NVUE -->
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" />
<!-- #endif -->
<!-- #ifdef APP-PLUS-NVUE -->
<web-view ref="web" src="/uni_modules/mp-html/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
<!-- #endif -->
</view>
</template>
<script>
/**
* mp-html v2.4.2
* @description 富文本组件
* @tutorial https://github.com/jin-yufeng/mp-html
* @property {String} container-style 容器的样式
* @property {String} content 用于渲染的 html 字符串
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
* @property {String} domain 主域名用于拼接链接
* @property {String} error-img 图片出错时的占位图链接
* @property {Boolean} lazy-load 是否开启图片懒加载
* @property {string} loading-img 图片加载过程中的占位图链接
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
* @property {Boolean} preview-img 是否允许图片被点击时自动预览
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
* @property {Boolean | String} selectable 是否开启长按复制
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
* @property {Object} tag-style 标签的默认样式
* @property {Boolean | Number} use-anchor 是否使用锚点链接
* @event {Function} load dom 结构加载完毕时触发
* @event {Function} ready 所有图片加载完毕时触发
* @event {Function} imgtap 图片被点击时触发
* @event {Function} linktap 链接被点击时触发
* @event {Function} play 音视频播放时触发
* @event {Function} error 媒体加载出错时触发
*/
// #ifndef APP-PLUS-NVUE
import node from './node/node'
// #endif
import Parser from './parser'
const plugins=[]
// #ifdef APP-PLUS-NVUE
const dom = weex.requireModule('dom')
// #endif
export default {
name: 'mp-html',
data () {
return {
nodes: [],
// #ifdef APP-PLUS-NVUE
height: 3
// #endif
}
},
props: {
containerStyle: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
copyLink: {
type: [Boolean, String],
default: true
},
domain: String,
errorImg: {
type: String,
default: ''
},
lazyLoad: {
type: [Boolean, String],
default: false
},
loadingImg: {
type: String,
default: ''
},
pauseVideo: {
type: [Boolean, String],
default: true
},
previewImg: {
type: [Boolean, String],
default: true
},
scrollTable: [Boolean, String],
selectable: [Boolean, String],
setTitle: {
type: [Boolean, String],
default: true
},
showImgMenu: {
type: [Boolean, String],
default: true
},
tagStyle: Object,
useAnchor: [Boolean, Number]
},
// #ifdef VUE3
emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
// #endif
// #ifndef APP-PLUS-NVUE
components: {
node
},
// #endif
watch: {
content (content) {
this.setContent(content)
}
},
created () {
this.plugins = []
for (let i = plugins.length; i--;) {
this.plugins.push(new plugins[i](this))
}
},
mounted () {
if (this.content && !this.nodes.length) {
this.setContent(this.content)
}
},
beforeDestroy () {
this._hook('onDetached')
},
methods: {
/**
* @description 将锚点跳转的范围限定在一个 scroll-view
* @param {Object} page scroll-view 所在页面的示例
* @param {String} selector scroll-view 的选择器
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
*/
in (page, selector, scrollTop) {
// #ifndef APP-PLUS-NVUE
if (page && selector && scrollTop) {
this._in = {
page,
selector,
scrollTop
}
}
// #endif
},
/**
* @description 锚点跳转
* @param {String} id 要跳转的锚点 id
* @param {Number} offset 跳转位置的偏移量
* @returns {Promise}
*/
navigateTo (id, offset) {
return new Promise((resolve, reject) => {
if (!this.useAnchor) {
reject(Error('Anchor is disabled'))
return
}
offset = offset || parseInt(this.useAnchor) || 0
// #ifdef APP-PLUS-NVUE
if (!id) {
dom.scrollToElement(this.$refs.web, {
offset
})
resolve()
} else {
this._navigateTo = {
resolve,
reject,
offset
}
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
}
// #endif
// #ifndef APP-PLUS-NVUE
let deep = ' '
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
deep = '>>>'
// #endif
const selector = uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this._in ? this._in.page : this)
// #endif
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
if (this._in) {
selector.select(this._in.selector).scrollOffset()
.select(this._in.selector).boundingClientRect()
} else {
// scroll-view
selector.selectViewport().scrollOffset() //
}
selector.exec(res => {
if (!res[0]) {
reject(Error('Label not found'))
return
}
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
if (this._in) {
// scroll-view
this._in.page[this._in.scrollTop] = scrollTop
} else {
//
uni.pageScrollTo({
scrollTop,
duration: 300
})
}
resolve()
})
// #endif
})
},
/**
* @description 获取文本内容
* @return {String}
*/
getText (nodes) {
let text = '';
(function traversal (nodes) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === 'text') {
text += node.text.replace(/&amp;/g, '&')
} else if (node.name === 'br') {
text += '\n'
} else {
//
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
if (isBlock && text && text[text.length - 1] !== '\n') {
text += '\n'
}
//
if (node.children) {
traversal(node.children)
}
if (isBlock && text[text.length - 1] !== '\n') {
text += '\n'
} else if (node.name === 'td' || node.name === 'th') {
text += '\t'
}
}
}
})(nodes || this.nodes)
return text
},
/**
* @description 获取内容大小和位置
* @return {Promise}
*/
getRect () {
return new Promise((resolve, reject) => {
uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this)
// #endif
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
})
},
/**
* @description 暂停播放媒体
*/
pauseMedia () {
for (let i = (this._videos || []).length; i--;) {
this._videos[i].pause()
}
// #ifdef APP-PLUS
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
// #ifndef APP-PLUS-NVUE
let page = this.$parent
while (!page.$scope) page = page.$parent
page.$scope.$getAppWebview().evalJS(command)
// #endif
// #ifdef APP-PLUS-NVUE
this.$refs.web.evalJs(command)
// #endif
// #endif
},
/**
* @description 设置媒体播放速率
* @param {Number} rate 播放速率
*/
setPlaybackRate (rate) {
this.playbackRate = rate
for (let i = (this._videos || []).length; i--;) {
this._videos[i].playbackRate(rate)
}
// #ifdef APP-PLUS
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate
// #ifndef APP-PLUS-NVUE
let page = this.$parent
while (!page.$scope) page = page.$parent
page.$scope.$getAppWebview().evalJS(command)
// #endif
// #ifdef APP-PLUS-NVUE
this.$refs.web.evalJs(command)
// #endif
// #endif
},
/**
* @description 设置内容
* @param {String} content html 内容
* @param {Boolean} append 是否在尾部追加
*/
setContent (content, append) {
if (!append || !this.imgList) {
this.imgList = []
}
const nodes = new Parser(this).parse(content)
// #ifdef APP-PLUS-NVUE
if (this._ready) {
this._set(nodes, append)
}
// #endif
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
// #ifndef APP-PLUS-NVUE
this._videos = []
this.$nextTick(() => {
this._hook('onLoad')
this.$emit('load')
})
if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {
// 350ms
let height = 0
const callback = rect => {
if (!rect || !rect.height) rect = {}
// 350ms ready
if (rect.height === height) {
this.$emit('ready', rect)
} else {
height = rect.height
setTimeout(() => {
this.getRect().then(callback).catch(callback)
}, 350)
}
}
this.getRect().then(callback).catch(callback)
} else {
//
if (!this.imgList._unloadimgs) {
this.getRect().then(rect => {
this.$emit('ready', rect)
}).catch(() => {
this.$emit('ready', {})
})
}
}
// #endif
},
/**
* @description 调用插件钩子函数
*/
_hook (name) {
for (let i = plugins.length; i--;) {
if (this.plugins[i][name]) {
this.plugins[i][name]()
}
}
},
// #ifdef APP-PLUS-NVUE
/**
* @description 设置内容
*/
_set (nodes, append) {
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
},
/**
* @description 接收到 web-view 消息
*/
_onMessage (e) {
const message = e.detail.data[0]
switch (message.action) {
// web-view
case 'onJSBridgeReady':
this._ready = true
if (this.nodes) {
this._set(this.nodes)
}
break
// dom
case 'onLoad':
this.height = message.height
this._hook('onLoad')
this.$emit('load')
break
//
case 'onReady':
this.getRect().then(res => {
this.$emit('ready', res)
}).catch(() => {
this.$emit('ready', {})
})
break
//
case 'onHeightChange':
this.height = message.height
break
//
case 'onImgTap':
this.$emit('imgtap', message.attrs)
if (this.previewImg) {
uni.previewImage({
current: parseInt(message.attrs.i),
urls: this.imgList
})
}
break
//
case 'onLinkTap': {
const href = message.attrs.href
this.$emit('linktap', message.attrs)
if (href) {
//
if (href[0] === '#') {
if (this.useAnchor) {
dom.scrollToElement(this.$refs.web, {
offset: message.offset
})
}
} else if (href.includes('://')) {
//
if (this.copyLink) {
plus.runtime.openWeb(href)
}
} else {
uni.navigateTo({
url: href,
fail () {
uni.switchTab({
url: href
})
}
})
}
}
break
}
case 'onPlay':
this.$emit('play')
break
//
case 'getOffset':
if (typeof message.offset === 'number') {
dom.scrollToElement(this.$refs.web, {
offset: message.offset + this._navigateTo.offset
})
this._navigateTo.resolve()
} else {
this._navigateTo.reject(Error('Label not found'))
}
break
//
case 'onClick':
this.$emit('tap')
this.$emit('click')
break
//
case 'onError':
this.$emit('error', {
source: message.source,
attrs: message.attrs
})
}
}
// #endif
}
}
</script>
<style>
/* #ifndef APP-PLUS-NVUE */
/* 根节点样式 */
._root {
padding: 1px 0;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}
/* 长按复制 */
._select {
user-select: text;
}
/* #endif */
</style>

View File

@ -0,0 +1,576 @@
<template>
<view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style">
<block v-for="(n, i) in childs" v-bind:key="i">
<!-- 图片 -->
<!-- 占位图 -->
<image v-if="n.name==='img'&&!n.t&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
<!-- 显示图片 -->
<!-- #ifdef H5 || (APP-PLUS && VUE2) -->
<img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- #ifndef H5 || (APP-PLUS && VUE2) -->
<!-- 表格中的图片使用 rich-text 防止大小不正确 -->
<rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style,src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" />
<!-- #endif -->
<!-- #ifndef H5 || APP-PLUS -->
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- #ifdef APP-PLUS && VUE3 -->
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- 文本 -->
<!-- #ifdef MP-WEIXIN -->
<text v-else-if="n.text" :user-select="opts[4]=='force'&&isiOS" decode>{{n.text}}</text>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
<text v-else-if="n.text" decode>{{n.text}}</text>
<!-- #endif -->
<text v-else-if="n.name==='br'">\n</text>
<!-- 链接 -->
<view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
<node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
</view>
<!-- 视频 -->
<!-- #ifdef APP-PLUS -->
<view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" />
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
<!-- #endif -->
<!-- #ifdef H5 || APP-PLUS -->
<iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
<embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" />
<!-- #endif -->
<!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) -->
<!-- 音频 -->
<audio v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
<!-- #endif -->
<view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
<node v-if="n.name==='li'" :childs="n.children" :opts="opts" />
<view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
<node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" />
<block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
<view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
<node :childs="tr.children" :opts="opts" />
</view>
<view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
<view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
<node :childs="td.children" :opts="opts" />
</view>
</view>
</block>
</view>
</view>
<!-- 富文本 -->
<!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
<rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
<!-- #endif -->
<!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="'display:inline;'+n.f" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
<!-- #endif -->
<!-- 继续递归 -->
<view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
<node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" />
</view>
<node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
</block>
</view>
</template>
<script module="handler" lang="wxs">
//
var inlineTags = {
abbr: true,
b: true,
big: true,
code: true,
del: true,
em: true,
i: true,
ins: true,
label: true,
q: true,
small: true,
span: true,
strong: true,
sub: true,
sup: true
}
/**
* @description 判断是否为行内标签
*/
module.exports = {
isInline: function (tagName, style) {
return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1
}
}
</script>
<script>
import node from './node'
export default {
name: 'node',
options: {
// #ifdef MP-WEIXIN
virtualHost: true,
// #endif
// #ifdef MP-TOUTIAO
addGlobalClass: false
// #endif
},
data () {
return {
ctrl: {},
// #ifdef MP-WEIXIN
isiOS: uni.getSystemInfoSync().system.includes('iOS')
// #endif
}
},
props: {
name: String,
attrs: {
type: Object,
default () {
return {}
}
},
childs: Array,
opts: Array
},
components: {
// #ifndef (H5 || APP-PLUS) && VUE3
node
// #endif
},
mounted () {
this.$nextTick(() => {
for (this.root = this.$parent; this.root.$options.name !== 'mp-html'; this.root = this.root.$parent);
})
// #ifdef H5 || APP-PLUS
if (this.opts[0]) {
let i
for (i = this.childs.length; i--;) {
if (this.childs[i].name === 'img') break
}
if (i !== -1) {
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
top: 500,
bottom: 500
})
this.observer.observe('._img', res => {
if (res.intersectionRatio) {
this.$set(this.ctrl, 'load', 1)
this.observer.disconnect()
}
})
}
}
// #endif
},
beforeDestroy () {
// #ifdef H5 || APP-PLUS
if (this.observer) {
this.observer.disconnect()
}
// #endif
},
methods:{
// #ifdef MP-WEIXIN
toJSON () { return this },
// #endif
/**
* @description 播放视频事件
* @param {Event} e
*/
play (e) {
this.root.$emit('play')
// #ifndef APP-PLUS
if (this.root.pauseVideo) {
let flag = false
const id = e.target.id
for (let i = this.root._videos.length; i--;) {
if (this.root._videos[i].id === id) {
flag = true
} else {
this.root._videos[i].pause() //
}
}
//
if (!flag) {
const ctx = uni.createVideoContext(id
// #ifndef MP-BAIDU
, this
// #endif
)
ctx.id = id
if (this.root.playbackRate) {
ctx.playbackRate(this.root.playbackRate)
}
this.root._videos.push(ctx)
}
}
// #endif
},
/**
* @description 图片点击事件
* @param {Event} e
*/
imgTap (e) {
const node = this.childs[e.currentTarget.dataset.i]
if (node.a) {
this.linkTap(node.a)
return
}
if (node.attrs.ignore) return
// #ifdef H5 || APP-PLUS
node.attrs.src = node.attrs.src || node.attrs['data-src']
// #endif
this.root.$emit('imgtap', node.attrs)
//
if (this.root.previewImg) {
uni.previewImage({
// #ifdef MP-WEIXIN
showmenu: this.root.showImgMenu,
// #endif
// #ifdef MP-ALIPAY
enablesavephoto: this.root.showImgMenu,
enableShowPhotoDownload: this.root.showImgMenu,
// #endif
current: parseInt(node.attrs.i),
urls: this.root.imgList
})
}
},
/**
* @description 图片长按
*/
imgLongTap (e) {
// #ifdef APP-PLUS
const attrs = this.childs[e.currentTarget.dataset.i].attrs
if (this.opts[3] && !attrs.ignore) {
uni.showActionSheet({
itemList: ['保存图片'],
success: () => {
const save = path => {
uni.saveImageToPhotosAlbum({
filePath: path,
success () {
uni.showToast({
title: '保存成功'
})
}
})
}
if (this.root.imgList[attrs.i].startsWith('http')) {
uni.downloadFile({
url: this.root.imgList[attrs.i],
success: res => save(res.tempFilePath)
})
} else {
save(this.root.imgList[attrs.i])
}
}
})
}
// #endif
},
/**
* @description 图片加载完成事件
* @param {Event} e
*/
imgLoad (e) {
const i = e.currentTarget.dataset.i
/* #ifndef H5 || (APP-PLUS && VUE2) */
if (!this.childs[i].w) {
//
this.$set(this.ctrl, i, e.detail.width)
} else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {
//
this.$set(this.ctrl, i, 1)
}
this.checkReady()
},
/**
* @description 检查是否所有图片加载完毕
*/
checkReady () {
if (this.root && !this.root.lazyLoad) {
this.root._unloadimgs -= 1
if (!this.root._unloadimgs) {
setTimeout(() => {
this.root.getRect().then(rect => {
this.root.$emit('ready', rect)
}).catch(() => {
this.root.$emit('ready', {})
})
}, 350)
}
}
},
/**
* @description 链接点击事件
* @param {Event} e
*/
linkTap (e) {
const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
const attrs = node.attrs || e
const href = attrs.href
this.root.$emit('linktap', Object.assign({
innerText: this.root.getText(node.children || []) //
}, attrs))
if (href) {
if (href[0] === '#') {
//
this.root.navigateTo(href.substring(1)).catch(() => { })
} else if (href.split('?')[0].includes('://')) {
//
if (this.root.copyLink) {
// #ifdef H5
window.open(href)
// #endif
// #ifdef MP
uni.setClipboardData({
data: href,
success: () =>
uni.showToast({
title: '链接已复制'
})
})
// #endif
// #ifdef APP-PLUS
plus.runtime.openWeb(href)
// #endif
}
} else {
//
uni.navigateTo({
url: href,
fail () {
uni.switchTab({
url: href,
fail () { }
})
}
})
}
}
},
/**
* @description 错误事件
* @param {Event} e
*/
mediaError (e) {
const i = e.currentTarget.dataset.i
const node = this.childs[i]
//
if (node.name === 'video' || node.name === 'audio') {
let index = (this.ctrl[i] || 0) + 1
if (index > node.src.length) {
index = 0
}
if (index < node.src.length) {
this.$set(this.ctrl, i, index)
return
}
} else if (node.name === 'img') {
// #ifdef H5 && VUE3
if (this.opts[0] && !this.ctrl.load) return
// #endif
//
if (this.opts[2]) {
this.$set(this.ctrl, i, -1)
}
this.checkReady()
}
if (this.root) {
this.root.$emit('error', {
source: node.name,
attrs: node.attrs,
// #ifndef H5 && VUE3
errMsg: e.detail.errMsg
// #endif
})
}
}
}
}
</script>
<style>
/* a 标签默认效果 */
._a {
padding: 1.5px 0 1.5px 0;
color: #366092;
word-break: break-all;
}
/* a 标签点击态效果 */
._hover {
text-decoration: underline;
opacity: 0.7;
}
/* 图片默认效果 */
._img {
max-width: 100%;
-webkit-touch-callout: none;
}
/* 内部样式 */
._block {
display: block;
}
._b,
._strong {
font-weight: bold;
}
._code {
font-family: monospace;
}
._del {
text-decoration: line-through;
}
._em,
._i {
font-style: italic;
}
._h1 {
font-size: 2em;
}
._h2 {
font-size: 1.5em;
}
._h3 {
font-size: 1.17em;
}
._h5 {
font-size: 0.83em;
}
._h6 {
font-size: 0.67em;
}
._h1,
._h2,
._h3,
._h4,
._h5,
._h6 {
display: block;
font-weight: bold;
}
._image {
height: 1px;
}
._ins {
text-decoration: underline;
}
._li {
display: list-item;
}
._ol {
list-style-type: decimal;
}
._ol,
._ul {
display: block;
padding-left: 40px;
margin: 1em 0;
}
._q::before {
content: '"';
}
._q::after {
content: '"';
}
._sub {
font-size: smaller;
vertical-align: sub;
}
._sup {
font-size: smaller;
vertical-align: super;
}
._thead,
._tbody,
._tfoot {
display: table-row-group;
}
._tr {
display: table-row;
}
._td,
._th {
display: table-cell;
vertical-align: middle;
}
._th {
font-weight: bold;
text-align: center;
}
._ul {
list-style-type: disc;
}
._ul ._ul {
margin: 0;
list-style-type: circle;
}
._ul ._ul ._ul {
list-style-type: square;
}
._abbr,
._b,
._code,
._del,
._em,
._i,
._ins,
._label,
._q,
._span,
._strong,
._sub,
._sup {
display: inline;
}
/* #ifdef APP-PLUS */
._video {
width: 300px;
height: 225px;
}
/* #endif */
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,76 @@
{
"id": "mp-html",
"displayName": "mp-html 富文本组件【全端支持支持编辑、latex等扩展】",
"version": "v2.4.2",
"description": "一个强大的富文本组件,高效轻量,功能丰富",
"keywords": [
"富文本",
"编辑器",
"html",
"rich-text",
"editor"
],
"repository": "https://github.com/jin-yufeng/mp-html",
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/mp-html",
"type": "component-vue"
},
"uni_modules": {
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1 @@
"use strict";function t(t){for(var e=Object.create(null),n=t.attributes.length;n--;)e[t.attributes[n].name]=t.attributes[n].value;return e}function e(){a[1]&&(this.src=a[1],this.onerror=null),this.onclick=null,this.ontouchstart=null,uni.postMessage({data:{action:"onError",source:"img",attrs:t(this)}})}function n(){window.unloadimgs-=1,0===window.unloadimgs&&uni.postMessage({data:{action:"onReady"}})}function o(r,s,c){for(var d=0;d<r.length;d++)!function(d){var u=r[d],l=void 0;if(u.type&&"node"!==u.type)l=document.createTextNode(u.text.replace(/&amp;/g,"&"));else{var g=u.name;"svg"===g&&(c="http://www.w3.org/2000/svg"),"html"!==g&&"body"!==g||(g="div"),l=c?document.createElementNS(c,g):document.createElement(g);for(var p in u.attrs)l.setAttribute(p,u.attrs[p]);if(u.children&&o(u.children,l,c),"img"===g){if(window.unloadimgs+=1,l.onload=n,l.onerror=n,!l.src&&l.getAttribute("data-src")&&(l.src=l.getAttribute("data-src")),u.attrs.ignore||(l.onclick=function(e){e.stopPropagation(),uni.postMessage({data:{action:"onImgTap",attrs:t(this)}})}),a[2]){var h=new Image;h.src=l.src,l.src=a[2],h.onload=function(){l.src=this.src},h.onerror=function(){l.onerror()}}l.onerror=e}else if("a"===g)l.addEventListener("click",function(e){e.stopPropagation(),e.preventDefault();var n,o=this.getAttribute("href");o&&"#"===o[0]&&(n=(document.getElementById(o.substr(1))||{}).offsetTop),uni.postMessage({data:{action:"onLinkTap",attrs:t(this),offset:n}})},!0);else if("video"===g||"audio"===g)i.push(l),u.attrs.autoplay||u.attrs.controls||l.setAttribute("controls","true"),l.onplay=function(){if(uni.postMessage({data:{action:"onPlay"}}),a[3])for(var t=0;t<i.length;t++)i[t]!==this&&i[t].pause()},l.onerror=function(){uni.postMessage({data:{action:"onError",source:g,attrs:t(this)}})};else if("table"===g&&a[4]&&!l.style.cssText.includes("inline")){var f=document.createElement("div");f.style.overflow="auto",f.appendChild(l),l=f}else"svg"===g&&(c=void 0)}s.appendChild(l)}(d)}document.addEventListener("UniAppJSBridgeReady",function(){document.body.onclick=function(){return uni.postMessage({data:{action:"onClick"}})},uni.postMessage({data:{action:"onJSBridgeReady"}})});var a,i=[];window.setContent=function(t,e,n){var r=document.getElementById("content");e[0]&&(document.body.style.cssText=e[0]),e[5]||(r.style.userSelect="none"),n||(r.innerHTML="",i=[]),a=e,window.unloadimgs=0;var s=document.createDocumentFragment();o(t,s),r.appendChild(s);var c=r.scrollHeight;uni.postMessage({data:{action:"onLoad",height:c}}),window.unloadimgs||uni.postMessage({data:{action:"onReady",height:c}}),clearInterval(window.timer),window.timer=setInterval(function(){r.scrollHeight!==c&&(c=r.scrollHeight,uni.postMessage({data:{action:"onHeightChange",height:c}}))},350)},window.onunload=function(){clearInterval(window.timer)};

View File

@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).uni=n()}(this,(function(){"use strict";try{var e={};Object.defineProperty(e,"passive",{get:function(){!0}}),window.addEventListener("test-passive",null,e)}catch(e){}var n=Object.prototype.hasOwnProperty;function t(e,t){return n.call(e,t)}var i=[],a=function(e,n){var t={options:{timestamp:+new Date},name:e,arg:n};if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){if("postMessage"===e){var a={data:[n]};return window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(a):window.__dcloud_weex_.postMessage(JSON.stringify(a))}var o={type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessageToService(o):window.__dcloud_weex_.postMessageToService(JSON.stringify(o))}if(!window.plus)return window.parent.postMessage({type:"WEB_INVOKE_APPSERVICE",data:t,pageId:""},"*");if(0===i.length){var r=plus.webview.currentWebview();if(!r)throw new Error("plus.webview.currentWebview() is undefined");var d=r.parent(),s="";s=d?d.id:r.id,i.push(s)}if(plus.webview.getWebviewById("__uniapp__service"))plus.webview.postMessageToUniNView({type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}},"__uniapp__service");else{var w=JSON.stringify(t);plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat("WEB_INVOKE_APPSERVICE",'",').concat(w,",").concat(JSON.stringify(i),");"))}},o={navigateTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("navigateTo",{url:encodeURI(n)})},navigateBack:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.delta;a("navigateBack",{delta:parseInt(n)||1})},switchTab:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("switchTab",{url:encodeURI(n)})},reLaunch:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("reLaunch",{url:encodeURI(n)})},redirectTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("redirectTo",{url:encodeURI(n)})},getEnv:function(e){window.plus?e({plus:!0}):e({h5:!0})},postMessage:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};a("postMessage",e.data||{})}},r=/uni-app/i.test(navigator.userAgent),d=/Html5Plus/i.test(navigator.userAgent),s=/complete|loaded|interactive/;var w=window.my&&navigator.userAgent.indexOf("AlipayClient")>-1;var u=window.swan&&window.swan.webView&&/swan/i.test(navigator.userAgent);var c=window.qq&&window.qq.miniProgram&&/QQ/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var g=window.tt&&window.tt.miniProgram&&/toutiaomicroapp/i.test(navigator.userAgent);var v=window.wx&&window.wx.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var p=window.qa&&/quickapp/i.test(navigator.userAgent);for(var l,_=function(){window.UniAppJSBridge=!0,document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady",{bubbles:!0,cancelable:!0}))},f=[function(e){if(r||d)return window.__dcloud_weex_postMessage||window.__dcloud_weex_?document.addEventListener("DOMContentLoaded",e):window.plus&&s.test(document.readyState)?setTimeout(e,0):document.addEventListener("plusready",e),o},function(e){if(v)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.wx.miniProgram},function(e){if(c)return window.QQJSBridge&&window.QQJSBridge.invoke?setTimeout(e,0):document.addEventListener("QQJSBridgeReady",e),window.qq.miniProgram},function(e){if(w){document.addEventListener("DOMContentLoaded",e);var n=window.my;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(u)return document.addEventListener("DOMContentLoaded",e),window.swan.webView},function(e){if(g)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(p){window.QaJSBridge&&window.QaJSBridge.invoke?setTimeout(e,0):document.addEventListener("QaJSBridgeReady",e);var n=window.qa;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){return document.addEventListener("DOMContentLoaded",e),o}],m=0;m<f.length&&!(l=f[m](_));m++);l||(l={});var E="undefined"!=typeof uni?uni:{};if(!E.navigateTo)for(var b in l)t(l,b)&&(E[b]=l[b]);return E.webView=l,E}));

View File

@ -0,0 +1 @@
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow-x:scroll;overflow-y:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}</style></head><body><div id="content" style="overflow:hidden"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body>

View File

@ -0,0 +1,26 @@
## 1.3.12021-12-20
- 修复 在vue页面下略缩图显示不正常的bug
## 1.3.02021-11-19
- 重构插槽的用法 header 替换为 title
- 新增 actions 插槽
- 新增 cover 封面图属性和插槽
- 新增 padding 内容默认内边距离
- 新增 margin 卡片默认外边距离
- 新增 spacing 卡片默认内边距
- 新增 shadow 卡片阴影属性
- 取消 mode 属性,可使用组合插槽代替
- 取消 note 属性 使用actions插槽代替
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)
## 1.2.12021-07-30
- 优化 vue3下事件警告的问题
## 1.2.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.82021-07-01
- 优化 图文卡片无图片加载时,提供占位图标
- 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持)
- 修复 thumbnail 不存在仍然占位的 bug
## 1.1.72021-05-12
- 新增 组件示例地址
## 1.1.62021-02-04
- 调整为uni_modules目录规范

View File

@ -0,0 +1,270 @@
<template>
<view class="uni-card" :class="{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':border}"
:style="{'margin':isFull?0:margin,'padding':spacing,'box-shadow':isShadow?shadow:''}">
<!-- 封面 -->
<slot name="cover">
<view v-if="cover" class="uni-card__cover">
<image class="uni-card__cover-image" mode="widthFix" @click="onClick('cover')" :src="cover"></image>
</view>
</slot>
<slot name="title">
<view v-if="title || extra" class="uni-card__header">
<!-- 卡片标题 -->
<view class="uni-card__header-box" @click="onClick('title')">
<view v-if="thumbnail" class="uni-card__header-avatar">
<image class="uni-card__header-avatar-image" :src="thumbnail" mode="aspectFit" />
</view>
<view class="uni-card__header-content">
<text class="uni-card__header-content-title uni-ellipsis">{{ title }}</text>
<text v-if="title&&subTitle"
class="uni-card__header-content-subtitle uni-ellipsis">{{ subTitle }}</text>
</view>
</view>
<view class="uni-card__header-extra" @click="onClick('extra')">
<text class="uni-card__header-extra-text">{{ extra }}</text>
</view>
</view>
</slot>
<!-- 卡片内容 -->
<view class="uni-card__content" :style="{padding:padding}" @click="onClick('content')">
<slot></slot>
</view>
<view class="uni-card__actions" @click="onClick('actions')">
<slot name="actions"></slot>
</view>
</view>
</template>
<script>
/**
* Card 卡片
* @description 卡片视图组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=22
* @property {String} title 标题文字
* @property {String} subTitle 副标题
* @property {Number} padding 内容内边距
* @property {Number} margin 卡片外边距
* @property {Number} spacing 卡片内边距
* @property {String} extra 标题额外信息
* @property {String} cover 封面图本地路径需要引入
* @property {String} thumbnail 标题左侧缩略图
* @property {Boolean} is-full = [true | false] 卡片内容是否通栏 true 时将去除padding值
* @property {Boolean} is-shadow = [true | false] 卡片内容是否开启阴影
* @property {String} shadow 卡片阴影
* @property {Boolean} border 卡片边框
* @event {Function} click 点击 Card 触发事件
*/
export default {
name: 'UniCard',
emits: ['click'],
props: {
title: {
type: String,
default: ''
},
subTitle: {
type: String,
default: ''
},
padding: {
type: String,
default: '10px'
},
margin: {
type: String,
default: '15px'
},
spacing: {
type: String,
default: '0 10px'
},
extra: {
type: String,
default: ''
},
cover: {
type: String,
default: ''
},
thumbnail: {
type: String,
default: ''
},
isFull: {
//
type: Boolean,
default: false
},
isShadow: {
//
type: Boolean,
default: true
},
shadow: {
type: String,
default: '0px 0px 3px 1px rgba(0, 0, 0, 0.08)'
},
border: {
type: Boolean,
default: true
}
},
methods: {
onClick(type) {
this.$emit('click', type)
}
}
}
</script>
<style lang="scss">
$uni-border-3: #EBEEF5 !default;
$uni-shadow-base:0 0px 6px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
$uni-main-color: #3a3a3a !default;
$uni-base-color: #6a6a6a !default;
$uni-secondary-color: #909399 !default;
$uni-spacing-sm: 8px !default;
$uni-border-color:$uni-border-3;
$uni-shadow: $uni-shadow-base;
$uni-card-title: 15px;
$uni-cart-title-color:$uni-main-color;
$uni-card-subtitle: 12px;
$uni-cart-subtitle-color:$uni-secondary-color;
$uni-card-spacing: 10px;
$uni-card-content-color: $uni-base-color;
.uni-card {
margin: $uni-card-spacing;
padding: 0 $uni-spacing-sm;
border-radius: 4px;
overflow: hidden;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
background-color: #fff;
flex: 1;
.uni-card__cover {
position: relative;
margin-top: $uni-card-spacing;
flex-direction: row;
overflow: hidden;
border-radius: 4px;
.uni-card__cover-image {
flex: 1;
// width: 100%;
/* #ifndef APP-PLUS */
vertical-align: middle;
/* #endif */
}
}
.uni-card__header {
display: flex;
border-bottom: 1px $uni-border-color solid;
flex-direction: row;
align-items: center;
padding: $uni-card-spacing;
overflow: hidden;
.uni-card__header-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
overflow: hidden;
}
.uni-card__header-avatar {
width: 40px;
height: 40px;
overflow: hidden;
border-radius: 5px;
margin-right: $uni-card-spacing;
.uni-card__header-avatar-image {
flex: 1;
width: 40px;
height: 40px;
}
}
.uni-card__header-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
flex: 1;
// height: 40px;
overflow: hidden;
.uni-card__header-content-title {
font-size: $uni-card-title;
color: $uni-cart-title-color;
// line-height: 22px;
}
.uni-card__header-content-subtitle {
font-size: $uni-card-subtitle;
margin-top: 5px;
color: $uni-cart-subtitle-color;
}
}
.uni-card__header-extra {
line-height: 12px;
.uni-card__header-extra-text {
font-size: 12px;
color: $uni-cart-subtitle-color;
}
}
}
.uni-card__content {
padding: $uni-card-spacing;
font-size: 14px;
color: $uni-card-content-color;
line-height: 22px;
}
.uni-card__actions {
font-size: 12px;
}
}
.uni-card--border {
border: 1px solid $uni-border-color;
}
.uni-card--shadow {
position: relative;
/* #ifndef APP-NVUE */
box-shadow: $uni-shadow;
/* #endif */
}
.uni-card--full {
margin: 0;
border-left-width: 0;
border-left-width: 0;
border-radius: 0;
}
/* #ifndef APP-NVUE */
.uni-card--full:after {
border-radius: 0;
}
/* #endif */
.uni-ellipsis {
/* #ifndef APP-NVUE */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
/* #endif */
}
</style>

View File

@ -0,0 +1,90 @@
{
"id": "uni-card",
"displayName": "uni-card 卡片",
"version": "1.3.1",
"description": "Card 组件,提供常见的卡片样式。",
"keywords": [
"uni-ui",
"uniui",
"card",
"",
"卡片"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-icons",
"uni-scss"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,12 @@
## Card 卡片
> **组件名uni-card**
> 代码块: `uCard`
卡片视图组件。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

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

View File

@ -0,0 +1,357 @@
## 2.0.342022-09-25
# uView2.0重磅发布,利剑出鞘,一统江湖
1. `u-input`、`u-textarea`增加`ignoreCompositionEvent`属性
2. 修复`route`方法调用可能报错的问题
3. 修复`u-no-network`组件`z-index`无效的问题
4. 修复`textarea`组件在h5上confirmType=""报错的问题
5. `u-rate`适配`nvue`
6. 优化验证手机号码的正则表达式(根据工信部发布的《电信网编号计划2017年版》进行修改。)
7. `form-item`添加`labelPosition`属性
8. `u-calendar`修复`maxDate`设置为当前日期并且当前时间大于0800时无法显示日期列表的问题 (#724)
9. `u-radio`增加一个默认插槽用于自定义修改label内容 (#680)
10. 修复`timeFormat`函数在safari重的兼容性问题 (#664)
## 2.0.332022-06-17
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复`loadmore`组件`lineColor`类型错误问题
2. 修复`u-parse`组件`imgtap`、`linktap`不生效问题
## 2.0.322022-06-16
# uView2.0重磅发布,利剑出鞘,一统江湖
1. `u-loadmore`新增自定义颜色、虚/实线
2. 修复`u-swiper-action`组件部分平台不能上下滑动的问题
3. 修复`u-list`回弹问题
4. 修复`notice-bar`组件动画在低端安卓机可能会抖动的问题
5. `u-loading-page`添加控制图标大小的属性`iconSize`
6. 修复`u-tooltip`组件`color`参数不生效的问题
7. 修复`u--input`组件使用`blur`事件输出为`undefined`的bug
8. `u-code-input`组件新增键盘弹起时,是否自动上推页面参数`adjustPosition`
9. 修复`image`组件`load`事件无回调对象问题
10. 修复`button`组件`loadingSize`设置无效问题
10. 其他修复
## 2.0.312022-04-19
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复`upload`在`vue`页面上传成功后没有成功标志的问题
2. 解决演示项目中微信小程序模拟上传图片一直出于上传中问题
3. 修复`u-code-input`组件在`nvue`页面编译到`app`平台上光标异常问题(`app`去除此功能)
4. 修复`actionSheet`组件标题关闭按钮点击事件名称错误的问题
5. 其他修复
## 2.0.302022-04-04
# uView2.0重磅发布,利剑出鞘,一统江湖
1. `u-rate`增加`readonly`属性
2. `tabs`滑块支持设置背景图片
3. 修复`u-subsection` `mode`为`subsection`时,滑块样式不正确的问题
4. `u-code-input`添加光标效果动画
5. 修复`popup`的`open`事件不触发
6. 修复`u-flex-column`无效的问题
7. 修复`u-datetime-picker`索引在特定场合异常问题
8. 修复`u-datetime-picker`最小时间字符串模板错误问题
9. `u-swiper`添加`m3u8`验证
10. `u-swiper`修改判断image和video逻辑
11. 修复`swiper`无法使用本地图片问题,增加`type`参数
12. 修复`u-row-notice`格式错误问题
13. 修复`u-switch`组件当`unit`为`rpx`时,`nodeStyle`消失的问题
14. 修复`datetime-picker`组件`showToolbar`与`visibleItemCount`属性无效的问题
15. 修复`upload`组件条件编译位置判断错误,导致`previewImage`属性设置为`false`时,整个组件都会被隐藏的问题
16. 修复`u-checkbox-group`设置`shape`属性无效的问题
17. 修复`u-upload`的`capture`传入字符串的时候不生效的问题
18. 修复`u-action-sheet`组件,关闭事件逻辑错误的问题
19. 修复`u-list`触顶事件的触发错误的问题
20. 修复`u-text`只有手机号可拨打的问题
21. 修复`u-textarea`不能换行的问题
22. 其他修复
## 2.0.292022-03-13
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复`u--text`组件设置`decoration`属性未生效的问题
2. 修复`u-datetime-picker`使用`formatter`后返回值不正确
3. 修复`u-datetime-picker` `intercept` 可能为undefined
4. 修复已设置单位 uni..config.unit = 'rpx'时,线型指示器 `transform` 的位置翻倍,导致指示器超出宽度
5. 修复mixin中bem方法生成的类名在支付宝和字节小程序中失效
6. 修复默认值传值为空的时候,打开`u-datetime-picker`报错不能选中第一列时间的bug
7. 修复`u-datetime-picker`使用`formatter`后返回值不正确
8. 修复`u-image`组件`loading`无效果的问题
9. 修复`config.unit`属性设为`rpx`时,导航栏占用高度不足导致塌陷的问题
10. 修复`u-datetime-picker`组件`itemHeight`无效问题
11. 其他修复
## 2.0.282022-02-22
# uView2.0重磅发布,利剑出鞘,一统江湖
1. search组件新增searchIconSize属性
2. 兼容Safari/Webkit中传入时间格式如2022-02-17 12:00:56
3. 修复text value.js 判断日期出format错误问题
4. priceFormat格式化金额出现精度错误
5. priceFormat在部分情况下出现精度损失问题
6. 优化表单rules提示
7. 修复avatar组件src为空时展示状态不对
8. 其他修复
## 2.0.272022-01-28
# uView2.0重磅发布,利剑出鞘,一统江湖
1.样式修复
## 2.0.262022-01-28
# uView2.0重磅发布,利剑出鞘,一统江湖
1.样式修复
## 2.0.252022-01-27
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复text组件mode=price时可能会导致精度错误的问题
2. 添加$u.setConfig()方法可设置uView内置的config, props, zIndex, color属性详见[修改uView内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
3. 优化form组件在errorType=toast时如果输入错误页面会有抖动的问题
4. 修复$u.addUnit()对配置默认单位可能无效的问题
## 2.0.242022-01-25
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复swiper在current指定非0时缩放有误
2. 修复u-icon添加stop属性的时候报错
3. 优化遗留的通过正则判断rpx单位的问题
4. 优化Layout布局 vue使用gutter时会超出固定区域
5. 优化search组件高度单位问题rpx -> px
6. 修复u-image slot 加载和错误的图片失去了高度
7. 修复u-index-list中footer插槽与header插槽存在性判断错误
8. 修复部分机型下u-popup关闭时会闪烁
9. 修复u-image在nvue-app下失去宽高
10. 修复u-popup运行报错
11. 修复u-tooltip报错
12. 修复box-sizing在app下的警告
13. 修复u-navbar在小程序中报运行时错误
14. 其他修复
## 2.0.232022-01-24
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复image组件在hx3.3.9的nvue下可能会显示异常的问题
2. 修复col组件gutter参数带rpx单位处理不正确的问题
3. 修复text组件单行时无法显示省略号的问题
4. navbar添加titleStyle参数
5. 升级到hx3.3.9可消除nvue下控制台样式警告的问题
## 2.0.222022-01-19
# uView2.0重磅发布,利剑出鞘,一统江湖
1. $u.page()方法优化,避免在特殊场景可能报错的问题
2. picker组件添加immediateChange参数
3. 新增$u.pages()方法
## 2.0.212022-01-19
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 优化form组件在用户设置rules的时候提示用户model必传
2. 优化遗留的通过正则判断rpx单位的问题
3. 修复微信小程序环境中tabbar组件开启safeAreaInsetBottom属性后placeholder高度填充不正确
4. 修复swiper在current指定非0时缩放有误
5. 修复u-icon添加stop属性的时候报错
6. 修复upload组件在accept=all的时候没有作用
7. 修复在text组件mode为phone时call属性无效的问题
8. 处理u-form clearValidate方法
9. 其他修复
## 2.0.202022-01-14
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复calendar默认会选择一个日期如果直接点确定的话无法取到值的问题
2. 修复Slider缺少disabled props 还有注释
3. 修复u-notice-bar点击事件无法拿到index索引值的问题
4. 修复u-collapse-item在vue文件下app端自定义插槽不生效的问题
5. 优化头像为空时显示默认头像
6. 修复图片地址赋值后判断加载状态为完成问题
7. 修复日历滚动到默认日期月份区域
8. search组件暴露点击左边icon事件
9. 修复u-form clearValidate方法不生效
10. upload h5端增加返回文件参数文件的name参数
11. 处理upload选择文件后url为blob类型无法预览的问题
12. u-code-input 修复输入框没有往左移出一半屏幕
13. 修复Upload上传 disabled为true时控制台报hoverClass类型错误
14. 临时处理ios app下grid点击坍塌问题
15. 其他修复
## 2.0.192021-12-29
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 优化微信小程序包体积可在微信中预览请升级HbuilderX3.3.4,同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码”
2. 优化微信小程序setData性能处理某些方法如$u.route()无法在模板中使用的问题
3. navbar添加autoBack参数
4. 允许avatar组件的事件冒泡
5. 修复cell组件报错问题
6. 其他修复
## 2.0.182021-12-28
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复app端编译报错问题
2. 重新处理微信小程序端setData过大的性能问题
3. 修复边框问题
4. 修复最大最小月份不大于0则没有数据出现的问题
5. 修复SwipeAction微信小程序端无法上下滑动问题
6. 修复input的placeholder在小程序端默认显示为true问题
7. 修复divider组件click事件无效问题
8. 修复u-code-input maxlength 属性值为 String 类型时显示异常
9. 修复当 grid只有 1到2时 在小程序端algin设置无效的问题
10. 处理form-item的label为top时取消错误提示的左边距
11. 其他修复
## 2.0.172021-12-26
## uView正在参与开源中国的“年度最佳项目”评选之前投过票的现在也可以投票恳请同学们投一票[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 解决HBuilderX3.3.3.20211225版本导致的样式问题
2. calendar日历添加monthNum参数
3. navbar添加center slot
## 2.0.162021-12-25
## uView正在参与开源中国的“年度最佳项目”评选之前投过票的现在也可以投票恳请同学们投一票[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 解决微信小程序setData性能问题
2. 修复count-down组件change事件不触发问题
## 2.0.152021-12-21
## uView正在参与开源中国的“年度最佳项目”评选之前投过票的现在也可以投票恳请同学们投一票[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复Cell单元格titleWidth无效
2. 修复cheakbox组件ischecked不更新
3. 修复keyboard是否显示"."按键默认值问题
4. 修复number-keyboard是否显示键盘的"."符号问题
5. 修复Input输入框 readonly无效
6. 修复u-avatar 导致打包app、H5时候报错问题
7. 修复Upload上传deletable无效
8. 修复upload当设置maxSize时无效的问题
9. 修复tabs lineWidth传入带单位的字符串的时候偏移量计算错误问题
10. 修复rate组件在有padding的view内显示的星星位置和可触摸区域不匹配无法正常选中星星
## 2.0.132021-12-14
## [点击加群交流反馈364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复配置默认单位为rpx可能会导致自定义导航栏高度异常的问题
## 2.0.122021-12-14
## [点击加群交流反馈364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复tabs组件在vue环境下划线消失的问题
2. 修复upload组件在安卓小程序无法选择视频的问题
3. 添加uni.$u.config.unit配置用于配置参数默认单位详见[默认单位配置](https://www.uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
4. 修复textarea组件在没绑定v-model时字符统计不生效问题
5. 修复nvue下控制是否出现滚动条失效问题
## 2.0.112021-12-13
## [点击加群交流反馈364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. text组件align参数无效的问题
2. subsection组件添加keyName参数
3. upload组件无法判断[Object file]类型的问题
4. 处理notify层级过低问题
5. codeInput组件添加disabledDot参数
6. 处理actionSheet组件round参数无效的问题
7. calendar组件添加round参数用于控制圆角值
8. 处理swipeAction组件在vue环境下默认被打开的问题
9. button组件的throttleTime节流参数无效的问题
10. 解决u-notify手动关闭方法close()无效的问题
11. input组件readonly不生效问题
12. tag组件type参数为info不生效问题
## 2.0.102021-12-08
## [点击加群交流反馈364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复button sendMessagePath属性不生效
2. 修复DatetimePicker选择器title无效
3. 修复u-toast设置loading=true不生效
4. 修复u-text金额模式传0报错
5. 修复u-toast组件的icon属性配置不生效
6. button的icon在特殊场景下的颜色优化
7. IndexList优化增加#
## 2.0.92021-12-01
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 优化swiper的height支持100%值(仅vue有效)修复嵌入视频时click事件无法触发的问题
2. 优化tabs组件对list值为空的判断或者动态变化list时重新计算相关尺寸的问题
3. 优化datetime-picker组件逻辑让其后续打开的默认值为上一次的选中值需要通过v-model绑定值才有效
4. 修复upload内嵌在其他组件中选择图片可能不会换行的问题
## 2.0.82021-12-01
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复toast的position参数无效问题
2. 处理input在ios nvue上无法获得焦点的问题
3. avatar-group组件添加extraValue参数让剩余展示数量可手动控制
4. tabs组件添加keyName参数用于配置从对象中读取的键名
5. 处理text组件名字脱敏默认配置无效的问题
6. 处理picker组件item文本太长换行问题
## 2.0.72021-11-30
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复radio和checkbox动态改变v-model无效的问题。
2. 优化form规则validator在微信小程序用法
3. 修复backtop组件mode参数在微信小程序无效的问题
4. 处理Album的previewFullImage属性无效的问题
5. 处理u-datetime-picker组件mode='time'在选择改变时间时,控制台报错的问题
## 2.0.62021-11-27
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 处理tag组件在vue下边框无效的问题。
2. 处理popup组件圆角参数可能无效的问题。
3. 处理tabs组件lineColor参数可能无效的问题。
4. propgress组件在值很小时显示异常的问题。
## 2.0.52021-11-25
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. calendar在vue下显示异常问题。
2. form组件labelPosition和errorType参数无效的问题
3. input组件inputAlign无效的问题
4. 其他一些修复
## 2.0.42021-11-23
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
0. input组件缺失@confirm事件以及subfix和prefix无效问题
1. component.scss文件样式在vue下干扰全局布局问题
2. 修复subsection在vue环境下表现异常的问题
3. tag组件的bgColor等参数无效的问题
4. upload组件不换行的问题
5. 其他的一些修复处理
## 2.0.32021-11-16
## [点击加群交流反馈1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. uView2.0已实现全面兼容nvue
2. uView2.0对1.x进行了架构重构细节和性能都有极大提升
3. 目前uView2.0为公测阶段,相关细节可能会有变动
4. 我们写了一份与1.x的对比指南详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
5. 处理modal的confirm回调事件拼写错误问题
6. 处理input组件@input事件参数错误问题
7. 其他一些修复
## 2.0.22021-11-16
## [点击加群交流反馈1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. uView2.0已实现全面兼容nvue
2. uView2.0对1.x进行了架构重构细节和性能都有极大提升
3. 目前uView2.0为公测阶段,相关细节可能会有变动
4. 我们写了一份与1.x的对比指南详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
5. 修复input组件formatter参数缺失问题
6. 优化loading-icon组件的scss写法问题防止不兼容新版本scss
## 2.0.0(2020-11-15)
## [点击加群交流反馈1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. uView2.0已实现全面兼容nvue
2. uView2.0对1.x进行了架构重构细节和性能都有极大提升
3. 目前uView2.0为公测阶段,相关细节可能会有变动
4. 我们写了一份与1.x的对比指南详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
5. 修复input组件formatter参数缺失问题

View File

@ -0,0 +1,78 @@
<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'
export default {
// #ifdef MP-WEIXIN
name: 'u-form',
// #endif
// #ifndef MP-WEIXIN
name: 'u--form',
// #endif
mixins: [uni.$u.mpMixin, props, uni.$u.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,47 @@
<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';
export default {
name: 'u--image',
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
components: {
uvImage
},
}
</script>

View File

@ -0,0 +1,73 @@
<template>
<uvInput
:value="value"
: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"
@focus="$emit('focus')"
@blur="e => $emit('blur', e)"
@keyboardheightchange="$emit('keyboardheightchange')"
@change="e => $emit('change', e)"
@input="e => $emit('input', e)"
@confirm="e => $emit('confirm', e)"
@clear="$emit('clear')"
@click="$emit('click')"
>
<!-- #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'
export default {
name: 'u--input',
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
components: {
uvInput
},
}
</script>

View File

@ -0,0 +1,44 @@
<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"
@click="$emit('click')"
></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";
export default {
name: "u--text",
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
components: {
uvText,
},
};
</script>

View File

@ -0,0 +1,48 @@
<template>
<uvTextarea
:value="value"
: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"
@focus="e => $emit('focus')"
@blur="e => $emit('blur')"
@linechange="e => $emit('linechange', e)"
@confirm="e => $emit('confirm')"
@input="e => $emit('input', e)"
@keyboardheightchange="e => $emit('keyboardheightchange')"
></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'
export default {
name: 'u--textarea',
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
components: {
uvTextarea
},
}
</script>

View File

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

View File

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

View File

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

View File

@ -0,0 +1,259 @@
<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"
>
<u--text
:text="`+${urls.length - maxCount}`"
color="#fff"
:size="multipleSize * 0.3"
align="center"
customStyle="justify-content: center"
></u--text>
</view>
</view>
</view>
</view>
</template>
<script>
import props from './props.js'
// #ifdef APP-NVUE
// weexKPIdom
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* Album 相册
* @description 本组件提供一个类似相册的功能让开发者开发起来更加得心应手减少重复的模板代码
* @tutorial https://www.uviewui.com/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: [uni.$u.mpMixin, uni.$u.mixin, props],
data() {
return {
//
singleWidth: 0,
//
singleHeight: 0,
//
singlePercent: 0.6
}
},
watch: {
urls: {
immediate: true,
handler(newVal) {
if (newVal.length === 1) {
this.getImageRect()
}
}
}
},
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 uni.$u.addUnit(
this.urls.length === 1 ? this.singleWidth : this.multipleSize
)
},
imageHeight() {
return uni.$u.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 uni.$u.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 uni.$u.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,256 @@
<template>
<view class="u-alert-tips" v-if="show" :class="[
!show ? 'u-close-alert-tips': '',
type ? 'u-alert-tips--bg--' + type + '-light' : '',
type ? 'u-alert-tips--border--' + type + '-disabled' : '',
]" :style="{
backgroundColor: bgColor,
borderColor: borderColor
}">
<view class="u-icon-wrap">
<u-icon v-if="showIcon" :name="uIcon" :size="description ? 40 : 32" class="u-icon" :color="uIconType" :custom-style="iconStyle"></u-icon>
</view>
<view class="u-alert-content" @tap.stop="click">
<view class="u-alert-title" :style="[uTitleStyle]">
{{title}}
</view>
<view v-if="description" class="u-alert-desc" :style="[descStyle]">
{{description}}
</view>
</view>
<view class="u-icon-wrap">
<u-icon @click="close" v-if="closeAble && !closeText" hoverClass="u-type-error-hover-color" name="close" color="#c0c4cc"
:size="22" class="u-close-icon" :style="{
top: description ? '18rpx' : '24rpx'
}"></u-icon>
</view>
<text v-if="closeAble && closeText" class="u-close-text" :style="{
top: description ? '18rpx' : '24rpx'
}">{{closeText}}</text>
</view>
</template>
<script>
/**
* alertTips 警告提示
* @description 警告提示展现需要关注的信息
* @tutorial https://uviewui.com/components/alertTips.html
* @property {String} title 显示的标题文字
* @property {String} description 辅助性文字颜色比title浅一点字号也小一点可选
* @property {String} type 关闭按钮(默认为叉号icon图标)
* @property {String} icon 图标名称
* @property {Object} icon-style 图标的样式对象形式
* @property {Object} title-style 标题的样式对象形式
* @property {Object} desc-style 描述的样式对象形式
* @property {String} close-able 用文字替代关闭图标close-able为true时有效
* @property {Boolean} show-icon 是否显示左边的辅助图标
* @property {Boolean} show 显示或隐藏组件
* @event {Function} click 点击组件时触发
* @event {Function} close 点击关闭按钮时触发
*/
export default {
name: 'u-alert-tips',
props: {
//
title: {
type: String,
default: ''
},
// success/warning/info/error
type: {
type: String,
default: 'warning'
},
//
description: {
type: String,
default: ''
},
//
closeAble: {
type: Boolean,
default: false
},
//
closeText: {
type: String,
default: ''
},
//
showIcon: {
type: Boolean,
default: false
},
// coloricon
color: {
type: String,
default: ''
},
//
bgColor: {
type: String,
default: ''
},
//
borderColor: {
type: String,
default: ''
},
//
show: {
type: Boolean,
default: true
},
// icon
icon: {
type: String,
default: ''
},
// icon
iconStyle: {
type: Object,
default() {
return {}
}
},
//
titleStyle: {
type: Object,
default() {
return {}
}
},
//
descStyle: {
type: Object,
default() {
return {}
}
},
},
data() {
return {
}
},
computed: {
uTitleStyle() {
let style = {};
//
style.fontWeight = this.description ? 500 : 'normal';
// stylestyle
return this.$u.deepMerge(style, this.titleStyle);
},
uIcon() {
// icon使type
return this.icon ? this.icon : this.$u.type2icon(this.type);
},
uIconType() {
// 使type
return Object.keys(this.iconStyle).length ? '' : this.type;
}
},
methods: {
//
click() {
this.$emit('click');
},
//
close() {
this.$emit('close');
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-alert-tips {
@include vue-flex;
align-items: center;
padding: 16rpx 30rpx;
border-radius: 8rpx;
position: relative;
transition: all 0.3s linear;
border: 1px solid #fff;
&--bg--primary-light {
background-color: $u-type-primary-light;
}
&--bg--info-light {
background-color: $u-type-info-light;
}
&--bg--success-light {
background-color: $u-type-success-light;
}
&--bg--warning-light {
background-color: $u-type-warning-light;
}
&--bg--error-light {
background-color: $u-type-error-light;
}
&--border--primary-disabled {
border-color: $u-type-primary-disabled;
}
&--border--success-disabled {
border-color: $u-type-success-disabled;
}
&--border--error-disabled {
border-color: $u-type-error-disabled;
}
&--border--warning-disabled {
border-color: $u-type-warning-disabled;
}
&--border--info-disabled {
border-color: $u-type-info-disabled;
}
}
.u-close-alert-tips {
opacity: 0;
visibility: hidden;
}
.u-icon {
margin-right: 16rpx;
}
.u-alert-title {
font-size: 28rpx;
color: $u-main-color;
}
.u-alert-desc {
font-size: 26rpx;
text-align: left;
color: $u-content-color;
}
.u-close-icon {
position: absolute;
top: 20rpx;
right: 20rpx;
}
.u-close-hover {
color: red;
}
.u-close-text {
font-size: 24rpx;
color: $u-tips-color;
position: absolute;
top: 20rpx;
right: 20rpx;
line-height: 1;
}
</style>

View File

@ -0,0 +1,44 @@
export default {
props: {
// 显示文字
title: {
type: String,
default: uni.$u.props.alert.title
},
// 主题success/warning/info/error
type: {
type: String,
default: uni.$u.props.alert.type
},
// 辅助性文字
description: {
type: String,
default: uni.$u.props.alert.description
},
// 是否可关闭
closable: {
type: Boolean,
default: uni.$u.props.alert.closable
},
// 是否显示图标
showIcon: {
type: Boolean,
default: uni.$u.props.alert.showIcon
},
// 浅或深色调light-浅色dark-深色
effect: {
type: String,
default: uni.$u.props.alert.effect
},
// 文字是否居中
center: {
type: Boolean,
default: uni.$u.props.alert.center
},
// 字体大小
fontSize: {
type: [String, Number],
default: uni.$u.props.alert.fontSize
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,103 @@
<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 : $u.addUnit(-size * gap)
}"
>
<u-avatar
:size="size"
:shape="shape"
:mode="mode"
:src="$u.test.object(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"
>
<u--text
color="#ffffff"
:size="size * 0.4"
:text="`+${extraValue || urls.length - showUrl.length}`"
align="center"
customStyle="justify-content: center"
></u--text>
</view>
</view>
</view>
</template>
<script>
import props from './props.js';
/**
* AvatarGroup 头像组
* @description 本组件一般用于展示头像的地方如个人中心或者评论列表页的用户头像展示等场所
* @tutorial https://www.uviewui.com/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: [uni.$u.mpMixin, uni.$u.mixin, props],
data() {
return {
}
},
computed: {
showUrl() {
return this.urls.slice(0, this.maxCount)
}
},
methods: {
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,78 @@
export default {
props: {
// 头像图片路径(不能为相对路径)
src: {
type: String,
default: uni.$u.props.avatar.src
},
// 头像形状circle-圆形square-方形
shape: {
type: String,
default: uni.$u.props.avatar.shape
},
// 头像尺寸
size: {
type: [String, Number],
default: uni.$u.props.avatar.size
},
// 裁剪模式
mode: {
type: String,
default: uni.$u.props.avatar.mode
},
// 显示的文字
text: {
type: String,
default: uni.$u.props.avatar.text
},
// 背景色
bgColor: {
type: String,
default: uni.$u.props.avatar.bgColor
},
// 文字颜色
color: {
type: String,
default: uni.$u.props.avatar.color
},
// 文字大小
fontSize: {
type: [String, Number],
default: uni.$u.props.avatar.fontSize
},
// 显示的图标
icon: {
type: String,
default: uni.$u.props.avatar.icon
},
// 显示小程序头像只对百度微信QQ小程序有效
mpAvatar: {
type: Boolean,
default: uni.$u.props.avatar.mpAvatar
},
// 是否使用随机背景色
randomBgColor: {
type: Boolean,
default: uni.$u.props.avatar.randomBgColor
},
// 加载失败的默认头像(组件有内置默认图片)
defaultUrl: {
type: String,
default: uni.$u.props.avatar.defaultUrl
},
// 如果配置了randomBgColor为true且配置了此值则从默认的背景色数组中取出对应索引的颜色值取值0-19之间
colorIndex: {
type: [String, Number],
// 校验参数规则索引在0-19之间
validator(n) {
return uni.$u.test.range(n, [0, 19]) || n === ''
},
default: uni.$u.props.avatar.colorIndex
},
// 组件标识符
name: {
type: String,
default: uni.$u.props.avatar.name
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,72 @@
export default {
props: {
// 是否显示圆点
isDot: {
type: Boolean,
default: uni.$u.props.badge.isDot
},
// 显示的内容
value: {
type: [Number, String],
default: uni.$u.props.badge.value
},
// 是否显示
show: {
type: Boolean,
default: uni.$u.props.badge.show
},
// 最大值,超过最大值会显示 '{max}+'
max: {
type: [Number, String],
default: uni.$u.props.badge.max
},
// 主题类型error|warning|success|primary
type: {
type: String,
default: uni.$u.props.badge.type
},
// 当数值为 0 时,是否展示 Badge
showZero: {
type: Boolean,
default: uni.$u.props.badge.showZero
},
// 背景颜色优先级比type高如设置type参数会失效
bgColor: {
type: [String, null],
default: uni.$u.props.badge.bgColor
},
// 字体颜色
color: {
type: [String, null],
default: uni.$u.props.badge.color
},
// 徽标形状circle-四角均为圆角horn-左下角为直角
shape: {
type: String,
default: uni.$u.props.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: uni.$u.props.badge.numberType
},
// 设置badge的位置偏移格式为 [x, y]也即设置的为top和right的值absolute为true时有效
offset: {
type: Array,
default: uni.$u.props.badge.offset
},
// 是否反转背景和字体颜色
inverted: {
type: Boolean,
default: uni.$u.props.badge.inverted
},
// 是否绝对定位
absolute: {
type: Boolean,
default: uni.$u.props.badge.absolute
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,80 @@
// 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%;
&__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,99 @@
<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>
export default {
name: 'u-calendar-header',
mixins: [uni.$u.mpMixin, uni.$u.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,579 @@
<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 dayjs from '../../libs/util/dayjs.js';
export default {
name: 'u-calendar-month',
mixins: [uni.$u.mpMixin, uni.$u.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: uni.$u.props.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 = uni.$u.addUnit(dayWidth)
// #endif
style.height = uni.$u.addUnit(this.rowHeight)
if (index2 === 0) {
// 0item
week = (week === 0 ? 7 : week) - 1
style.marginLeft = uni.$u.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 = uni.$u.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%
uni.$u.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://www.uviewui.com/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 = uni.$u.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) {
uni.$u.toast(this.rangePrompt)
} else {
uni.$u.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 (!uni.$u.test.array(this.defaultDate)) {
defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
} else {
defaultDate = [this.defaultDate[0]]
}
} else {
//
if (!uni.$u.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)
}
}
}
</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,144 @@
export default {
props: {
// 日历顶部标题
title: {
type: String,
default: uni.$u.props.calendar.title
},
// 是否显示标题
showTitle: {
type: Boolean,
default: uni.$u.props.calendar.showTitle
},
// 是否显示副标题
showSubtitle: {
type: Boolean,
default: uni.$u.props.calendar.showSubtitle
},
// 日期类型选择single-选择单个日期multiple-可以选择多个日期range-选择日期范围
mode: {
type: String,
default: uni.$u.props.calendar.mode
},
// mode=range时第一个日期底部的提示文字
startText: {
type: String,
default: uni.$u.props.calendar.startText
},
// mode=range时最后一个日期底部的提示文字
endText: {
type: String,
default: uni.$u.props.calendar.endText
},
// 自定义列表
customList: {
type: Array,
default: uni.$u.props.calendar.customList
},
// 主题色,对底部按钮和选中日期有效
color: {
type: String,
default: uni.$u.props.calendar.color
},
// 最小的可选日期
minDate: {
type: [String, Number],
default: uni.$u.props.calendar.minDate
},
// 最大可选日期
maxDate: {
type: [String, Number],
default: uni.$u.props.calendar.maxDate
},
// 默认选中的日期mode为multiple或range是必须为数组格式
defaultDate: {
type: [Array, String, Date, null],
default: uni.$u.props.calendar.defaultDate
},
// mode=multiple时最多可选多少个日期
maxCount: {
type: [String, Number],
default: uni.$u.props.calendar.maxCount
},
// 日期行高
rowHeight: {
type: [String, Number],
default: uni.$u.props.calendar.rowHeight
},
// 日期格式化函数
formatter: {
type: [Function, null],
default: uni.$u.props.calendar.formatter
},
// 是否显示农历
showLunar: {
type: Boolean,
default: uni.$u.props.calendar.showLunar
},
// 是否显示月份背景色
showMark: {
type: Boolean,
default: uni.$u.props.calendar.showMark
},
// 确定按钮的文字
confirmText: {
type: String,
default: uni.$u.props.calendar.confirmText
},
// 确认按钮处于禁用状态时的文字
confirmDisabledText: {
type: String,
default: uni.$u.props.calendar.confirmDisabledText
},
// 是否显示日历弹窗
show: {
type: Boolean,
default: uni.$u.props.calendar.show
},
// 是否允许点击遮罩关闭日历
closeOnClickOverlay: {
type: Boolean,
default: uni.$u.props.calendar.closeOnClickOverlay
},
// 是否为只读状态,只读状态下禁止选择日期
readonly: {
type: Boolean,
default: uni.$u.props.calendar.readonly
},
// 是否展示确认按钮
showConfirm: {
type: Boolean,
default: uni.$u.props.calendar.showConfirm
},
// 日期区间最多可选天数默认无限制mode = range时有效
maxRange: {
type: [Number, String],
default: uni.$u.props.calendar.maxRange
},
// 范围选择超过最多可选天数时的提示文案mode = range时有效
rangePrompt: {
type: String,
default: uni.$u.props.calendar.rangePrompt
},
// 范围选择超过最多可选天数时是否展示提示文案mode = range时有效
showRangePrompt: {
type: Boolean,
default: uni.$u.props.calendar.showRangePrompt
},
// 是否允许日期范围的起止时间为同一天mode = range时有效
allowSameDay: {
type: Boolean,
default: uni.$u.props.calendar.allowSameDay
},
// 圆角值
round: {
type: [Boolean, String, Number],
default: uni.$u.props.calendar.round
},
// 最多展示月份数量
monthNum: {
type: [Number, String],
default: 3
}
}
}

View File

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

View File

@ -0,0 +1,85 @@
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,14 @@
export default {
props: {
// 是否打乱键盘按键的顺序
random: {
type: Boolean,
default: false
},
// 输入一个中文后,是否自动切换到英文
autoChange: {
type: Boolean,
default: false
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,14 @@
export default {
props: {
// 分组标题
title: {
type: String,
default: uni.$u.props.cellGroup.title
},
// 是否显示外边框
border: {
type: Boolean,
default: uni.$u.props.cellGroup.border
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,229 @@
<template>
<view class="u-cell" :class="[customClass]" :style="[$u.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 name="title">
<text v-if="title" class="u-cell__title-text" :style="[titleTextStyle]"
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
</slot>
<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="!$u.test.empty(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" v-if="$slots['right-icon']">
</slot>
<u-icon v-else :name="rightIcon" :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
:size="size === 'large' ? 18 : 16"></u-icon>
</view>
</view>
<u-line v-if="border"></u-line>
</view>
</template>
<script>
import props from './props.js';
/**
* cell 单元格
* @description cell单元格一般用于一组列表的情况比如个人中心页设置页等
* @tutorial https://uviewui.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: [uni.$u.mpMixin, uni.$u.mixin, props],
computed: {
titleTextStyle() {
return uni.$u.addStyle(this.titleStyle)
}
},
methods: {
// 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: 10px 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;
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,82 @@
export default {
props: {
// 标识符
name: {
type: String,
default: uni.$u.props.checkboxGroup.name
},
// 绑定的值
value: {
type: Array,
default: uni.$u.props.checkboxGroup.value
},
// 形状circle-圆形square-方形
shape: {
type: String,
default: uni.$u.props.checkboxGroup.shape
},
// 是否禁用全部checkbox
disabled: {
type: Boolean,
default: uni.$u.props.checkboxGroup.disabled
},
// 选中状态下的颜色如设置此值将会覆盖parent的activeColor值
activeColor: {
type: String,
default: uni.$u.props.checkboxGroup.activeColor
},
// 未选中的颜色
inactiveColor: {
type: String,
default: uni.$u.props.checkboxGroup.inactiveColor
},
// 整个组件的尺寸默认px
size: {
type: [String, Number],
default: uni.$u.props.checkboxGroup.size
},
// 布局方式row-横向column-纵向
placement: {
type: String,
default: uni.$u.props.checkboxGroup.placement
},
// label的字体大小px单位
labelSize: {
type: [String, Number],
default: uni.$u.props.checkboxGroup.labelSize
},
// label的字体颜色
labelColor: {
type: [String],
default: uni.$u.props.checkboxGroup.labelColor
},
// 是否禁止点击文本操作
labelDisabled: {
type: Boolean,
default: uni.$u.props.checkboxGroup.labelDisabled
},
// 图标颜色
iconColor: {
type: String,
default: uni.$u.props.checkboxGroup.iconColor
},
// 图标的大小单位px
iconSize: {
type: [String, Number],
default: uni.$u.props.checkboxGroup.iconSize
},
// 勾选图标的对齐方式left-左边right-右边
iconPlacement: {
type: String,
default: uni.$u.props.checkboxGroup.iconPlacement
},
// 竖向配列时,是否显示下划线
borderBottom: {
type: Boolean,
default: uni.$u.props.checkboxGroup.borderBottom
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
export default {
props: {
percentage: {
type: [String, Number],
default: uni.$u.props.circleProgress.percentage
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,252 @@
<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: $u.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: $u.addUnit(size)
}"
@focus="isFocus = true"
@blur="isFocus = false"
/>
</view>
</template>
<script>
import props from './props.js';
/**
* CodeInput 验证码输入
* @description 该组件一般用于验证用户短信验证码的场景也可以结合uView的键盘组件使用
* @tutorial https://www.uviewui.com/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: [uni.$u.mpMixin, uni.$u.mixin, props],
data() {
return {
inputValue: '',
isFocus: this.focus
}
},
watch: {
value: {
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 addUnit = uni.$u.addUnit
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 (uni.$u.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 = uni.$u.addUnit(this.size)
// 线
style.backgroundColor = this.borderColor
return style
}
},
methods: {
//
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
this.$emit('input', value)
//
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,34 @@
export default {
props: {
// 倒计时总秒数
seconds: {
type: [String, Number],
default: uni.$u.props.code.seconds
},
// 尚未开始时提示
startText: {
type: String,
default: uni.$u.props.code.startText
},
// 正在倒计时中的提示
changeText: {
type: String,
default: uni.$u.props.code.changeText
},
// 倒计时结束时的提示
endText: {
type: String,
default: uni.$u.props.code.endText
},
// 是否在H5刷新或各端返回再进入时继续倒计时
keepRunning: {
type: Boolean,
default: uni.$u.props.code.keepRunning
},
// 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
uniqueKey: {
type: String,
default: uni.$u.props.code.uniqueKey
}
}
}

View File

@ -0,0 +1,129 @@
<template>
<view class="u-code">
<!-- 此组件功能由js完成无需写html逻辑 -->
</view>
</template>
<script>
import props from './props.js';
/**
* Code 验证码输入框
* @description 考虑到用户实际发送验证码的场景可能是一个按钮也可能是一段文字提示语各有不同所以本组件 不提供界面显示只提供提示语由用户将提示语嵌入到具体的场景
* @tutorial https://www.uviewui.com/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: [uni.$u.mpMixin, uni.$u.mixin,props],
data() {
return {
secNum: this.seconds,
timer: null,
canGetCode: true, //
}
},
mounted() {
this.checkKeepRunning()
},
watch: {
seconds: {
immediate: true,
handler(n) {
this.secNum = n
}
}
},
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 || !this.timer) 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)
})
}
}
},
//
beforeDestroy() {
this.setTimeToStorage()
clearTimeout(this.timer)
this.timer = null
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
</style>

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,19 @@
export default {
props: {
// 当前展开面板的name非手风琴模式[<string | number>]手风琴模式string | number
value: {
type: [String, Number, Array, null],
default: uni.$u.props.collapse.value
},
// 是否手风琴模式
accordion: {
type: Boolean,
default: uni.$u.props.collapse.accordion
},
// 是否显示外边框
border: {
type: Boolean,
default: uni.$u.props.collapse.border
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,24 @@
export default {
props: {
// 倒计时时长单位ms
time: {
type: [String, Number],
default: uni.$u.props.countDown.time
},
// 时间格式DD-日HH-时mm-分ss-秒SSS-毫秒
format: {
type: String,
default: uni.$u.props.countDown.format
},
// 是否自动开始倒计时
autoStart: {
type: Boolean,
default: uni.$u.props.countDown.autoStart
},
// 是否展示毫秒倒计时
millisecond: {
type: Boolean,
default: uni.$u.props.countDown.millisecond
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,116 @@
export default {
props: {
// 是否打开组件
show: {
type: Boolean,
default: uni.$u.props.datetimePicker.show
},
// 是否展示顶部的操作栏
showToolbar: {
type: Boolean,
default: uni.$u.props.datetimePicker.showToolbar
},
// 绑定值
value: {
type: [String, Number],
default: uni.$u.props.datetimePicker.value
},
// 顶部标题
title: {
type: String,
default: uni.$u.props.datetimePicker.title
},
// 展示格式mode=date为日期选择mode=time为时间选择mode=year-month为年月选择mode=datetime为日期时间选择
mode: {
type: String,
default: uni.$u.props.datetimePicker.mode
},
// 可选的最大时间
maxDate: {
type: Number,
// 最大默认值为后10年
default: uni.$u.props.datetimePicker.maxDate
},
// 可选的最小时间
minDate: {
type: Number,
// 最小默认值为前10年
default: uni.$u.props.datetimePicker.minDate
},
// 可选的最小小时仅mode=time有效
minHour: {
type: Number,
default: uni.$u.props.datetimePicker.minHour
},
// 可选的最大小时仅mode=time有效
maxHour: {
type: Number,
default: uni.$u.props.datetimePicker.maxHour
},
// 可选的最小分钟仅mode=time有效
minMinute: {
type: Number,
default: uni.$u.props.datetimePicker.minMinute
},
// 可选的最大分钟仅mode=time有效
maxMinute: {
type: Number,
default: uni.$u.props.datetimePicker.maxMinute
},
// 选项过滤函数
filter: {
type: [Function, null],
default: uni.$u.props.datetimePicker.filter
},
// 选项格式化函数
formatter: {
type: [Function, null],
default: uni.$u.props.datetimePicker.formatter
},
// 是否显示加载中状态
loading: {
type: Boolean,
default: uni.$u.props.datetimePicker.loading
},
// 各列中,单个选项的高度
itemHeight: {
type: [String, Number],
default: uni.$u.props.datetimePicker.itemHeight
},
// 取消按钮的文字
cancelText: {
type: String,
default: uni.$u.props.datetimePicker.cancelText
},
// 确认按钮的文字
confirmText: {
type: String,
default: uni.$u.props.datetimePicker.confirmText
},
// 取消按钮的颜色
cancelColor: {
type: String,
default: uni.$u.props.datetimePicker.cancelColor
},
// 确认按钮的颜色
confirmColor: {
type: String,
default: uni.$u.props.datetimePicker.confirmColor
},
// 每列中可见选项的数量
visibleItemCount: {
type: [String, Number],
default: uni.$u.props.datetimePicker.visibleItemCount
},
// 是否允许点击遮罩关闭选择器
closeOnClickOverlay: {
type: Boolean,
default: uni.$u.props.datetimePicker.closeOnClickOverlay
},
// 各列的默认索引
defaultIndex: {
type: Array,
default: uni.$u.props.datetimePicker.defaultIndex
}
}
}

View File

@ -0,0 +1,360 @@
<template>
<u-picker
ref="picker"
:show="show"
: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.js';
import dayjs from '../../libs/util/dayjs.js';
/**
* DatetimePicker 时间日期选择器
* @description 此选择器用于时间日期
* @tutorial https://www.uviewui.com/components/datetimePicker.html
* @property {Boolean} show 用于控制选择器的弹出与收起 ( 默认 false )
* @property {Boolean} showToolbar 是否显示顶部的操作栏 ( 默认 true )
* @property {String | Number} value 绑定值
* @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: [uni.$u.mpMixin, uni.$u.mixin, props],
data() {
return {
columns: [],
innerDefaultIndex: [],
innerFormatter: (type, value) => value
}
},
watch: {
show(newValue, oldValue) {
if (newValue) {
this.updateColumnValue(this.innerValue)
}
},
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()
},
methods: {
init() {
this.innerValue = this.correctValue(this.value)
this.updateColumnValue(this.innerValue)
},
// propsref
setFormatter(e) {
this.innerFormatter = e
},
//
close() {
if (this.closeOnClickOverlay) {
this.$emit('close')
}
},
//
cancel() {
this.$emit('cancel')
},
//
confirm() {
this.$emit('confirm', {
value: this.innerValue,
mode: this.mode
})
this.$emit('input', this.innerValue)
},
//,,
intercept(e,type){
let judge = e.match(/\d+/g)
//
if(judge.length>1){
uni.$u.error("请勿在过滤或格式化函数时添加数字")
return 0
}else if(type&&judge[0].length==4){//
return judge[0]
}else if(judge[0].length>2){
uni.$u.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()
this.updateIndexs(value)
},
//
updateIndexs(value) {
let values = []
const formatter = this.formatter || this.innerFormatter
const padZero = uni.$u.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}` : uni.$u.padZero(value)
return value
})
//
if (this.filter) {
values = this.filter(type, values)
}
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 && !uni.$u.test.date(value)) {
// 使
value = this.minDate
} else if (!isDateMode && !value) {
//
value = `${uni.$u.padZero(this.minHour)}:${uni.$u.padZero(this.minMinute)}`
}
//
if (!isDateMode) {
if (String(value).indexOf(':') === -1) return uni.$u.error('时间错误请传递如12:24的格式')
let [hour, minute] = value.split(':')
//
hour = uni.$u.padZero(uni.$u.range(this.minHour, this.maxHour, Number(hour)))
minute = uni.$u.padZero(uni.$u.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,44 @@
export default {
props: {
// 是否虚线
dashed: {
type: Boolean,
default: uni.$u.props.divider.dashed
},
// 是否细线
hairline: {
type: Boolean,
default: uni.$u.props.divider.hairline
},
// 是否以点替代文字优先于text字段起作用
dot: {
type: Boolean,
default: uni.$u.props.divider.dot
},
// 内容文本的位置left-左边center-中间right-右边
textPosition: {
type: String,
default: uni.$u.props.divider.textPosition
},
// 文本内容
text: {
type: [String, Number],
default: uni.$u.props.divider.text
},
// 文本大小
textSize: {
type: [String, Number],
default: uni.$u.props.divider.textSize
},
// 文本颜色
textColor: {
type: String,
default: uni.$u.props.divider.textColor
},
// 线条颜色
lineColor: {
type: String,
default: uni.$u.props.divider.lineColor
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,24 @@
export default {
props: {
// 背景颜色默认transparent
bgColor: {
type: String,
default: uni.$u.props.gap.bgColor
},
// 分割槽高度单位px默认30
height: {
type: [String, Number],
default: uni.$u.props.gap.height
},
// 与上一个组件的距离
marginTop: {
type: [String, Number],
default: uni.$u.props.gap.marginTop
},
// 与下一个组件的距离
marginBottom: {
type: [String, Number],
default: uni.$u.props.gap.marginBottom
}
}
}

View File

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

View File

@ -0,0 +1,14 @@
export default {
props: {
// 宫格的name
name: {
type: [String, Number, null],
default: uni.$u.props.gridItem.name
},
// 背景颜色
bgColor: {
type: String,
default: uni.$u.props.gridItem.bgColor
}
}
}

View File

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

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