正在显示
31 个修改的文件
包含
4730 行增加
和
0 行删除
.hbuilderx/launch.json
0 → 100644
1 | +{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/ | ||
2 | + // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数 | ||
3 | + "version": "0.0", | ||
4 | + "configurations": [{ | ||
5 | + "default" : | ||
6 | + { | ||
7 | + "launchtype" : "local" | ||
8 | + }, | ||
9 | + "mp-weixin" : | ||
10 | + { | ||
11 | + "launchtype" : "local" | ||
12 | + }, | ||
13 | + "type" : "uniCloud" | ||
14 | + } | ||
15 | + ] | ||
16 | +} |
App.vue
0 → 100644
index.html
0 → 100644
1 | +<!DOCTYPE html> | ||
2 | +<html lang="en"> | ||
3 | + <head> | ||
4 | + <meta charset="UTF-8" /> | ||
5 | + <script> | ||
6 | + var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || | ||
7 | + CSS.supports('top: constant(a)')) | ||
8 | + document.write( | ||
9 | + '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + | ||
10 | + (coverSupport ? ', viewport-fit=cover' : '') + '" />') | ||
11 | + </script> | ||
12 | + <title></title> | ||
13 | + <!--preload-links--> | ||
14 | + <!--app-context--> | ||
15 | + </head> | ||
16 | + <body> | ||
17 | + <div id="app"><!--app-html--></div> | ||
18 | + <script type="module" src="/main.js"></script> | ||
19 | + </body> | ||
20 | +</html> |
main.js
0 → 100644
1 | +import App from './App' | ||
2 | + | ||
3 | +// #ifndef VUE3 | ||
4 | +import Vue from 'vue' | ||
5 | +import './uni.promisify.adaptor' | ||
6 | +Vue.config.productionTip = false | ||
7 | +App.mpType = 'app' | ||
8 | +const app = new Vue({ | ||
9 | + ...App | ||
10 | +}) | ||
11 | +app.$mount() | ||
12 | +// #endif | ||
13 | + | ||
14 | +// #ifdef VUE3 | ||
15 | +import { createSSRApp } from 'vue' | ||
16 | +export function createApp() { | ||
17 | + const app = createSSRApp(App) | ||
18 | + return { | ||
19 | + app | ||
20 | + } | ||
21 | +} | ||
22 | +// #endif |
manifest.json
0 → 100644
1 | +{ | ||
2 | + "name" : "blind-box", | ||
3 | + "appid" : "__UNI__586A55B", | ||
4 | + "description" : "", | ||
5 | + "versionName" : "1.0.0", | ||
6 | + "versionCode" : "100", | ||
7 | + "transformPx" : false, | ||
8 | + /* 5+App特有相关 */ | ||
9 | + "app-plus" : { | ||
10 | + "usingComponents" : true, | ||
11 | + "nvueStyleCompiler" : "uni-app", | ||
12 | + "compilerVersion" : 3, | ||
13 | + "splashscreen" : { | ||
14 | + "alwaysShowBeforeRender" : true, | ||
15 | + "waiting" : true, | ||
16 | + "autoclose" : true, | ||
17 | + "delay" : 0 | ||
18 | + }, | ||
19 | + /* 模块配置 */ | ||
20 | + "modules" : {}, | ||
21 | + /* 应用发布信息 */ | ||
22 | + "distribute" : { | ||
23 | + /* android打包配置 */ | ||
24 | + "android" : { | ||
25 | + "permissions" : [ | ||
26 | + "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", | ||
27 | + "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", | ||
28 | + "<uses-permission android:name=\"android.permission.VIBRATE\"/>", | ||
29 | + "<uses-permission android:name=\"android.permission.READ_LOGS\"/>", | ||
30 | + "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", | ||
31 | + "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", | ||
32 | + "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", | ||
33 | + "<uses-permission android:name=\"android.permission.CAMERA\"/>", | ||
34 | + "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", | ||
35 | + "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", | ||
36 | + "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", | ||
37 | + "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", | ||
38 | + "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", | ||
39 | + "<uses-feature android:name=\"android.hardware.camera\"/>", | ||
40 | + "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" | ||
41 | + ] | ||
42 | + }, | ||
43 | + /* ios打包配置 */ | ||
44 | + "ios" : {}, | ||
45 | + /* SDK配置 */ | ||
46 | + "sdkConfigs" : {} | ||
47 | + } | ||
48 | + }, | ||
49 | + /* 快应用特有相关 */ | ||
50 | + "quickapp" : {}, | ||
51 | + /* 小程序特有相关 */ | ||
52 | + "mp-weixin" : { | ||
53 | + "appid" : "", | ||
54 | + "setting" : { | ||
55 | + "urlCheck" : false | ||
56 | + }, | ||
57 | + "usingComponents" : true | ||
58 | + }, | ||
59 | + "mp-alipay" : { | ||
60 | + "usingComponents" : true | ||
61 | + }, | ||
62 | + "mp-baidu" : { | ||
63 | + "usingComponents" : true | ||
64 | + }, | ||
65 | + "mp-toutiao" : { | ||
66 | + "usingComponents" : true | ||
67 | + }, | ||
68 | + "uniStatistics" : { | ||
69 | + "enable" : false | ||
70 | + }, | ||
71 | + "vueVersion" : "3" | ||
72 | +} |
node_modules/.package-lock.json
0 → 100644
1 | +{ | ||
2 | + "name": "blind-box", | ||
3 | + "lockfileVersion": 2, | ||
4 | + "requires": true, | ||
5 | + "packages": { | ||
6 | + "node_modules/@climblee/uv-ui": { | ||
7 | + "version": "1.1.1-4.1", | ||
8 | + "resolved": "https://registry.npmjs.org/@climblee/uv-ui/-/uv-ui-1.1.1-4.1.tgz", | ||
9 | + "integrity": "sha512-gq8uKxTNUzjNsFhNoqG8yAhAlXcQLBpIwtbwZXyJhS559xYSkrYVpXy7u3Mj9+zibH5LuBC5JhPwW+otjBy/XQ==" | ||
10 | + } | ||
11 | + } | ||
12 | +} |
node_modules/@climblee/uv-ui/LICENSE
0 → 100644
1 | +MIT License | ||
2 | + | ||
3 | +Copyright (c) 2023 www.uvui.cn | ||
4 | + | ||
5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | ||
6 | +of this software and associated documentation files (the "Software"), to deal | ||
7 | +in the Software without restriction, including without limitation the rights | ||
8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
9 | +copies of the Software, and to permit persons to whom the Software is | ||
10 | +furnished to do so, subject to the following conditions: | ||
11 | + | ||
12 | +The above copyright notice and this permission notice shall be included in all | ||
13 | +copies or substantial portions of the Software. | ||
14 | + | ||
15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
21 | +SOFTWARE. |
node_modules/@climblee/uv-ui/README.md
0 → 100644
1 | +<p align="center"> | ||
2 | + <span> </span><img alt="logo" src="https://www.uvui.cn/common/logo.png" width="120" height="120"> | ||
3 | +</p> | ||
4 | +<h3 align="center">uv-ui</h3> | ||
5 | +<h3 align="center">兼容vue3+2多平台快速开发的UI框架</h3> | ||
6 | + | ||
7 | +[![star](https://gitee.com/climblee/uv-ui/badge/star.svg?theme=gvp)](https://gitee.com/climblee/uv-ui) | ||
8 | +[![star](https://gitee.com/climblee/uv-ui/badge/fork.svg?theme=gvp)](https://gitee.com/climblee/uv-ui) | ||
9 | +[![star](https://img.shields.io/github/stars/climblee/uv-ui?style=flat-square&logo=GitHub)](https://github.com/climblee/uv-ui) | ||
10 | +[![issues](https://img.shields.io/github/issues/climblee/uv-ui?style=flat-square&logo=GitHub)](https://github.com/climblee/uv-ui/issues) | ||
11 | +[![Website](https://img.shields.io/badge/uvui-up-blue?style=flat-square)](https://www.uvui.cn) | ||
12 | +[![version](https://img.shields.io/badge/version-1.1.10-brightgreen.svg)](https://www.uvui.cn/components/changelog.html) | ||
13 | +[![license](https://img.shields.io/github/license/climblee/uv-ui?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License) | ||
14 | + | ||
15 | +## 温馨提示:如需下载uv-ui示例项目,请不要使用【下载插件ZIP】按钮。 | ||
16 | + | ||
17 | +### uvui官方群1:<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=aaoyDvfV7nSee9vzWfzyZM1vKlu3xrNZ&authKey=pdU6HDpyzGUoc1QvQvCmzCbwzsoHgBbToF%2F0ChP4lNjPIPgWHGRE4I99XYGiTcNI&noverify=0&group_code=549833913" target="_blank">549833913</a> | ||
18 | +### uvui官方群2:<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=2QbEeKBn6ysCyQ09V4mgst1W8onxFybQ&authKey=aMTGL5zCYwsinG%2FeH0qMlAqXAdyKr5AjXVf2oMnsmj7NCg%2F2HraMU%2FNFxELLIPvp&noverify=0&group_code=206060892" target="_blank">206060892</a> | ||
19 | + | ||
20 | +## uvui特点 | ||
21 | + | ||
22 | +1. **uv-ui的前世今生**,`uv-ui` 是基于 `uview2.x` 版本改造而来。重命名也是为了避开发布冲突和很多组件 `u-`在 `nvue` 中不能使用的情况,所以这才诞生了`uv-ui`。感谢 `uview-ui` 作者的开源奉献,再次为开源点赞。 同时 `uv-ui` 也是无条件开源。 | ||
23 | + | ||
24 | +2. **全端兼容**,`uv-ui`支持vue3、vue2、app-vue、app-nvue、h5、小程序等。`uv-ui`的组件都是多端自适应的,底层会抹平很多小程序平台的差异或bug。 | ||
25 | + | ||
26 | +3. **扩展配置**,`uv-ui`内置的方法默认不再挂载到`uni`对象之上,也就意味着默认情况下不能在项目中直接使用`uni.$uv.xxx`使用内置方法。但是可以通过扩展可以解决,通过如下方式进行配置即可,使用方式请参考[扩展配置](https://www.uvui.cn/components/setting.html)。其中包括[ JS工具库](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-js%E5%B7%A5%E5%85%B7%E5%BA%93)、[ 自定义主题](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%BB%E9%A2%98)、[ 基础样式](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-%E5%9F%BA%E7%A1%80%E6%A0%B7%E5%BC%8F)、[ setconfig](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-setconfig)等。 | ||
27 | + | ||
28 | +## 预览 | ||
29 | + | ||
30 | +通过微信或浏览器扫码查看演示效果。 | ||
31 | + | ||
32 | +<img src="https://www.uvui.cn/common/h5_qrcode.png" width="180" height="180" /> | ||
33 | + | ||
34 | +## 链接 | ||
35 | + | ||
36 | +- [官方文档](https://www.uvui.cn) | ||
37 | +- [演示地址](https://h5.uvui.cn) | ||
38 | +- [更新日志](https://www.uvui.cn/components/changelog.html) | ||
39 | +- [关于我们](https://www.uvui.cn/cooperation/about.html) | ||
40 | +- <a href="#list">组件列表</a> | ||
41 | + | ||
42 | +## 交流反馈 | ||
43 | + | ||
44 | +欢迎加入我们的QQ群交流反馈:[点此跳转](https://www.uvui.cn/components/addQQGroup.html) | ||
45 | + | ||
46 | +## 快速开始 | ||
47 | + | ||
48 | +`uv-ui` 强烈建议通过 `下载插件并导入HbuilderX` 导入组件。或者下载完整 [uv-ui项目](https://ext.dcloud.net.cn/plugin?id=12287) 将 `uni_modules` 复制到自己的项目。道路千万条,选择最适合自己的一条。 | ||
49 | + | ||
50 | +请通过[快速上手](https://www.uvui.cn/components/quickstart.html)了解更详细的内容。 | ||
51 | + | ||
52 | +**注意:导入插件后,建议`HBuilderX`重新运行项目,可能新导入的插件不能实时更新而导致不能运行。** | ||
53 | + | ||
54 | +## 使用方法 | ||
55 | + | ||
56 | +组件导入 `uni_modules` 后,直接在项目中使用,无需通过import引入组件。 | ||
57 | + | ||
58 | +```html | ||
59 | +<template> | ||
60 | + <uv-icon name="baidu" size="30" color="#909399"></uv-icon> | ||
61 | +</template> | ||
62 | +``` | ||
63 | + | ||
64 | +## 扩展功能 | ||
65 | + | ||
66 | +`uv-ui` 内置了强大的工具函数、请求封装等,可以根据自身需求进行扩展配置,详情请查看[扩展配置](https://www.uvui.cn/components/setting.html)。 | ||
67 | + | ||
68 | +**注意:只有[扩展配置](https://www.uvui.cn/components/setting.html)后才能在自己的项目页面中使用组件库内置方法和变量等**。 | ||
69 | + | ||
70 | +<div id="list"></div> | ||
71 | + | ||
72 | +## 组件列表 | ||
73 | + | ||
74 | +下表为 `uv-ui` 的扩展组件清单,点击每个组件**点击下载&安装**即可在详情页面导入组件到项目下,导入后建议重新运行即可直接使用,组件无需import和注册。 | ||
75 | + | ||
76 | +| 组件名 | 组件说明 | | ||
77 | +| --- | --- | | ||
78 | +| uv-calendars | [新版日历(推荐)](https://www.uvui.cn/components/calendars.html) | | ||
79 | +| uv-drop-down | [下拉筛选](https://www.uvui.cn/components/dropDown.html) | | ||
80 | +| uv-scroll-list | [横向滚动列表](https://www.uvui.cn/components/scrollList.html) | | ||
81 | +| uv-vtabs | [垂直选项卡](https://www.uvui.cn/components/vtabs.html) | | ||
82 | +| uv-pick-color | [颜色选择器](https://www.uvui.cn/components/pickColor.html) | | ||
83 | +| uv-qrcode | [二维码](https://www.uvui.cn/components/qrcode.html) | | ||
84 | +| uv-waterfall | [瀑布流](https://www.uvui.cn/components/waterfall.html) | | ||
85 | +| uv-row | [Layout 布局](https://www.uvui.cn/components/layout.html) | | ||
86 | +| uv-icon | [图标](https://www.uvui.cn/components/icon.html) | | ||
87 | +| uv-button | [按钮](https://www.uvui.cn/components/button.html) | | ||
88 | +| uv-text | [文本](https://www.uvui.cn/components/text.html) | | ||
89 | +| uv-link | [超链接](https://www.uvui.cn/components/link.html) | | ||
90 | +| uv-image | [图片](https://www.uvui.cn/components/image.html) | | ||
91 | +| uv-transition | [动画](https://www.uvui.cn/components/transition.html) | | ||
92 | +| uv-form | [表单](https://www.uvui.cn/components/form.html) | | ||
93 | +| uv-input | [增强输入框](https://www.uvui.cn/components/input.html) | | ||
94 | +| uv-textarea | [增强文本域](https://www.uvui.cn/components/textarea.html) | | ||
95 | +| uv-checkbox | [复选框](https://www.uvui.cn/components/checkbox.html) | | ||
96 | +| uv-radio | [单选框](https://www.uvui.cn/components/radio.html) | | ||
97 | +| uv-switch | [开关选择器](https://www.uvui.cn/components/switch.html) | | ||
98 | +| uv-calendar | [日历](https://www.uvui.cn/components/calendar.html) | | ||
99 | +| uv-picker | [选择器](https://www.uvui.cn/components/picker.html) | | ||
100 | +| uv-datetime-picker | [时间选择器](https://www.uvui.cn/components/datetimePicker.html) | | ||
101 | +| uv-code | [验证码倒计时](https://www.uvui.cn/components/code.html) | | ||
102 | +| uv-keyboard | [键盘](https://www.uvui.cn/components/keyboard.html) | | ||
103 | +| uv-rate | [评分](https://www.uvui.cn/components/rate.html) | | ||
104 | +| uv-search | [多功能搜索框](https://www.uvui.cn/components/search.html) | | ||
105 | +| uv-number-box | [步进器](https://www.uvui.cn/components/numberBox.html) | | ||
106 | +| uv-upload | [上传](https://www.uvui.cn/components/upload.html) | | ||
107 | +| uv-slider | [滑动选择器](https://www.uvui.cn/components/slider.html) | | ||
108 | +| uv-list | [列表](https://www.uvui.cn/components/list.html) | | ||
109 | +| uv-index-list | [索引列表](https://www.uvui.cn/components/indexList.html) | | ||
110 | +| uv-tags | [标签](https://www.uvui.cn/components/tag.html) | | ||
111 | +| uv-line-progress | [线形进度条](https://www.uvui.cn/components/lineProgress.html) | | ||
112 | +| uv-badge | [徽标数](https://www.uvui.cn/components/badge.html) | | ||
113 | +| uv-count-down | [倒计时](https://www.uvui.cn/components/countDown.html) | | ||
114 | +| uv-count-to | [数字滚动](https://www.uvui.cn/components/countTo.html) | | ||
115 | +| uv-avatar | [头像](https://www.uvui.cn/components/avatar.html) | | ||
116 | +| uv-skeleton | [骨架屏](https://www.uvui.cn/components/skeleton.html) | | ||
117 | +| uv-loading-icon | [加载动画](https://www.uvui.cn/components/loadingIcon.html) | | ||
118 | +| uv-loading-page | [加载页](https://www.uvui.cn/components/loadingPage.html) | | ||
119 | +| uv-load-more | [加载更多](https://www.uvui.cn/components/loadMore.html) | | ||
120 | +| uv-empty | [内容为空](https://www.uvui.cn/components/empty.html) | | ||
121 | +| uv-tooltip | [长按提示](https://www.uvui.cn/components/tooltip.html) | | ||
122 | +| uv-alert | [警告提示](https://www.uvui.cn/components/alert.html) | | ||
123 | +| uv-toast | [消息提示](https://www.uvui.cn/components/toast.html) | | ||
124 | +| uv-notice-bar | [滚动通知](https://www.uvui.cn/components/noticeBar.html) | | ||
125 | +| uv-notify | [消息提示](https://www.uvui.cn/components/notify.html) | | ||
126 | +| uv-no-network | [无网络提示](https://www.uvui.cn/components/noNetwork.html) | | ||
127 | +| uv-popup | [弹出层](https://www.uvui.cn/components/popup.html) | | ||
128 | +| uv-modal | [模态框](https://www.uvui.cn/components/modal.html) | | ||
129 | +| uv-cell | [单元格](https://www.uvui.cn/components/cell.html) | | ||
130 | +| uv-swipe-action | [滑动单元格](https://www.uvui.cn/components/swipeAction.html) | | ||
131 | +| uv-swiper | [轮播图](https://www.uvui.cn/components/swiper.html) | | ||
132 | +| uv-collapse | [折叠面板](https://www.uvui.cn/components/collapse.html) | | ||
133 | +| uv-grid | [宫格布局](https://www.uvui.cn/components/grid.html) | | ||
134 | +| uv-album | [相册](https://www.uvui.cn/components/album.html) | | ||
135 | +| uv-tabbar | [底部导航栏](https://www.uvui.cn/components/tabbar.html) | | ||
136 | +| uv-back-top | [返回顶部](https://www.uvui.cn/components/backTop.html) | | ||
137 | +| uv-navbar | [自定义导航栏](https://www.uvui.cn/components/navbar.html) | | ||
138 | +| uv-action-sheet | [底部操作菜单](https://www.uvui.cn/components/actionSheet.html) | | ||
139 | +| uv-tabs | [标签选项卡](https://www.uvui.cn/components/tabs.html) | | ||
140 | +| uv-steps | [步骤条](https://www.uvui.cn/components/steps.html) | | ||
141 | +| uv-subsection | [分段器](https://www.uvui.cn/components/subsection.html) | | ||
142 | +| uv-sticky | [吸顶](https://www.uvui.cn/components/sticky.html) | | ||
143 | +| uv-parse | [富文本解析器](https://www.uvui.cn/components/parse.html) | | ||
144 | +| uv-overlay | [遮罩层](https://www.uvui.cn/components/overlay.html) | | ||
145 | +| uv-code-input | [验证码输入](https://www.uvui.cn/components/codeInput.html) | | ||
146 | +| uv-read-more | [展开阅读更多](https://www.uvui.cn/components/readMore.html) | | ||
147 | +| uv-line | [线条](https://www.uvui.cn/components/line.html) | | ||
148 | +| uv-gap | [间隔槽](https://www.uvui.cn/components/gap.html) | | ||
149 | +| uv-divider | [分割线](https://www.uvui.cn/components/divider.html) | | ||
150 | + | ||
151 | +## 版权信息 | ||
152 | +uv-ui遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uv-ui应用到您的产品中。 | ||
153 | + | ||
154 | +## 作者想说 | ||
155 | +- 开源真的不易,不图大家的钱财,所以希望大家多多鼓励支持,希望不要恶意评论,有问题加群快速解决。 | ||
156 | +- 遇到BUG,是一件很正常的事情,是程序肯定就有BUG,所以希望大家能以理解的心态去提出BUG,然后作者才有动力去努力修复。 | ||
157 | +- 最后觉得好用的小伙伴,不要吝啬你的双手,给个好评就是给我们最大的鼓励。 | ||
158 | + | ||
159 | +# 恶评者手下留情,有事加QQ群解决:549833913 |
node_modules/@climblee/uv-ui/changelog.md
0 → 100644
1 | +export default { | ||
2 | + props: { | ||
3 | + // 标题,有值则显示,同时会显示关闭按钮 | ||
4 | + title: { | ||
5 | + type: String, | ||
6 | + default: '' | ||
7 | + }, | ||
8 | + // 选项上方的描述信息 | ||
9 | + description: { | ||
10 | + type: String, | ||
11 | + default: '' | ||
12 | + }, | ||
13 | + // 数据 | ||
14 | + actions: { | ||
15 | + type: Array, | ||
16 | + default: () => [] | ||
17 | + }, | ||
18 | + // 取消按钮的文字,不为空时显示按钮 | ||
19 | + cancelText: { | ||
20 | + type: String, | ||
21 | + default: '' | ||
22 | + }, | ||
23 | + // 点击某个菜单项时是否关闭弹窗 | ||
24 | + closeOnClickAction: { | ||
25 | + type: Boolean, | ||
26 | + default: true | ||
27 | + }, | ||
28 | + // 处理底部安全区(默认true) | ||
29 | + safeAreaInsetBottom: { | ||
30 | + type: Boolean, | ||
31 | + default: true | ||
32 | + }, | ||
33 | + // 小程序的打开方式 | ||
34 | + openType: { | ||
35 | + type: String, | ||
36 | + default: '' | ||
37 | + }, | ||
38 | + // 点击遮罩是否允许关闭 (默认true) | ||
39 | + closeOnClickOverlay: { | ||
40 | + type: Boolean, | ||
41 | + default: true | ||
42 | + }, | ||
43 | + // 圆角值 | ||
44 | + round: { | ||
45 | + type: [Boolean, String, Number], | ||
46 | + default: 0 | ||
47 | + }, | ||
48 | + ...uni.$uv?.props?.actionSheet | ||
49 | + } | ||
50 | +} |
1 | + | ||
2 | +<template> | ||
3 | + <uv-popup | ||
4 | + ref="popup" | ||
5 | + mode="bottom" | ||
6 | + :safeAreaInsetBottom="safeAreaInsetBottom" | ||
7 | + :round="round" | ||
8 | + :close-on-click-overlay="closeOnClickOverlay" | ||
9 | + @change="popupChange" | ||
10 | + > | ||
11 | + <view class="uv-action-sheet"> | ||
12 | + <view | ||
13 | + class="uv-action-sheet__header" | ||
14 | + v-if="title" | ||
15 | + > | ||
16 | + <text class="uv-action-sheet__header__title uv-line-1">{{title}}</text> | ||
17 | + <view | ||
18 | + class="uv-action-sheet__header__icon-wrap" | ||
19 | + @tap.stop="cancel" | ||
20 | + > | ||
21 | + <uv-icon | ||
22 | + name="close" | ||
23 | + size="17" | ||
24 | + color="#c8c9cc" | ||
25 | + bold | ||
26 | + ></uv-icon> | ||
27 | + </view> | ||
28 | + </view> | ||
29 | + <text | ||
30 | + class="uv-action-sheet__description" | ||
31 | + :style="[{ | ||
32 | + marginTop: `${title && description ? 0 : '18px'}` | ||
33 | + }]" | ||
34 | + v-if="description" | ||
35 | + >{{description}}</text> | ||
36 | + <slot> | ||
37 | + <uv-line v-if="description"></uv-line> | ||
38 | + <view class="uv-action-sheet__item-wrap"> | ||
39 | + <view v-for="(item, index) in actions" :key="index"> | ||
40 | + <!-- #ifdef MP --> | ||
41 | + <button | ||
42 | + class="uv-reset-button" | ||
43 | + :openType="item.openType" | ||
44 | + @getuserinfo="onGetUserInfo" | ||
45 | + @contact="onContact" | ||
46 | + @getphonenumber="onGetPhoneNumber" | ||
47 | + @error="onError" | ||
48 | + @launchapp="onLaunchApp" | ||
49 | + @opensetting="onOpenSetting" | ||
50 | + :lang="lang" | ||
51 | + :session-from="sessionFrom" | ||
52 | + :send-message-title="sendMessageTitle" | ||
53 | + :send-message-path="sendMessagePath" | ||
54 | + :send-message-img="sendMessageImg" | ||
55 | + :show-message-card="showMessageCard" | ||
56 | + :app-parameter="appParameter" | ||
57 | + @tap="selectHandler(index)" | ||
58 | + :hover-class="!item.disabled && !item.loading ? 'uv-action-sheet--hover' : ''" | ||
59 | + > | ||
60 | + <!-- #endif --> | ||
61 | + <view | ||
62 | + class="uv-action-sheet__item-wrap__item" | ||
63 | + @tap.stop="selectHandler(index)" | ||
64 | + :hover-class="!item.disabled && !item.loading ? 'uv-action-sheet--hover' : ''" | ||
65 | + :hover-stay-time="150" | ||
66 | + > | ||
67 | + <template v-if="!item.loading"> | ||
68 | + <text | ||
69 | + class="uv-action-sheet__item-wrap__item__name" | ||
70 | + :style="[itemStyle(index)]" | ||
71 | + >{{ item.name }}</text> | ||
72 | + <text | ||
73 | + v-if="item.subname" | ||
74 | + class="uv-action-sheet__item-wrap__item__subname" | ||
75 | + >{{ item.subname }}</text> | ||
76 | + </template> | ||
77 | + <uv-loading-icon | ||
78 | + v-else | ||
79 | + custom-class="van-action-sheet__loading" | ||
80 | + size="18" | ||
81 | + mode="circle" | ||
82 | + /> | ||
83 | + </view> | ||
84 | + <!-- #ifdef MP --> | ||
85 | + </button> | ||
86 | + <!-- #endif --> | ||
87 | + <uv-line v-if="index !== actions.length - 1"></uv-line> | ||
88 | + </view> | ||
89 | + </view> | ||
90 | + </slot> | ||
91 | + <uv-gap | ||
92 | + bgColor="#eaeaec" | ||
93 | + height="6" | ||
94 | + v-if="cancelText" | ||
95 | + ></uv-gap> | ||
96 | + <view hover-class="uv-action-sheet--hover"> | ||
97 | + <text | ||
98 | + @touchmove.stop.prevent | ||
99 | + :hover-stay-time="150" | ||
100 | + v-if="cancelText" | ||
101 | + class="uv-action-sheet__cancel-text" | ||
102 | + @tap="cancel" | ||
103 | + >{{cancelText}}</text> | ||
104 | + </view> | ||
105 | + </view> | ||
106 | + </uv-popup> | ||
107 | +</template> | ||
108 | + | ||
109 | +<script> | ||
110 | + import mpMixin from '../../libs/mixin/mpMixin.js' | ||
111 | + import mixin from '../../libs/mixin/mixin.js' | ||
112 | + import button from '../../libs/mixin/button.js' | ||
113 | + import openType from '../../libs/mixin/openType.js' | ||
114 | + import props from './props.js'; | ||
115 | + /** | ||
116 | + * ActionSheet 操作菜单 | ||
117 | + * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。 | ||
118 | + * @tutorial https://www.uvui.cn/components/actionSheet.html | ||
119 | + * @property {Boolean} show 操作菜单是否展示 (默认 false ) | ||
120 | + * @property {String} title 操作菜单标题 | ||
121 | + * @property {String} description 选项上方的描述信息 | ||
122 | + * @property {Array<Object>} actions 按钮的文字数组,见官方文档示例 | ||
123 | + * @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮 | ||
124 | + * @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true ) | ||
125 | + * @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true ) | ||
126 | + * @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error ) | ||
127 | + * @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true ) | ||
128 | + * @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文 | ||
129 | + * @property {String} sessionFrom 会话来源,openType="contact"时有效 | ||
130 | + * @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效 | ||
131 | + * @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效 | ||
132 | + * @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效 | ||
133 | + * @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false ) | ||
134 | + * @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效 | ||
135 | + * | ||
136 | + * @event {Function} select 点击ActionSheet列表项时触发 | ||
137 | + * @event {Function} close 点击取消按钮时触发 | ||
138 | + * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效 | ||
139 | + * @event {Function} contact 客服消息回调,openType="contact"时有效 | ||
140 | + * @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效 | ||
141 | + * @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效 | ||
142 | + * @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效 | ||
143 | + * @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效 | ||
144 | + * @example <uv-action-sheet ref="actionSheet" :actions="list" :title="title" ></uv-action-sheet> | ||
145 | + */ | ||
146 | + export default { | ||
147 | + name: "uv-action-sheet", | ||
148 | + mixins: [openType, button, mpMixin , mixin, props], | ||
149 | + emits: ['close', 'select'], | ||
150 | + computed: { | ||
151 | + // 操作项目的样式 | ||
152 | + itemStyle() { | ||
153 | + return (index) => { | ||
154 | + let style = {}; | ||
155 | + if (this.actions[index].color) style.color = this.actions[index].color | ||
156 | + if (this.actions[index].fontSize) style.fontSize = this.$uv.addUnit(this.actions[index].fontSize) | ||
157 | + // 选项被禁用的样式 | ||
158 | + if (this.actions[index].disabled) style.color = '#c0c4cc' | ||
159 | + return style; | ||
160 | + } | ||
161 | + }, | ||
162 | + }, | ||
163 | + methods: { | ||
164 | + open() { | ||
165 | + this.$refs.popup.open(); | ||
166 | + }, | ||
167 | + close() { | ||
168 | + this.$refs.popup.close(); | ||
169 | + }, | ||
170 | + popupChange(e) { | ||
171 | + if(!e.show) this.$emit('close'); | ||
172 | + }, | ||
173 | + // 点击取消按钮 | ||
174 | + cancel() { | ||
175 | + this.close(); | ||
176 | + }, | ||
177 | + selectHandler(index) { | ||
178 | + const item = this.actions[index] | ||
179 | + if (item && !item.disabled && !item.loading) { | ||
180 | + this.$emit('select', item) | ||
181 | + if (this.closeOnClickAction) { | ||
182 | + this.close(); | ||
183 | + } | ||
184 | + } | ||
185 | + }, | ||
186 | + } | ||
187 | + } | ||
188 | +</script> | ||
189 | + | ||
190 | +<style lang="scss" scoped> | ||
191 | + $show-lines: 1; | ||
192 | + $show-reset-button: 1; | ||
193 | + @import '../../libs/css/variable.scss'; | ||
194 | + @import '../../libs/css/components.scss'; | ||
195 | + @import '../../libs/css/color.scss'; | ||
196 | + $uv-action-sheet-reset-button-width:100% !default; | ||
197 | + $uv-action-sheet-title-font-size: 16px !default; | ||
198 | + $uv-action-sheet-title-padding: 12px 30px !default; | ||
199 | + $uv-action-sheet-title-color: $uv-main-color !default; | ||
200 | + $uv-action-sheet-header-icon-wrap-right:15px !default; | ||
201 | + $uv-action-sheet-header-icon-wrap-top:15px !default; | ||
202 | + $uv-action-sheet-description-font-size:13px !default; | ||
203 | + $uv-action-sheet-description-color:14px !default; | ||
204 | + $uv-action-sheet-description-margin: 18px 15px !default; | ||
205 | + $uv-action-sheet-item-wrap-item-padding:15px !default; | ||
206 | + $uv-action-sheet-item-wrap-name-font-size:16px !default; | ||
207 | + $uv-action-sheet-item-wrap-subname-font-size:13px !default; | ||
208 | + $uv-action-sheet-item-wrap-subname-color: #c0c4cc !default; | ||
209 | + $uv-action-sheet-item-wrap-subname-margin-top:10px !default; | ||
210 | + $uv-action-sheet-cancel-text-font-size:16px !default; | ||
211 | + $uv-action-sheet-cancel-text-color:$uv-content-color !default; | ||
212 | + $uv-action-sheet-cancel-text-font-size:15px !default; | ||
213 | + $uv-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default; | ||
214 | + | ||
215 | + .uv-reset-button { | ||
216 | + width: $uv-action-sheet-reset-button-width; | ||
217 | + } | ||
218 | + | ||
219 | + .uv-action-sheet { | ||
220 | + text-align: center; | ||
221 | + &__header { | ||
222 | + position: relative; | ||
223 | + padding: $uv-action-sheet-title-padding; | ||
224 | + &__title { | ||
225 | + font-size: $uv-action-sheet-title-font-size; | ||
226 | + color: $uv-action-sheet-title-color; | ||
227 | + font-weight: bold; | ||
228 | + text-align: center; | ||
229 | + } | ||
230 | + | ||
231 | + &__icon-wrap { | ||
232 | + position: absolute; | ||
233 | + right: $uv-action-sheet-header-icon-wrap-right; | ||
234 | + top: $uv-action-sheet-header-icon-wrap-top; | ||
235 | + } | ||
236 | + } | ||
237 | + | ||
238 | + &__description { | ||
239 | + font-size: $uv-action-sheet-description-font-size; | ||
240 | + color: $uv-tips-color; | ||
241 | + margin: $uv-action-sheet-description-margin; | ||
242 | + text-align: center; | ||
243 | + } | ||
244 | + | ||
245 | + &__item-wrap { | ||
246 | + | ||
247 | + &__item { | ||
248 | + padding: $uv-action-sheet-item-wrap-item-padding; | ||
249 | + @include flex; | ||
250 | + align-items: center; | ||
251 | + justify-content: center; | ||
252 | + flex-direction: column; | ||
253 | + | ||
254 | + &__name { | ||
255 | + font-size: $uv-action-sheet-item-wrap-name-font-size; | ||
256 | + color: $uv-main-color; | ||
257 | + text-align: center; | ||
258 | + } | ||
259 | + | ||
260 | + &__subname { | ||
261 | + font-size: $uv-action-sheet-item-wrap-subname-font-size; | ||
262 | + color: $uv-action-sheet-item-wrap-subname-color; | ||
263 | + margin-top: $uv-action-sheet-item-wrap-subname-margin-top; | ||
264 | + text-align: center; | ||
265 | + } | ||
266 | + } | ||
267 | + } | ||
268 | + | ||
269 | + &__cancel-text { | ||
270 | + font-size: $uv-action-sheet-cancel-text-font-size; | ||
271 | + color: $uv-action-sheet-cancel-text-color; | ||
272 | + text-align: center; | ||
273 | + padding: $uv-action-sheet-cancel-text-font-size; | ||
274 | + } | ||
275 | + | ||
276 | + &--hover { | ||
277 | + background-color: $uv-action-sheet-cancel-text-hover-background-color; | ||
278 | + } | ||
279 | + } | ||
280 | +</style> |
1 | +<template> | ||
2 | + <view class="uv-album"> | ||
3 | + <view | ||
4 | + class="uv-album__row" | ||
5 | + ref="uv-album__row" | ||
6 | + v-for="(arr, index) in showUrls" | ||
7 | + :forComputedUse="albumWidth" | ||
8 | + :key="index" | ||
9 | + > | ||
10 | + <view | ||
11 | + class="uv-album__row__wrapper" | ||
12 | + v-for="(item, index1) in arr" | ||
13 | + :key="index1" | ||
14 | + :style="[imageStyle(index + 1, index1 + 1)]" | ||
15 | + @tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''" | ||
16 | + > | ||
17 | + <image | ||
18 | + :src="getSrc(item)" | ||
19 | + :mode=" | ||
20 | + urls.length === 1 | ||
21 | + ? imageHeight > 0 | ||
22 | + ? singleMode | ||
23 | + : 'widthFix' | ||
24 | + : multipleMode | ||
25 | + " | ||
26 | + :style="[ | ||
27 | + { | ||
28 | + width: imageWidth, | ||
29 | + height: imageHeight | ||
30 | + } | ||
31 | + ]" | ||
32 | + ></image> | ||
33 | + <view | ||
34 | + v-if=" | ||
35 | + showMore && | ||
36 | + urls.length > rowCount * showUrls.length && | ||
37 | + index === showUrls.length - 1 && | ||
38 | + index1 === showUrls[showUrls.length - 1].length - 1 | ||
39 | + " | ||
40 | + class="uv-album__row__wrapper__text" | ||
41 | + > | ||
42 | + <uv-text | ||
43 | + :text="`+${urls.length - maxCount}`" | ||
44 | + color="#fff" | ||
45 | + :size="multipleSize * 0.3" | ||
46 | + align="center" | ||
47 | + customStyle="justify-content: center" | ||
48 | + ></uv-text> | ||
49 | + </view> | ||
50 | + </view> | ||
51 | + </view> | ||
52 | + </view> | ||
53 | +</template> | ||
54 | +<script> | ||
55 | + import mpMixin from '../../libs/mixin/mpMixin.js' | ||
56 | + import mixin from '../../libs/mixin/mixin.js' | ||
57 | + // #ifdef APP-NVUE | ||
58 | + // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度 | ||
59 | + const dom = uni.requireNativePlugin('dom') | ||
60 | + // #endif | ||
61 | + | ||
62 | + /** | ||
63 | + * Album 相册 | ||
64 | + * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码 | ||
65 | + * @tutorial https://www.uvui.cn/components/album.html | ||
66 | + * @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式 | ||
67 | + * @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址 | ||
68 | + * @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 ) | ||
69 | + * @property {String | Number} multipleSize 多图时,图片边长 (默认 70 ) | ||
70 | + * @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 ) | ||
71 | + * @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' ) | ||
72 | + * @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' ) | ||
73 | + * @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 ) | ||
74 | + * @property {Boolean} previewFullImage 是否可以预览图片 (默认 true ) | ||
75 | + * @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 ) | ||
76 | + * @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true ) | ||
77 | + * | ||
78 | + * @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width ) | ||
79 | + * @example <uv-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></uv-album> | ||
80 | + */ | ||
81 | + export default { | ||
82 | + name: 'uv-album', | ||
83 | + mixins: [mpMixin, mixin], | ||
84 | + emits: ['albumWidth'], | ||
85 | + props: { | ||
86 | + // 图片地址,Array<String>|Array<Object>形式 | ||
87 | + urls: { | ||
88 | + type: Array, | ||
89 | + default: () => [] | ||
90 | + }, | ||
91 | + // 指定从数组的对象元素中读取哪个属性作为图片地址 | ||
92 | + keyName: { | ||
93 | + type: String, | ||
94 | + default: '' | ||
95 | + }, | ||
96 | + // 单图时,图片长边的长度 | ||
97 | + singleSize: { | ||
98 | + type: [String, Number], | ||
99 | + default: 180 | ||
100 | + }, | ||
101 | + // 多图时,图片边长 | ||
102 | + multipleSize: { | ||
103 | + type: [String, Number], | ||
104 | + default: 70 | ||
105 | + }, | ||
106 | + // 多图时,图片水平和垂直之间的间隔 | ||
107 | + space: { | ||
108 | + type: [String, Number], | ||
109 | + default: 6 | ||
110 | + }, | ||
111 | + // 单图时,图片缩放裁剪的模式 | ||
112 | + singleMode: { | ||
113 | + type: String, | ||
114 | + default: 'scaleToFill' | ||
115 | + }, | ||
116 | + // 多图时,图片缩放裁剪的模式 | ||
117 | + multipleMode: { | ||
118 | + type: String, | ||
119 | + default: 'aspectFill' | ||
120 | + }, | ||
121 | + // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量 | ||
122 | + maxCount: { | ||
123 | + type: [String, Number], | ||
124 | + default: 9 | ||
125 | + }, | ||
126 | + // 是否可以预览图片 | ||
127 | + previewFullImage: { | ||
128 | + type: Boolean, | ||
129 | + default: true | ||
130 | + }, | ||
131 | + // 每行展示图片数量,如设置,singleSize和multipleSize将会无效 | ||
132 | + rowCount: { | ||
133 | + type: [String, Number], | ||
134 | + default: 3 | ||
135 | + }, | ||
136 | + // 超出maxCount时是否显示查看更多的提示 | ||
137 | + showMore: { | ||
138 | + type: Boolean, | ||
139 | + default: true | ||
140 | + }, | ||
141 | + ...uni.$uv?.props?.album | ||
142 | + }, | ||
143 | + data() { | ||
144 | + return { | ||
145 | + // 单图的宽度 | ||
146 | + singleWidth: 0, | ||
147 | + // 单图的高度 | ||
148 | + singleHeight: 0, | ||
149 | + // 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比 | ||
150 | + singlePercent: 0.6 | ||
151 | + } | ||
152 | + }, | ||
153 | + watch: { | ||
154 | + urls: { | ||
155 | + immediate: true, | ||
156 | + handler(newVal) { | ||
157 | + if (newVal.length === 1) { | ||
158 | + this.getImageRect() | ||
159 | + } | ||
160 | + } | ||
161 | + } | ||
162 | + }, | ||
163 | + computed: { | ||
164 | + imageStyle() { | ||
165 | + return (index1, index2) => { | ||
166 | + const { space, rowCount, multipleSize, urls } = this; | ||
167 | + const rowLen = this.showUrls.length; | ||
168 | + const allLen = this.urls.length; | ||
169 | + const style = { | ||
170 | + marginRight: this.$uv.addUnit(space), | ||
171 | + marginBottom: this.$uv.addUnit(space) | ||
172 | + } | ||
173 | + // 如果为最后一行,则每个图片都无需下边框 | ||
174 | + if (index1 === rowLen) style.marginBottom = 0 | ||
175 | + // 每行的最右边一张和总长度的最后一张无需右边框 | ||
176 | + if ( | ||
177 | + index2 === rowCount || | ||
178 | + (index1 === rowLen && | ||
179 | + index2 === this.showUrls[index1 - 1].length) | ||
180 | + ) | ||
181 | + style.marginRight = 0 | ||
182 | + return style | ||
183 | + } | ||
184 | + }, | ||
185 | + // 将数组划分为二维数组 | ||
186 | + showUrls() { | ||
187 | + const arr = [] | ||
188 | + this.urls.map((item, index) => { | ||
189 | + // 限制最大展示数量 | ||
190 | + if (index + 1 <= this.maxCount) { | ||
191 | + // 计算该元素为第几个素组内 | ||
192 | + const itemIndex = Math.floor(index / this.rowCount) | ||
193 | + // 判断对应的索引是否存在 | ||
194 | + if (!arr[itemIndex]) { | ||
195 | + arr[itemIndex] = [] | ||
196 | + } | ||
197 | + arr[itemIndex].push(item) | ||
198 | + } | ||
199 | + }) | ||
200 | + return arr | ||
201 | + }, | ||
202 | + imageWidth() { | ||
203 | + return this.$uv.addUnit( | ||
204 | + this.urls.length === 1 ? this.singleWidth : this.multipleSize | ||
205 | + ) | ||
206 | + }, | ||
207 | + imageHeight() { | ||
208 | + return this.$uv.addUnit( | ||
209 | + this.urls.length === 1 ? this.singleHeight : this.multipleSize | ||
210 | + ) | ||
211 | + }, | ||
212 | + // 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度 | ||
213 | + // 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送 | ||
214 | + albumWidth() { | ||
215 | + let width = 0 | ||
216 | + if (this.urls.length === 1) { | ||
217 | + width = this.singleWidth | ||
218 | + } else { | ||
219 | + width = | ||
220 | + this.showUrls[0].length * this.multipleSize + | ||
221 | + this.space * (this.showUrls[0].length - 1) | ||
222 | + } | ||
223 | + this.$emit('albumWidth', width) | ||
224 | + return width | ||
225 | + } | ||
226 | + }, | ||
227 | + methods: { | ||
228 | + // 预览图片 | ||
229 | + onPreviewTap(url) { | ||
230 | + const urls = this.urls.map((item) => { | ||
231 | + return this.getSrc(item) | ||
232 | + }) | ||
233 | + uni.previewImage({ | ||
234 | + current: url, | ||
235 | + urls | ||
236 | + }) | ||
237 | + }, | ||
238 | + // 获取图片的路径 | ||
239 | + getSrc(item) { | ||
240 | + return this.$uv.test.object(item) ? | ||
241 | + (this.keyName && item[this.keyName]) || item.src : | ||
242 | + item | ||
243 | + }, | ||
244 | + // 单图时,获取图片的尺寸 | ||
245 | + // 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸 | ||
246 | + // 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent) | ||
247 | + getImageRect() { | ||
248 | + const src = this.getSrc(this.urls[0]) | ||
249 | + uni.getImageInfo({ | ||
250 | + src, | ||
251 | + success: (res) => { | ||
252 | + // 判断图片横向还是竖向展示方式 | ||
253 | + const isHorizotal = res.width >= res.height | ||
254 | + this.singleWidth = isHorizotal ? | ||
255 | + this.singleSize : | ||
256 | + (res.width / res.height) * this.singleSize | ||
257 | + this.singleHeight = !isHorizotal ? | ||
258 | + this.singleSize : | ||
259 | + (res.height / res.width) * this.singleWidth | ||
260 | + }, | ||
261 | + fail: () => { | ||
262 | + this.getComponentWidth() | ||
263 | + } | ||
264 | + }) | ||
265 | + }, | ||
266 | + // 获取组件的宽度 | ||
267 | + async getComponentWidth() { | ||
268 | + // 延时一定时间,以获取dom尺寸 | ||
269 | + await this.$uv.sleep(30) | ||
270 | + // #ifndef APP-NVUE | ||
271 | + this.$uGetRect('.uv-album__row').then((size) => { | ||
272 | + this.singleWidth = size.width * this.singlePercent | ||
273 | + }) | ||
274 | + // #endif | ||
275 | + | ||
276 | + // #ifdef APP-NVUE | ||
277 | + // 这里ref="uv-album__row"所在的标签为通过for循环出来,导致this.$refs['uv-album__row']是一个数组 | ||
278 | + const ref = this.$refs['uv-album__row'][0] | ||
279 | + ref && | ||
280 | + dom.getComponentRect(ref, (res) => { | ||
281 | + this.singleWidth = res.size.width * this.singlePercent | ||
282 | + }) | ||
283 | + // #endif | ||
284 | + } | ||
285 | + } | ||
286 | + } | ||
287 | +</script> | ||
288 | + | ||
289 | +<style lang="scss" scoped> | ||
290 | + @import '../../libs/css/components.scss'; | ||
291 | + .uv-album { | ||
292 | + @include flex(column); | ||
293 | + &__row { | ||
294 | + @include flex(row); | ||
295 | + flex-wrap: wrap; | ||
296 | + &__wrapper { | ||
297 | + position: relative; | ||
298 | + &__text { | ||
299 | + position: absolute; | ||
300 | + top: 0; | ||
301 | + left: 0; | ||
302 | + right: 0; | ||
303 | + bottom: 0; | ||
304 | + background-color: rgba(0, 0, 0, 0.3); | ||
305 | + @include flex(row); | ||
306 | + justify-content: center; | ||
307 | + align-items: center; | ||
308 | + } | ||
309 | + } | ||
310 | + } | ||
311 | + } | ||
312 | +</style> |
1 | +export default { | ||
2 | + props: { | ||
3 | + // 显示文字 | ||
4 | + title: { | ||
5 | + type: String, | ||
6 | + default: '' | ||
7 | + }, | ||
8 | + // 主题,success/warning/info/error | ||
9 | + type: { | ||
10 | + type: String, | ||
11 | + default: 'warning' | ||
12 | + }, | ||
13 | + // 辅助性文字 | ||
14 | + description: { | ||
15 | + type: String, | ||
16 | + default: '' | ||
17 | + }, | ||
18 | + // 是否可关闭 | ||
19 | + closable: { | ||
20 | + type: Boolean, | ||
21 | + default: false | ||
22 | + }, | ||
23 | + // 是否显示图标 | ||
24 | + showIcon: { | ||
25 | + type: Boolean, | ||
26 | + default: false | ||
27 | + }, | ||
28 | + // 浅或深色调,light-浅色,dark-深色 | ||
29 | + effect: { | ||
30 | + type: String, | ||
31 | + default: 'light' | ||
32 | + }, | ||
33 | + // 文字是否居中 | ||
34 | + center: { | ||
35 | + type: Boolean, | ||
36 | + default: false | ||
37 | + }, | ||
38 | + // 字体大小 | ||
39 | + fontSize: { | ||
40 | + type: [String, Number], | ||
41 | + default: 14 | ||
42 | + }, | ||
43 | + ...uni.$uv?.props?.alert | ||
44 | + } | ||
45 | +} |
1 | +<template> | ||
2 | + <uv-transition | ||
3 | + mode="fade" | ||
4 | + :show="show" | ||
5 | + > | ||
6 | + <view | ||
7 | + class="uv-alert" | ||
8 | + :class="[`uv-alert--${type}--${effect}`]" | ||
9 | + @tap.stop="clickHandler" | ||
10 | + :style="[$uv.addStyle(customStyle)]" | ||
11 | + > | ||
12 | + <view | ||
13 | + class="uv-alert__icon" | ||
14 | + v-if="showIcon" | ||
15 | + > | ||
16 | + <uv-icon | ||
17 | + :name="iconName" | ||
18 | + size="18" | ||
19 | + :color="iconColor" | ||
20 | + ></uv-icon> | ||
21 | + </view> | ||
22 | + <view | ||
23 | + class="uv-alert__content" | ||
24 | + :style="[{ | ||
25 | + paddingRight: closable ? '20px' : 0 | ||
26 | + }]" | ||
27 | + > | ||
28 | + <text | ||
29 | + class="uv-alert__content__title" | ||
30 | + v-if="title" | ||
31 | + :style="[{ | ||
32 | + fontSize: $uv.addUnit(fontSize), | ||
33 | + textAlign: center ? 'center' : 'left' | ||
34 | + }]" | ||
35 | + :class="[effect === 'dark' ? 'uv-alert__text--dark' : `uv-alert__text--${type}--light`]" | ||
36 | + >{{ title }}</text> | ||
37 | + <text | ||
38 | + class="uv-alert__content__desc" | ||
39 | + v-if="description" | ||
40 | + :style="[{ | ||
41 | + fontSize: $uv.addUnit(fontSize), | ||
42 | + textAlign: center ? 'center' : 'left' | ||
43 | + }]" | ||
44 | + :class="[effect === 'dark' ? 'uv-alert__text--dark' : `uv-alert__text--${type}--light`]" | ||
45 | + >{{ description }}</text> | ||
46 | + </view> | ||
47 | + <view | ||
48 | + class="uv-alert__close" | ||
49 | + v-if="closable" | ||
50 | + @tap.stop="closeHandler" | ||
51 | + > | ||
52 | + <uv-icon | ||
53 | + name="close" | ||
54 | + :color="iconColor" | ||
55 | + size="15" | ||
56 | + ></uv-icon> | ||
57 | + </view> | ||
58 | + </view> | ||
59 | + </uv-transition> | ||
60 | +</template> | ||
61 | + | ||
62 | +<script> | ||
63 | + import mpMixin from '../../libs/mixin/mpMixin.js' | ||
64 | + import mixin from '../../libs/mixin/mixin.js' | ||
65 | + import props from './props.js'; | ||
66 | + /** | ||
67 | + * Alert 警告提示 | ||
68 | + * @description 警告提示,展现需要关注的信息。 | ||
69 | + * @tutorial https://www.uvui.cn/components/alertTips.html | ||
70 | + * | ||
71 | + * @property {String} title 显示的文字 | ||
72 | + * @property {String} type 使用预设的颜色 (默认 'warning' ) | ||
73 | + * @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选 | ||
74 | + * @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false ) | ||
75 | + * @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false ) | ||
76 | + * @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' ) | ||
77 | + * @property {Boolean} center 文字是否居中 (默认 false ) | ||
78 | + * @property {String | Number} fontSize 字体大小 (默认 14 ) | ||
79 | + * @property {Object} customStyle 定义需要用到的外部样式 | ||
80 | + * @event {Function} click 点击组件时触发 | ||
81 | + * @example <uv-alert :title="title" type = "warning" :closable="closable" :description = "description"></uv-alert> | ||
82 | + */ | ||
83 | + export default { | ||
84 | + name: 'uv-alert', | ||
85 | + mixins: [mpMixin, mixin, props], | ||
86 | + emits: ['click'], | ||
87 | + data() { | ||
88 | + return { | ||
89 | + show: true | ||
90 | + } | ||
91 | + }, | ||
92 | + computed: { | ||
93 | + iconColor() { | ||
94 | + return this.effect === 'light' ? this.type : '#fff' | ||
95 | + }, | ||
96 | + // 不同主题对应不同的图标 | ||
97 | + iconName() { | ||
98 | + switch (this.type) { | ||
99 | + case 'success': | ||
100 | + return 'checkmark-circle-fill'; | ||
101 | + break; | ||
102 | + case 'error': | ||
103 | + return 'close-circle-fill'; | ||
104 | + break; | ||
105 | + case 'warning': | ||
106 | + return 'error-circle-fill'; | ||
107 | + break; | ||
108 | + case 'info': | ||
109 | + return 'info-circle-fill'; | ||
110 | + break; | ||
111 | + case 'primary': | ||
112 | + return 'more-circle-fill'; | ||
113 | + break; | ||
114 | + default: | ||
115 | + return 'error-circle-fill'; | ||
116 | + } | ||
117 | + } | ||
118 | + }, | ||
119 | + methods: { | ||
120 | + // 点击内容 | ||
121 | + clickHandler() { | ||
122 | + this.$emit('click') | ||
123 | + }, | ||
124 | + // 点击关闭按钮 | ||
125 | + closeHandler() { | ||
126 | + this.show = false | ||
127 | + } | ||
128 | + } | ||
129 | + } | ||
130 | +</script> | ||
131 | + | ||
132 | +<style lang="scss" scoped> | ||
133 | + @import '../../libs/css/components.scss'; | ||
134 | + @import '../../libs/css/color.scss'; | ||
135 | + .uv-alert { | ||
136 | + position: relative; | ||
137 | + background-color: $uv-primary; | ||
138 | + padding: 8px 10px; | ||
139 | + @include flex(row); | ||
140 | + align-items: center; | ||
141 | + border-top-left-radius: 4px; | ||
142 | + border-top-right-radius: 4px; | ||
143 | + border-bottom-left-radius: 4px; | ||
144 | + border-bottom-right-radius: 4px; | ||
145 | + | ||
146 | + &--primary--dark { | ||
147 | + background-color: $uv-primary; | ||
148 | + } | ||
149 | + | ||
150 | + &--primary--light { | ||
151 | + background-color: #ecf5ff; | ||
152 | + } | ||
153 | + | ||
154 | + &--error--dark { | ||
155 | + background-color: $uv-error; | ||
156 | + } | ||
157 | + | ||
158 | + &--error--light { | ||
159 | + background-color: #FEF0F0; | ||
160 | + } | ||
161 | + | ||
162 | + &--success--dark { | ||
163 | + background-color: $uv-success; | ||
164 | + } | ||
165 | + | ||
166 | + &--success--light { | ||
167 | + background-color: #f5fff0; | ||
168 | + } | ||
169 | + | ||
170 | + &--warning--dark { | ||
171 | + background-color: $uv-warning; | ||
172 | + } | ||
173 | + | ||
174 | + &--warning--light { | ||
175 | + background-color: #FDF6EC; | ||
176 | + } | ||
177 | + | ||
178 | + &--info--dark { | ||
179 | + background-color: $uv-info; | ||
180 | + } | ||
181 | + | ||
182 | + &--info--light { | ||
183 | + background-color: #f4f4f5; | ||
184 | + } | ||
185 | + | ||
186 | + &__icon { | ||
187 | + margin-right: 5px; | ||
188 | + } | ||
189 | + | ||
190 | + &__content { | ||
191 | + @include flex(column); | ||
192 | + flex: 1; | ||
193 | + | ||
194 | + &__title { | ||
195 | + color: $uv-main-color; | ||
196 | + font-size: 14px; | ||
197 | + font-weight: bold; | ||
198 | + color: #fff; | ||
199 | + margin-bottom: 2px; | ||
200 | + } | ||
201 | + | ||
202 | + &__desc { | ||
203 | + color: $uv-main-color; | ||
204 | + font-size: 14px; | ||
205 | + flex-wrap: wrap; | ||
206 | + color: #fff; | ||
207 | + } | ||
208 | + } | ||
209 | + | ||
210 | + &__title--dark, | ||
211 | + &__desc--dark { | ||
212 | + color: #FFFFFF; | ||
213 | + } | ||
214 | + | ||
215 | + &__text--primary--light, | ||
216 | + &__text--primary--light { | ||
217 | + color: $uv-primary; | ||
218 | + } | ||
219 | + | ||
220 | + &__text--success--light, | ||
221 | + &__text--success--light { | ||
222 | + color: $uv-success; | ||
223 | + } | ||
224 | + | ||
225 | + &__text--warning--light, | ||
226 | + &__text--warning--light { | ||
227 | + color: $uv-warning; | ||
228 | + } | ||
229 | + | ||
230 | + &__text--error--light, | ||
231 | + &__text--error--light { | ||
232 | + color: $uv-error; | ||
233 | + } | ||
234 | + | ||
235 | + &__text--info--light, | ||
236 | + &__text--info--light { | ||
237 | + color: $uv-info; | ||
238 | + } | ||
239 | + | ||
240 | + &__close { | ||
241 | + position: absolute; | ||
242 | + top: 11px; | ||
243 | + right: 10px; | ||
244 | + } | ||
245 | + } | ||
246 | +</style> |
1 | +export default { | ||
2 | + props: { | ||
3 | + // 头像图片组 | ||
4 | + urls: { | ||
5 | + type: Array, | ||
6 | + default: () => [] | ||
7 | + }, | ||
8 | + // 最多展示的头像数量 | ||
9 | + maxCount: { | ||
10 | + type: [String, Number], | ||
11 | + default: 5 | ||
12 | + }, | ||
13 | + // 头像形状 | ||
14 | + shape: { | ||
15 | + type: String, | ||
16 | + default: 'circle' | ||
17 | + }, | ||
18 | + // 图片裁剪模式 | ||
19 | + mode: { | ||
20 | + type: String, | ||
21 | + default: 'scaleToFill' | ||
22 | + }, | ||
23 | + // 超出maxCount时是否显示查看更多的提示 | ||
24 | + showMore: { | ||
25 | + type: Boolean, | ||
26 | + default: true | ||
27 | + }, | ||
28 | + // 头像大小 | ||
29 | + size: { | ||
30 | + type: [String, Number], | ||
31 | + default: 40 | ||
32 | + }, | ||
33 | + // 指定从数组的对象元素中读取哪个属性作为图片地址 | ||
34 | + keyName: { | ||
35 | + type: String, | ||
36 | + default: '' | ||
37 | + }, | ||
38 | + // 头像之间的遮挡比例 | ||
39 | + gap: { | ||
40 | + type: [String, Number], | ||
41 | + validator(value) { | ||
42 | + return value >= 0 && value <= 1 | ||
43 | + }, | ||
44 | + default: 0.5 | ||
45 | + }, | ||
46 | + // 需额外显示的值 | ||
47 | + extraValue: { | ||
48 | + type: [Number, String], | ||
49 | + default: 0 | ||
50 | + }, | ||
51 | + ...uni.$uv?.props?.avatarGroup | ||
52 | + } | ||
53 | +} |
1 | +<template> | ||
2 | + <view class="uv-avatar-group"> | ||
3 | + <view | ||
4 | + class="uv-avatar-group__item" | ||
5 | + v-for="(item, index) in showUrl" | ||
6 | + :key="index" | ||
7 | + :style="{ | ||
8 | + marginLeft: index === 0 ? 0 : $uv.addUnit(-size * gap) | ||
9 | + }" | ||
10 | + > | ||
11 | + <uv-avatar | ||
12 | + :size="size" | ||
13 | + :shape="shape" | ||
14 | + :mode="mode" | ||
15 | + :src="$uv.test.object(item) ? keyName && item[keyName] || item.url : item" | ||
16 | + ></uv-avatar> | ||
17 | + <view | ||
18 | + class="uv-avatar-group__item__show-more" | ||
19 | + v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)" | ||
20 | + @tap="clickHandler" | ||
21 | + > | ||
22 | + <uv-text | ||
23 | + color="#ffffff" | ||
24 | + :size="size * 0.4" | ||
25 | + :text="`+${extraValue || urls.length - showUrl.length}`" | ||
26 | + align="center" | ||
27 | + customStyle="justify-content: center" | ||
28 | + ></uv-text> | ||
29 | + </view> | ||
30 | + </view> | ||
31 | + </view> | ||
32 | +</template> | ||
33 | + | ||
34 | +<script> | ||
35 | + import mpMixin from '../../libs/mixin/mpMixin.js' | ||
36 | + import mixin from '../../libs/mixin/mixin.js' | ||
37 | + import props from './props.js'; | ||
38 | + /** | ||
39 | + * AvatarGroup 头像组 | ||
40 | + * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 | ||
41 | + * @tutorial https://www.uvui.cn/components/avatar.html | ||
42 | + * | ||
43 | + * @property {Array} urls 头像图片组 (默认 [] ) | ||
44 | + * @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 ) | ||
45 | + * @property {String} shape 头像形状( 'circle' (默认) | 'square' ) | ||
46 | + * @property {String} mode 图片裁剪模式(默认 'scaleToFill' ) | ||
47 | + * @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true ) | ||
48 | + * @property {String | Number} size 头像大小 (默认 40 ) | ||
49 | + * @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址 | ||
50 | + * @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 ) | ||
51 | + * @property {String | Number} extraValue 需额外显示的值 | ||
52 | + * @event {Function} showMore 头像组更多点击 | ||
53 | + * @example <uv-avatar-group:urls="urls" size="35" gap="0.4" ></uv-avatar-group:urls=> | ||
54 | + */ | ||
55 | + export default { | ||
56 | + name: 'uv-avatar-group', | ||
57 | + mixins: [mpMixin, mixin, props], | ||
58 | + data() { | ||
59 | + return { | ||
60 | + | ||
61 | + } | ||
62 | + }, | ||
63 | + computed: { | ||
64 | + showUrl() { | ||
65 | + return this.urls.slice(0, this.maxCount) | ||
66 | + } | ||
67 | + }, | ||
68 | + methods: { | ||
69 | + clickHandler() { | ||
70 | + this.$emit('showMore') | ||
71 | + } | ||
72 | + }, | ||
73 | + } | ||
74 | +</script> | ||
75 | + | ||
76 | +<style lang="scss" scoped> | ||
77 | + @import '../../libs/css/components.scss'; | ||
78 | + @import '../../libs/css/color.scss'; | ||
79 | + | ||
80 | + .uv-avatar-group { | ||
81 | + @include flex; | ||
82 | + | ||
83 | + &__item { | ||
84 | + margin-left: -10px; | ||
85 | + position: relative; | ||
86 | + | ||
87 | + &--no-indent { | ||
88 | + // 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持 | ||
89 | + margin-left: 0; | ||
90 | + } | ||
91 | + | ||
92 | + &__show-more { | ||
93 | + position: absolute; | ||
94 | + top: 0; | ||
95 | + bottom: 0; | ||
96 | + left: 0; | ||
97 | + right: 0; | ||
98 | + background-color: rgba(0, 0, 0, 0.3); | ||
99 | + @include flex; | ||
100 | + align-items: center; | ||
101 | + justify-content: center; | ||
102 | + border-radius: 100px; | ||
103 | + } | ||
104 | + } | ||
105 | + } | ||
106 | +</style> |
1 | +import { range } from '../../libs/function/test.js' | ||
2 | +export default { | ||
3 | + props: { | ||
4 | + // 头像图片路径(不能为相对路径) | ||
5 | + src: { | ||
6 | + type: String, | ||
7 | + default: '' | ||
8 | + }, | ||
9 | + // 头像形状,circle-圆形,square-方形 | ||
10 | + shape: { | ||
11 | + type: String, | ||
12 | + default: 'circle' | ||
13 | + }, | ||
14 | + // 头像尺寸 | ||
15 | + size: { | ||
16 | + type: [String, Number], | ||
17 | + default: 40 | ||
18 | + }, | ||
19 | + // 裁剪模式 | ||
20 | + mode: { | ||
21 | + type: String, | ||
22 | + default: 'scaleToFill' | ||
23 | + }, | ||
24 | + // 显示的文字 | ||
25 | + text: { | ||
26 | + type: String, | ||
27 | + default: '' | ||
28 | + }, | ||
29 | + // 背景色 | ||
30 | + bgColor: { | ||
31 | + type: String, | ||
32 | + default: '#c0c4cc' | ||
33 | + }, | ||
34 | + // 文字颜色 | ||
35 | + color: { | ||
36 | + type: String, | ||
37 | + default: '#fff' | ||
38 | + }, | ||
39 | + // 文字大小 | ||
40 | + fontSize: { | ||
41 | + type: [String, Number], | ||
42 | + default: 18 | ||
43 | + }, | ||
44 | + // 显示的图标 | ||
45 | + icon: { | ||
46 | + type: String, | ||
47 | + default: '' | ||
48 | + }, | ||
49 | + // 显示小程序头像,只对百度,微信,QQ小程序有效 | ||
50 | + mpAvatar: { | ||
51 | + type: Boolean, | ||
52 | + default: false | ||
53 | + }, | ||
54 | + // 是否使用随机背景色 | ||
55 | + randomBgColor: { | ||
56 | + type: Boolean, | ||
57 | + default: false | ||
58 | + }, | ||
59 | + // 加载失败的默认头像(组件有内置默认图片) | ||
60 | + defaultUrl: { | ||
61 | + type: String, | ||
62 | + default: '' | ||
63 | + }, | ||
64 | + // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间 | ||
65 | + colorIndex: { | ||
66 | + type: [String, Number], | ||
67 | + // 校验参数规则,索引在0-19之间 | ||
68 | + validator(n) { | ||
69 | + return range(n, [0, 19]) || n === '' | ||
70 | + }, | ||
71 | + default: '' | ||
72 | + }, | ||
73 | + // 组件标识符 | ||
74 | + name: { | ||
75 | + type: String, | ||
76 | + default: '' | ||
77 | + }, | ||
78 | + ...uni.$uv?.props?.avatar | ||
79 | + } | ||
80 | +} |
1 | +<template> | ||
2 | + <view | ||
3 | + class="uv-avatar" | ||
4 | + :class="[`uv-avatar--${shape}`]" | ||
5 | + :style="[{ | ||
6 | + backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : $uv.random(0, 19)] : bgColor) : 'transparent', | ||
7 | + width: $uv.addUnit(size), | ||
8 | + height: $uv.addUnit(size), | ||
9 | + }, $uv.addStyle(customStyle)]" | ||
10 | + @tap="clickHandler" | ||
11 | + > | ||
12 | + <slot> | ||
13 | + <!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU --> | ||
14 | + <open-data | ||
15 | + v-if="mpAvatar && allowMp" | ||
16 | + type="userAvatarUrl" | ||
17 | + :style="[{ | ||
18 | + width: $uv.addUnit(size), | ||
19 | + height: $uv.addUnit(size) | ||
20 | + }]" | ||
21 | + /> | ||
22 | + <!-- #endif --> | ||
23 | + <!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU --> | ||
24 | + <template v-if="mpAvatar && allowMp"></template> | ||
25 | + <!-- #endif --> | ||
26 | + <uv-icon | ||
27 | + v-else-if="icon" | ||
28 | + :name="icon" | ||
29 | + :size="fontSize" | ||
30 | + :color="color" | ||
31 | + ></uv-icon> | ||
32 | + <uv-text | ||
33 | + v-else-if="text" | ||
34 | + :text="text" | ||
35 | + :size="fontSize" | ||
36 | + :color="color" | ||
37 | + align="center" | ||
38 | + customStyle="justify-content: center" | ||
39 | + ></uv-text> | ||
40 | + <image | ||
41 | + class="uv-avatar__image" | ||
42 | + v-else | ||
43 | + :class="[`uv-avatar__image--${shape}`]" | ||
44 | + :src="avatarUrl || defaultUrl" | ||
45 | + :mode="mode" | ||
46 | + @error="errorHandler" | ||
47 | + :style="[{ | ||
48 | + width: $uv.addUnit(size), | ||
49 | + height: $uv.addUnit(size) | ||
50 | + }]" | ||
51 | + ></image> | ||
52 | + </slot> | ||
53 | + </view> | ||
54 | +</template> | ||
55 | + | ||
56 | +<script> | ||
57 | + import mpMixin from '../../libs/mixin/mpMixin.js' | ||
58 | + import mixin from '../../libs/mixin/mixin.js' | ||
59 | + import props from './props.js'; | ||
60 | + const base64Avatar = | ||
61 | + "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"; | ||
62 | + /** | ||
63 | + * Avatar 头像 | ||
64 | + * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 | ||
65 | + * @tutorial https://www.uvui.cn/components/avatar.html | ||
66 | + * | ||
67 | + * @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径) | ||
68 | + * @property {String} shape 头像形状 ( circle (默认) | square) | ||
69 | + * @property {String | Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值 (默认 40 ) | ||
70 | + * @property {String} mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值 (默认 'scaleToFill' ) | ||
71 | + * @property {String} text 用文字替代图片,级别优先于src | ||
72 | + * @property {String} bgColor 背景颜色,一般显示文字时用 (默认 '#c0c4cc' ) | ||
73 | + * @property {String} color 文字颜色 (默认 '#ffffff' ) | ||
74 | + * @property {String | Number} fontSize 文字大小 (默认 18 ) | ||
75 | + * @property {String} icon 显示的图标 | ||
76 | + * @property {Boolean} mpAvatar 显示小程序头像,只对百度,微信,QQ小程序有效 (默认 false ) | ||
77 | + * @property {Boolean} randomBgColor 是否使用随机背景色 (默认 false ) | ||
78 | + * @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片) | ||
79 | + * @property {String | Number} colorIndex 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间 | ||
80 | + * @property {String} name 组件标识符 (默认 'level' ) | ||
81 | + * @property {Object} customStyle 定义需要用到的外部样式 | ||
82 | + * | ||
83 | + * @event {Function} click 点击组件时触发 index: 用户传递的标识符 | ||
84 | + * @example <uv-avatar :src="src" mode="square"></uv-avatar> | ||
85 | + */ | ||
86 | + export default { | ||
87 | + name: 'uv-avatar', | ||
88 | + emits: ['click'], | ||
89 | + mixins: [mpMixin, mixin, props], | ||
90 | + data() { | ||
91 | + return { | ||
92 | + // 如果配置randomBgColor参数为true,在图标或者文字的模式下,会随机从中取出一个颜色值当做背景色 | ||
93 | + colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2', | ||
94 | + '#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee', | ||
95 | + '#73d1f1', | ||
96 | + '#80a7dc' | ||
97 | + ], | ||
98 | + avatarUrl: this.src, | ||
99 | + allowMp: false | ||
100 | + } | ||
101 | + }, | ||
102 | + watch: { | ||
103 | + // 监听头像src的变化,赋值给内部的avatarUrl变量,因为图片加载失败时,需要修改图片的src为默认值 | ||
104 | + // 而组件内部不能直接修改props的值,所以需要一个中间变量 | ||
105 | + src: { | ||
106 | + immediate: true, | ||
107 | + handler(newVal) { | ||
108 | + this.avatarUrl = newVal | ||
109 | + // 如果没有传src,则主动触发error事件,用于显示默认的头像,否则src为''空字符等的时候,会无内容展示 | ||
110 | + if(!newVal) { | ||
111 | + this.errorHandler() | ||
112 | + } | ||
113 | + } | ||
114 | + } | ||
115 | + }, | ||
116 | + computed: { | ||
117 | + imageStyle() { | ||
118 | + const style = {} | ||
119 | + return style | ||
120 | + } | ||
121 | + }, | ||
122 | + created() { | ||
123 | + this.init() | ||
124 | + }, | ||
125 | + methods: { | ||
126 | + init() { | ||
127 | + // 目前只有这几个小程序平台具有open-data标签 | ||
128 | + // 其他平台可以通过uni.getUserInfo类似接口获取信息,但是需要弹窗授权(首次),不合符组件逻辑 | ||
129 | + // 故目前自动获取小程序头像只支持这几个平台 | ||
130 | + // #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU | ||
131 | + this.allowMp = true | ||
132 | + // #endif | ||
133 | + }, | ||
134 | + // 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式 | ||
135 | + isImg() { | ||
136 | + return this.src.indexOf('/') !== -1 | ||
137 | + }, | ||
138 | + // 图片加载时失败时触发 | ||
139 | + errorHandler() { | ||
140 | + this.avatarUrl = this.defaultUrl || base64Avatar | ||
141 | + }, | ||
142 | + clickHandler() { | ||
143 | + this.$emit('click', this.name) | ||
144 | + } | ||
145 | + } | ||
146 | + } | ||
147 | +</script> | ||
148 | + | ||
149 | +<style lang="scss" scoped> | ||
150 | + @import '../../libs/css/components.scss'; | ||
151 | + | ||
152 | + .uv-avatar { | ||
153 | + @include flex; | ||
154 | + align-items: center; | ||
155 | + justify-content: center; | ||
156 | + | ||
157 | + &--circle { | ||
158 | + border-radius: 100px; | ||
159 | + } | ||
160 | + | ||
161 | + &--square { | ||
162 | + border-radius: 4px; | ||
163 | + } | ||
164 | + | ||
165 | + &__image { | ||
166 | + &--circle { | ||
167 | + border-radius: 100px; | ||
168 | + } | ||
169 | + | ||
170 | + &--square { | ||
171 | + border-radius: 4px; | ||
172 | + } | ||
173 | + } | ||
174 | + } | ||
175 | +</style> |
1 | +export default { | ||
2 | + props: { | ||
3 | + // 返回顶部的形状,circle-圆形,square-方形 | ||
4 | + mode: { | ||
5 | + type: String, | ||
6 | + default: 'circle' | ||
7 | + }, | ||
8 | + // 自定义图标 | ||
9 | + icon: { | ||
10 | + type: String, | ||
11 | + default: 'arrow-upward' | ||
12 | + }, | ||
13 | + // 提示文字 | ||
14 | + text: { | ||
15 | + type: String, | ||
16 | + default: '' | ||
17 | + }, | ||
18 | + // 返回顶部滚动时间 | ||
19 | + duration: { | ||
20 | + type: [String, Number], | ||
21 | + default: 100 | ||
22 | + }, | ||
23 | + // 滚动距离 | ||
24 | + scrollTop: { | ||
25 | + type: [String, Number], | ||
26 | + default: 0 | ||
27 | + }, | ||
28 | + // 距离顶部多少距离显示,单位px | ||
29 | + top: { | ||
30 | + type: [String, Number], | ||
31 | + default: 400 | ||
32 | + }, | ||
33 | + // 返回顶部按钮到底部的距离,单位px | ||
34 | + bottom: { | ||
35 | + type: [String, Number], | ||
36 | + default: 100 | ||
37 | + }, | ||
38 | + // 返回顶部按钮到右边的距离,单位px | ||
39 | + right: { | ||
40 | + type: [String, Number], | ||
41 | + default: 20 | ||
42 | + }, | ||
43 | + // 层级 | ||
44 | + zIndex: { | ||
45 | + type: [String, Number], | ||
46 | + default: 9 | ||
47 | + }, | ||
48 | + // 图标的样式,对象形式 | ||
49 | + iconStyle: { | ||
50 | + type: Object, | ||
51 | + default: () => ({ | ||
52 | + color: '#909399', | ||
53 | + fontSize: '19px' | ||
54 | + }) | ||
55 | + }, | ||
56 | + ...uni.$uv?.props?.backtop | ||
57 | + } | ||
58 | +} |
1 | +<template> | ||
2 | + <uv-transition mode="fade" :customStyle="backTopStyle" :show="show"> | ||
3 | + <slot> | ||
4 | + <view class="uv-back-top" :style="[contentStyle]" @click="backToTop"> | ||
5 | + <uv-icon :name="icon" :custom-style="iconStyle"></uv-icon> | ||
6 | + <text v-if="text" class="uv-back-top__text">{{text}}</text> | ||
7 | + </view> | ||
8 | + </slot> | ||
9 | + </uv-transition> | ||
10 | +</template> | ||
11 | + | ||
12 | +<script> | ||
13 | + import mpMixin from '../../libs/mixin/mpMixin.js' | ||
14 | + import mixin from '../../libs/mixin/mixin.js' | ||
15 | + import props from './props.js'; | ||
16 | + // #ifdef APP-NVUE | ||
17 | + const dom = weex.requireModule('dom') | ||
18 | + // #endif | ||
19 | + /** | ||
20 | + * backTop 返回顶部 | ||
21 | + * @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。 | ||
22 | + * @tutorial https://www.uvui.cn/components/backTop.html | ||
23 | + * @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' ) | ||
24 | + * @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例 | ||
25 | + * @property {String} text 提示文字 | ||
26 | + * @property {String | Number} duration 返回顶部滚动时间 (默认 100) | ||
27 | + * @property {String | Number} scrollTop 滚动距离 (默认 0 ) | ||
28 | + * @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 ) | ||
29 | + * @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 ) | ||
30 | + * @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 ) | ||
31 | + * @property {String | Number} zIndex 层级 (默认 9 ) | ||
32 | + * @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'}) | ||
33 | + * @property {Object} customStyle 定义需要用到的外部样式 | ||
34 | + * | ||
35 | + * @example <uv-back-top :scrollTop="scrollTop"></uv-back-top> | ||
36 | + */ | ||
37 | + export default { | ||
38 | + name: 'uv-back-top', | ||
39 | + emits: ['click'], | ||
40 | + mixins: [mpMixin, mixin, props], | ||
41 | + computed: { | ||
42 | + backTopStyle() { | ||
43 | + // 动画组件样式 | ||
44 | + const style = { | ||
45 | + bottom: this.$uv.addUnit(this.bottom), | ||
46 | + right: this.$uv.addUnit(this.right), | ||
47 | + width: '40px', | ||
48 | + height: '40px', | ||
49 | + position: 'fixed', | ||
50 | + zIndex: 10, | ||
51 | + } | ||
52 | + return style | ||
53 | + }, | ||
54 | + show() { | ||
55 | + return this.$uv.getPx(this.scrollTop) > this.$uv.getPx(this.top) | ||
56 | + }, | ||
57 | + contentStyle() { | ||
58 | + const style = {} | ||
59 | + let radius = 0 | ||
60 | + // 是否圆形 | ||
61 | + if (this.mode === 'circle') { | ||
62 | + radius = '100px' | ||
63 | + } else { | ||
64 | + radius = '4px' | ||
65 | + } | ||
66 | + // 为了兼容安卓nvue,只能这么分开写 | ||
67 | + style.borderTopLeftRadius = radius | ||
68 | + style.borderTopRightRadius = radius | ||
69 | + style.borderBottomLeftRadius = radius | ||
70 | + style.borderBottomRightRadius = radius | ||
71 | + return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle)) | ||
72 | + } | ||
73 | + }, | ||
74 | + methods: { | ||
75 | + backToTop() { | ||
76 | + // #ifdef APP-NVUE | ||
77 | + if (!this.$parent.$refs['uv-back-top']) { | ||
78 | + this.$uv.error(`nvue页面需要给页面最外层元素设置"ref='uv-back-top'`) | ||
79 | + } | ||
80 | + dom.scrollToElement(this.$parent.$refs['uv-back-top'], { | ||
81 | + offset: 0 | ||
82 | + }) | ||
83 | + // #endif | ||
84 | + | ||
85 | + // #ifndef APP-NVUE | ||
86 | + uni.pageScrollTo({ | ||
87 | + scrollTop: 0, | ||
88 | + duration: this.duration | ||
89 | + }); | ||
90 | + // #endif | ||
91 | + this.$emit('click') | ||
92 | + } | ||
93 | + } | ||
94 | + } | ||
95 | +</script> | ||
96 | + | ||
97 | +<style lang="scss" scoped> | ||
98 | + @import '../../libs/css/components.scss'; | ||
99 | + $uv-back-top-flex: 1 !default; | ||
100 | + $uv-back-top-height: 100% !default; | ||
101 | + $uv-back-top-background-color: #E1E1E1 !default; | ||
102 | + $uv-back-top-tips-font-size: 12px !default; | ||
103 | + .uv-back-top { | ||
104 | + @include flex; | ||
105 | + flex-direction: column; | ||
106 | + align-items: center; | ||
107 | + flex: $uv-back-top-flex; | ||
108 | + height: $uv-back-top-height; | ||
109 | + justify-content: center; | ||
110 | + background-color: $uv-back-top-background-color; | ||
111 | + &__tips { | ||
112 | + font-size: $uv-back-top-tips-font-size; | ||
113 | + transform: scale(0.8); | ||
114 | + } | ||
115 | + } | ||
116 | +</style> |
1 | +export default { | ||
2 | + props: { | ||
3 | + // 是否显示圆点 | ||
4 | + isDot: { | ||
5 | + type: Boolean, | ||
6 | + default: false | ||
7 | + }, | ||
8 | + // 显示的内容 | ||
9 | + value: { | ||
10 | + type: [Number, String], | ||
11 | + default: '' | ||
12 | + }, | ||
13 | + // 是否显示 | ||
14 | + show: { | ||
15 | + type: Boolean, | ||
16 | + default: true | ||
17 | + }, | ||
18 | + // 最大值,超过最大值会显示 '{max}+' | ||
19 | + max: { | ||
20 | + type: [Number, String], | ||
21 | + default: 999 | ||
22 | + }, | ||
23 | + // 主题类型,error|warning|success|primary | ||
24 | + type: { | ||
25 | + type: [String,undefined,null], | ||
26 | + default: 'error' | ||
27 | + }, | ||
28 | + // 当数值为 0 时,是否展示 Badge | ||
29 | + showZero: { | ||
30 | + type: Boolean, | ||
31 | + default: false | ||
32 | + }, | ||
33 | + // 背景颜色,优先级比type高,如设置,type参数会失效 | ||
34 | + bgColor: { | ||
35 | + type: [String, null], | ||
36 | + default: null | ||
37 | + }, | ||
38 | + // 字体颜色 | ||
39 | + color: { | ||
40 | + type: [String, null], | ||
41 | + default: null | ||
42 | + }, | ||
43 | + // 徽标形状,circle-四角均为圆角,horn-左下角为直角 | ||
44 | + shape: { | ||
45 | + type: [String,undefined,null], | ||
46 | + default: 'circle' | ||
47 | + }, | ||
48 | + // 设置数字的显示方式,overflow|ellipsis|limit | ||
49 | + // overflow会根据max字段判断,超出显示`${max}+` | ||
50 | + // ellipsis会根据max判断,超出显示`${max}...` | ||
51 | + // limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数 | ||
52 | + numberType: { | ||
53 | + type: [String,undefined,null], | ||
54 | + default: 'overflow' | ||
55 | + }, | ||
56 | + // 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效 | ||
57 | + offset: { | ||
58 | + type: Array, | ||
59 | + default: () => [] | ||
60 | + }, | ||
61 | + // 是否反转背景和字体颜色 | ||
62 | + inverted: { | ||
63 | + type: Boolean, | ||
64 | + default: false | ||
65 | + }, | ||
66 | + // 是否绝对定位 | ||
67 | + absolute: { | ||
68 | + type: Boolean, | ||
69 | + default: false | ||
70 | + }, | ||
71 | + ...uni.$uv?.props?.badge | ||
72 | + } | ||
73 | +} |
1 | +<template> | ||
2 | + <text | ||
3 | + v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)" | ||
4 | + :class="[isDot ? 'uv-badge--dot' : 'uv-badge--not-dot', inverted && 'uv-badge--inverted', shape === 'horn' && 'uv-badge--horn', `uv-badge--${propsType}${inverted ? '--inverted' : ''}`]" | ||
5 | + :style="[$uv.addStyle(customStyle), badgeStyle]" | ||
6 | + class="uv-badge" | ||
7 | + >{{ isDot ? '' :showValue }}</text> | ||
8 | +</template> | ||
9 | + | ||
10 | +<script> | ||
11 | + import mpMixin from '../../libs/mixin/mpMixin.js' | ||
12 | + import mixin from '../../libs/mixin/mixin.js' | ||
13 | + import props from './props.js'; | ||
14 | + /** | ||
15 | + * badge 徽标数 | ||
16 | + * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。 | ||
17 | + * @tutorial https://www.uvui.cn/components/badge.html | ||
18 | + * | ||
19 | + * @property {Boolean} isDot 是否显示圆点 (默认 false ) | ||
20 | + * @property {String | Number} value 显示的内容 | ||
21 | + * @property {Boolean} show 是否显示 (默认 true ) | ||
22 | + * @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999) | ||
23 | + * @property {String} type 主题类型,error|warning|success|primary (默认 'error' ) | ||
24 | + * @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false ) | ||
25 | + * @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效 | ||
26 | + * @property {String} color 字体颜色 (默认 '#ffffff' ) | ||
27 | + * @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' ) | ||
28 | + * @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' ) | ||
29 | + * @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效 | ||
30 | + * @property {Boolean} inverted 是否反转背景和字体颜色(默认 false ) | ||
31 | + * @property {Boolean} absolute 是否绝对定位(默认 false ) | ||
32 | + * @property {Object} customStyle 定义需要用到的外部样式 | ||
33 | + * @example <uv-badge :type="type" :count="count"></uv-badge> | ||
34 | + */ | ||
35 | + export default { | ||
36 | + name: 'uv-badge', | ||
37 | + mixins: [mpMixin, mixin, props], | ||
38 | + computed: { | ||
39 | + // 是否将badge中心与父组件右上角重合 | ||
40 | + boxStyle() { | ||
41 | + let style = {}; | ||
42 | + return style; | ||
43 | + }, | ||
44 | + // 整个组件的样式 | ||
45 | + badgeStyle() { | ||
46 | + const style = {} | ||
47 | + if(this.color) { | ||
48 | + style.color = this.color | ||
49 | + } | ||
50 | + if (this.bgColor && !this.inverted) { | ||
51 | + style.backgroundColor = this.bgColor | ||
52 | + } | ||
53 | + if (this.absolute) { | ||
54 | + style.position = 'absolute' | ||
55 | + // 如果有设置offset参数 | ||
56 | + if(this.offset.length) { | ||
57 | + // top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top | ||
58 | + const top = this.offset[0] | ||
59 | + const right = this.offset[1] || top | ||
60 | + style.top = this.$uv.addUnit(top) | ||
61 | + style.right = this.$uv.addUnit(right) | ||
62 | + } | ||
63 | + } | ||
64 | + return style | ||
65 | + }, | ||
66 | + showValue() { | ||
67 | + switch (this.numberType) { | ||
68 | + case "overflow": | ||
69 | + return Number(this.value) > Number(this.max) ? this.max + "+" : this.value | ||
70 | + break; | ||
71 | + case "ellipsis": | ||
72 | + return Number(this.value) > Number(this.max) ? "..." : this.value | ||
73 | + break; | ||
74 | + case "limit": | ||
75 | + return Number(this.value) > 999 ? Number(this.value) >= 9999 ? | ||
76 | + Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value / | ||
77 | + 1e3 * 100) / 100 + "k" : this.value | ||
78 | + break; | ||
79 | + default: | ||
80 | + return Number(this.value) | ||
81 | + } | ||
82 | + }, | ||
83 | + propsType(){ | ||
84 | + return this.type || 'error' | ||
85 | + } | ||
86 | + } | ||
87 | + } | ||
88 | +</script> | ||
89 | + | ||
90 | +<style lang="scss" scoped> | ||
91 | + @import '../../libs/css/components.scss'; | ||
92 | + @import '../../libs/css/color.scss'; | ||
93 | + $uv-badge-primary: $uv-primary !default; | ||
94 | + $uv-badge-error: $uv-error !default; | ||
95 | + $uv-badge-success: $uv-success !default; | ||
96 | + $uv-badge-info: $uv-info !default; | ||
97 | + $uv-badge-warning: $uv-warning !default; | ||
98 | + $uv-badge-dot-radius: 100px !default; | ||
99 | + $uv-badge-dot-size: 8px !default; | ||
100 | + $uv-badge-dot-right: 4px !default; | ||
101 | + $uv-badge-dot-top: 0 !default; | ||
102 | + $uv-badge-text-font-size: 11px !default; | ||
103 | + $uv-badge-text-right: 10px !default; | ||
104 | + $uv-badge-text-padding: 2px 5px !default; | ||
105 | + $uv-badge-text-align: center !default; | ||
106 | + $uv-badge-text-color: #FFFFFF !default; | ||
107 | + | ||
108 | + .uv-badge { | ||
109 | + border-top-right-radius: $uv-badge-dot-radius; | ||
110 | + border-top-left-radius: $uv-badge-dot-radius; | ||
111 | + border-bottom-left-radius: $uv-badge-dot-radius; | ||
112 | + border-bottom-right-radius: $uv-badge-dot-radius; | ||
113 | + @include flex; | ||
114 | + line-height: $uv-badge-text-font-size; | ||
115 | + text-align: $uv-badge-text-align; | ||
116 | + font-size: $uv-badge-text-font-size; | ||
117 | + color: $uv-badge-text-color; | ||
118 | + | ||
119 | + &--dot { | ||
120 | + height: $uv-badge-dot-size; | ||
121 | + width: $uv-badge-dot-size; | ||
122 | + } | ||
123 | + | ||
124 | + &--inverted { | ||
125 | + font-size: 13px; | ||
126 | + } | ||
127 | + | ||
128 | + &--not-dot { | ||
129 | + padding: $uv-badge-text-padding; | ||
130 | + } | ||
131 | + | ||
132 | + &--horn { | ||
133 | + border-bottom-left-radius: 0; | ||
134 | + } | ||
135 | + | ||
136 | + &--primary { | ||
137 | + background-color: $uv-badge-primary; | ||
138 | + } | ||
139 | + | ||
140 | + &--primary--inverted { | ||
141 | + color: $uv-badge-primary; | ||
142 | + } | ||
143 | + | ||
144 | + &--error { | ||
145 | + background-color: $uv-badge-error; | ||
146 | + } | ||
147 | + | ||
148 | + &--error--inverted { | ||
149 | + color: $uv-badge-error; | ||
150 | + } | ||
151 | + | ||
152 | + &--success { | ||
153 | + background-color: $uv-badge-success; | ||
154 | + } | ||
155 | + | ||
156 | + &--success--inverted { | ||
157 | + color: $uv-badge-success; | ||
158 | + } | ||
159 | + | ||
160 | + &--info { | ||
161 | + background-color: $uv-badge-info; | ||
162 | + } | ||
163 | + | ||
164 | + &--info--inverted { | ||
165 | + color: $uv-badge-info; | ||
166 | + } | ||
167 | + | ||
168 | + &--warning { | ||
169 | + background-color: $uv-badge-warning; | ||
170 | + } | ||
171 | + | ||
172 | + &--warning--inverted { | ||
173 | + color: $uv-badge-warning; | ||
174 | + } | ||
175 | + } | ||
176 | +</style> |
1 | +$uv-button-active-opacity:0.75 !default; | ||
2 | +$uv-button-loading-text-margin-left:4px !default; | ||
3 | +$uv-button-text-color: #FFFFFF !default; | ||
4 | +$uv-button-text-plain-error-color:$uv-error !default; | ||
5 | +$uv-button-text-plain-warning-color:$uv-warning !default; | ||
6 | +$uv-button-text-plain-success-color:$uv-success !default; | ||
7 | +$uv-button-text-plain-info-color:$uv-info !default; | ||
8 | +$uv-button-text-plain-primary-color:$uv-primary !default; | ||
9 | +.uv-button { | ||
10 | + &--active { | ||
11 | + opacity: $uv-button-active-opacity; | ||
12 | + } | ||
13 | + | ||
14 | + &--active--plain { | ||
15 | + background-color: rgb(217, 217, 217); | ||
16 | + } | ||
17 | + | ||
18 | + &__loading-text { | ||
19 | + margin-left:$uv-button-loading-text-margin-left; | ||
20 | + } | ||
21 | + | ||
22 | + &__text, | ||
23 | + &__loading-text { | ||
24 | + color:$uv-button-text-color; | ||
25 | + } | ||
26 | + | ||
27 | + &__text--plain--error { | ||
28 | + color:$uv-button-text-plain-error-color; | ||
29 | + } | ||
30 | + | ||
31 | + &__text--plain--warning { | ||
32 | + color:$uv-button-text-plain-warning-color; | ||
33 | + } | ||
34 | + | ||
35 | + &__text--plain--success{ | ||
36 | + color:$uv-button-text-plain-success-color; | ||
37 | + } | ||
38 | + | ||
39 | + &__text--plain--info { | ||
40 | + color:$uv-button-text-plain-info-color; | ||
41 | + } | ||
42 | + | ||
43 | + &__text--plain--primary { | ||
44 | + color:$uv-button-text-plain-primary-color; | ||
45 | + } | ||
46 | +} |
1 | +export default { | ||
2 | + props: { | ||
3 | + // 是否细边框 | ||
4 | + hairline: { | ||
5 | + type: Boolean, | ||
6 | + default: true | ||
7 | + }, | ||
8 | + // 按钮的预置样式,info,primary,error,warning,success | ||
9 | + type: { | ||
10 | + type: String, | ||
11 | + default: 'info' | ||
12 | + }, | ||
13 | + // 按钮尺寸,large,normal,small,mini | ||
14 | + size: { | ||
15 | + type: String, | ||
16 | + default: 'normal' | ||
17 | + }, | ||
18 | + // 按钮形状,circle(两边为半圆),square(带圆角) | ||
19 | + shape: { | ||
20 | + type: String, | ||
21 | + default: 'square' | ||
22 | + }, | ||
23 | + // 按钮是否镂空 | ||
24 | + plain: { | ||
25 | + type: Boolean, | ||
26 | + default: false | ||
27 | + }, | ||
28 | + // 是否禁止状态 | ||
29 | + disabled: { | ||
30 | + type: Boolean, | ||
31 | + default: false | ||
32 | + }, | ||
33 | + // 是否加载中 | ||
34 | + loading: { | ||
35 | + type: Boolean, | ||
36 | + default: false | ||
37 | + }, | ||
38 | + // 加载中提示文字 | ||
39 | + loadingText: { | ||
40 | + type: [String, Number], | ||
41 | + default: '' | ||
42 | + }, | ||
43 | + // 加载状态图标类型 | ||
44 | + loadingMode: { | ||
45 | + type: String, | ||
46 | + default: 'spinner' | ||
47 | + }, | ||
48 | + // 加载图标大小 | ||
49 | + loadingSize: { | ||
50 | + type: [String, Number], | ||
51 | + default: 14 | ||
52 | + }, | ||
53 | + // 开放能力,具体请看uniapp稳定关于button组件部分说明 | ||
54 | + // https://uniapp.dcloud.io/component/button | ||
55 | + openType: { | ||
56 | + type: String, | ||
57 | + default: '' | ||
58 | + }, | ||
59 | + // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 | ||
60 | + // 取值为submit(提交表单),reset(重置表单) | ||
61 | + formType: { | ||
62 | + type: String, | ||
63 | + default: '' | ||
64 | + }, | ||
65 | + // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 | ||
66 | + // 只微信小程序、QQ小程序有效 | ||
67 | + appParameter: { | ||
68 | + type: String, | ||
69 | + default: '' | ||
70 | + }, | ||
71 | + // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效 | ||
72 | + hoverStopPropagation: { | ||
73 | + type: Boolean, | ||
74 | + default: true | ||
75 | + }, | ||
76 | + // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效 | ||
77 | + lang: { | ||
78 | + type: String, | ||
79 | + default: 'en' | ||
80 | + }, | ||
81 | + // 会话来源,open-type="contact"时有效。只微信小程序有效 | ||
82 | + sessionFrom: { | ||
83 | + type: String, | ||
84 | + default: '' | ||
85 | + }, | ||
86 | + // 会话内消息卡片标题,open-type="contact"时有效 | ||
87 | + // 默认当前标题,只微信小程序有效 | ||
88 | + sendMessageTitle: { | ||
89 | + type: String, | ||
90 | + default: '' | ||
91 | + }, | ||
92 | + // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效 | ||
93 | + // 默认当前分享路径,只微信小程序有效 | ||
94 | + sendMessagePath: { | ||
95 | + type: String, | ||
96 | + default: '' | ||
97 | + }, | ||
98 | + // 会话内消息卡片图片,open-type="contact"时有效 | ||
99 | + // 默认当前页面截图,只微信小程序有效 | ||
100 | + sendMessageImg: { | ||
101 | + type: String, | ||
102 | + default: '' | ||
103 | + }, | ||
104 | + // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示, | ||
105 | + // 用户点击后可以快速发送小程序消息,open-type="contact"时有效 | ||
106 | + showMessageCard: { | ||
107 | + type: Boolean, | ||
108 | + default: true | ||
109 | + }, | ||
110 | + // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 | ||
111 | + dataName: { | ||
112 | + type: String, | ||
113 | + default: '' | ||
114 | + }, | ||
115 | + // 节流,一定时间内只能触发一次 | ||
116 | + throttleTime: { | ||
117 | + type: [String, Number], | ||
118 | + default: 0 | ||
119 | + }, | ||
120 | + // 按住后多久出现点击态,单位毫秒 | ||
121 | + hoverStartTime: { | ||
122 | + type: [String, Number], | ||
123 | + default: 0 | ||
124 | + }, | ||
125 | + // 手指松开后点击态保留时间,单位毫秒 | ||
126 | + hoverStayTime: { | ||
127 | + type: [String, Number], | ||
128 | + default: 200 | ||
129 | + }, | ||
130 | + // 按钮文字,之所以通过props传入,是因为slot传入的话 | ||
131 | + // nvue中无法控制文字的样式 | ||
132 | + text: { | ||
133 | + type: [String, Number], | ||
134 | + default: '' | ||
135 | + }, | ||
136 | + // 按钮图标 | ||
137 | + icon: { | ||
138 | + type: String, | ||
139 | + default: '' | ||
140 | + }, | ||
141 | + // 按钮图标大小 | ||
142 | + iconSize: { | ||
143 | + type: [String, Number], | ||
144 | + default: '' | ||
145 | + }, | ||
146 | + // 按钮图标颜色 | ||
147 | + iconColor: { | ||
148 | + type: String, | ||
149 | + default: '#000000' | ||
150 | + }, | ||
151 | + // 按钮颜色,支持传入linear-gradient渐变色 | ||
152 | + color: { | ||
153 | + type: String, | ||
154 | + default: '' | ||
155 | + }, | ||
156 | + // 自定义按钮文本样式 | ||
157 | + customTextStyle: { | ||
158 | + type: [Object,String], | ||
159 | + default: ()=>{} | ||
160 | + }, | ||
161 | + ...uni.$uv?.props?.button | ||
162 | + } | ||
163 | +} |
1 | +<template> | ||
2 | + <view class="uv-button-wrapper"> | ||
3 | + <!-- #ifndef APP-NVUE --> | ||
4 | + <!-- #ifdef MP --> | ||
5 | + <!-- 为了解决微信小程序动态设置hover-class点击态不消失的BUG --> | ||
6 | + <view class="uv-button-wrapper--dis" v-if="disabled || loading"></view> | ||
7 | + <button | ||
8 | + :hover-start-time="Number(hoverStartTime)" | ||
9 | + :hover-stay-time="Number(hoverStayTime)" | ||
10 | + :form-type="formType" | ||
11 | + :open-type="openType" | ||
12 | + :app-parameter="appParameter" | ||
13 | + :hover-stop-propagation="hoverStopPropagation" | ||
14 | + :send-message-title="sendMessageTitle" | ||
15 | + :send-message-path="sendMessagePath" | ||
16 | + :lang="lang" | ||
17 | + :data-name="dataName" | ||
18 | + :session-from="sessionFrom" | ||
19 | + :send-message-img="sendMessageImg" | ||
20 | + :show-message-card="showMessageCard" | ||
21 | + @getphonenumber="onGetPhoneNumber" | ||
22 | + @getuserinfo="onGetUserInfo" | ||
23 | + @error="onError" | ||
24 | + @opensetting="onOpenSetting" | ||
25 | + @launchapp="onLaunchApp" | ||
26 | + @contact="onContact" | ||
27 | + @chooseavatar="onChooseavatar" | ||
28 | + @agreeprivacyauthorization="onAgreeprivacyauthorization" | ||
29 | + @addgroupapp="onAddgroupapp" | ||
30 | + @chooseaddress="onChooseaddress" | ||
31 | + @subscribe="onSubscribe" | ||
32 | + @login="onLogin" | ||
33 | + @im="onIm" | ||
34 | + hover-class="uv-button--active" | ||
35 | + class="uv-button uv-reset-button" | ||
36 | + :style="[baseColor, $uv.addStyle(customStyle)]" | ||
37 | + @tap="clickHandler" | ||
38 | + :class="bemClass" | ||
39 | + > | ||
40 | + <!-- #endif --> | ||
41 | + <!-- #ifndef MP --> | ||
42 | + <button | ||
43 | + :hover-start-time="Number(hoverStartTime)" | ||
44 | + :hover-stay-time="Number(hoverStayTime)" | ||
45 | + :form-type="formType" | ||
46 | + :open-type="openType" | ||
47 | + :app-parameter="appParameter" | ||
48 | + :hover-stop-propagation="hoverStopPropagation" | ||
49 | + :send-message-title="sendMessageTitle" | ||
50 | + :send-message-path="sendMessagePath" | ||
51 | + :lang="lang" | ||
52 | + :data-name="dataName" | ||
53 | + :session-from="sessionFrom" | ||
54 | + :send-message-img="sendMessageImg" | ||
55 | + :show-message-card="showMessageCard" | ||
56 | + :hover-class="!disabled && !loading ? 'uv-button--active' : ''" | ||
57 | + class="uv-button uv-reset-button" | ||
58 | + :style="[baseColor, $uv.addStyle(customStyle)]" | ||
59 | + @tap="clickHandler" | ||
60 | + :class="bemClass" | ||
61 | + > | ||
62 | + <!-- #endif --> | ||
63 | + <template v-if="loading"> | ||
64 | + <uv-loading-icon | ||
65 | + :mode="loadingMode" | ||
66 | + :size="loadingSize * 1.15" | ||
67 | + :color="loadingColor" | ||
68 | + ></uv-loading-icon> | ||
69 | + <text | ||
70 | + class="uv-button__loading-text" | ||
71 | + :style="[ | ||
72 | + { fontSize: textSize + 'px' }, | ||
73 | + $uv.addStyle(customTextStyle) | ||
74 | + ]" | ||
75 | + >{{ loadingText || text }}</text> | ||
76 | + </template> | ||
77 | + <template v-else> | ||
78 | + <uv-icon | ||
79 | + v-if="icon" | ||
80 | + :name="icon" | ||
81 | + :color="iconColorCom" | ||
82 | + :size="getIconSize" | ||
83 | + :customStyle="{ marginRight: '2px' }" | ||
84 | + ></uv-icon> | ||
85 | + <slot> | ||
86 | + <text | ||
87 | + class="uv-button__text" | ||
88 | + :style="[ | ||
89 | + { fontSize: textSize + 'px' }, | ||
90 | + $uv.addStyle(customTextStyle) | ||
91 | + ]" | ||
92 | + >{{ text }}</text> | ||
93 | + </slot> | ||
94 | + </template> | ||
95 | + </button> | ||
96 | + <!-- #endif --> | ||
97 | + <!-- #ifdef APP-NVUE --> | ||
98 | + <view | ||
99 | + :hover-start-time="Number(hoverStartTime)" | ||
100 | + :hover-stay-time="Number(hoverStayTime)" | ||
101 | + class="uv-button" | ||
102 | + :hover-class=" | ||
103 | + !disabled && !loading && !color && (plain || type === 'info') | ||
104 | + ? 'uv-button--active--plain' | ||
105 | + : !disabled && !loading && !plain | ||
106 | + ? 'uv-button--active' | ||
107 | + : '' | ||
108 | + " | ||
109 | + @tap="clickHandler" | ||
110 | + :class="bemClass" | ||
111 | + :style="[baseColor, $uv.addStyle(customStyle)]" | ||
112 | + > | ||
113 | + <template v-if="loading"> | ||
114 | + <uv-loading-icon | ||
115 | + :mode="loadingMode" | ||
116 | + :size="loadingSize * 1.15" | ||
117 | + :color="loadingColor" | ||
118 | + ></uv-loading-icon> | ||
119 | + <text | ||
120 | + class="uv-button__loading-text" | ||
121 | + :style="[nvueTextStyle,$uv.addStyle(customTextStyle)]" | ||
122 | + :class="[plain && `uv-button__text--plain--${type}`]" | ||
123 | + >{{ loadingText || text }}</text> | ||
124 | + </template> | ||
125 | + <template v-else> | ||
126 | + <uv-icon | ||
127 | + v-if="icon" | ||
128 | + :name="icon" | ||
129 | + :color="iconColorCom" | ||
130 | + :size="getIconSize" | ||
131 | + ></uv-icon> | ||
132 | + <text | ||
133 | + class="uv-button__text" | ||
134 | + :style="[ | ||
135 | + { | ||
136 | + marginLeft: icon ? '2px' : 0, | ||
137 | + }, | ||
138 | + nvueTextStyle, | ||
139 | + $uv.addStyle(customTextStyle) | ||
140 | + ]" | ||
141 | + :class="[plain && `uv-button__text--plain--${type}`]" | ||
142 | + >{{ text }}</text> | ||
143 | + </template> | ||
144 | + </view> | ||
145 | + <!-- #endif --> | ||
146 | + </view> | ||
147 | +</template> | ||
148 | + | ||
149 | +<script> | ||
150 | +import throttle from '../../libs/function/throttle.js'; | ||
151 | +import mpMixin from '../../libs/mixin/mpMixin.js' | ||
152 | +import mixin from '../../libs/mixin/mixin.js' | ||
153 | +import button from '../../libs/mixin/button.js' | ||
154 | +import openType from '../../libs/mixin/openType.js' | ||
155 | +import props from "./props.js"; | ||
156 | +/** | ||
157 | + * button 按钮 | ||
158 | + * @description Button 按钮 | ||
159 | + * @tutorial https://www.uvui.cn/components/button.html | ||
160 | + * @property {Boolean} hairline 是否显示按钮的细边框 (默认 true ) | ||
161 | + * @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' ) | ||
162 | + * @property {String} size 按钮尺寸,large,normal,mini (默认 normal) | ||
163 | + * @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' ) | ||
164 | + * @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false) | ||
165 | + * @property {Boolean} disabled 是否禁用 (默认 false) | ||
166 | + * @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false) | ||
167 | + * @property {String | Number} loadingText 加载中提示文字 | ||
168 | + * @property {String} loadingMode 加载状态图标类型 (默认 'spinner' ) | ||
169 | + * @property {String | Number} loadingSize 加载图标大小 (默认 15 ) | ||
170 | + * @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明 | ||
171 | + * @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 | ||
172 | + * @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效) | ||
173 | + * @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true ) | ||
174 | + * @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en ) | ||
175 | + * @property {String} sessionFrom 会话来源,openType="contact"时有效 | ||
176 | + * @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效 | ||
177 | + * @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效 | ||
178 | + * @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效 | ||
179 | + * @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false) | ||
180 | + * @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 | ||
181 | + * @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 ) | ||
182 | + * @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 ) | ||
183 | + * @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 ) | ||
184 | + * @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式) | ||
185 | + * @property {String} icon 按钮图标 | ||
186 | + * @property {String} iconColor 按钮图标颜色 | ||
187 | + * @property {String} color 按钮颜色,支持传入linear-gradient渐变色 | ||
188 | + * @property {Object} customStyle 定义需要用到的外部样式 | ||
189 | + * @event {Function} click 非禁止并且非加载中,才能点击 | ||
190 | + * @event {Function} getphonenumber open-type="getPhoneNumber"时有效 | ||
191 | + * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo | ||
192 | + * @event {Function} error 当使用开放能力时,发生错误的回调 | ||
193 | + * @event {Function} opensetting 在打开授权设置页并关闭后回调 | ||
194 | + * @event {Function} launchapp 打开 APP 成功的回调 | ||
195 | + * @example <uv-button>月落</uv-button> | ||
196 | + */ | ||
197 | +export default { | ||
198 | + name: "uv-button", | ||
199 | + // #ifdef MP | ||
200 | + mixins: [mpMixin, mixin, button, openType, props], | ||
201 | + // #endif | ||
202 | + // #ifndef MP | ||
203 | + mixins: [mpMixin, mixin, props], | ||
204 | + // #endif | ||
205 | + emits: ['click'], | ||
206 | + data() { | ||
207 | + return {}; | ||
208 | + }, | ||
209 | + computed: { | ||
210 | + // 生成bem风格的类名 | ||
211 | + bemClass() { | ||
212 | + // this.bem为一个computed变量,在mixin中 | ||
213 | + if (!this.color) { | ||
214 | + return this.bem("button", | ||
215 | + ["type", "shape", "size"], | ||
216 | + ["disabled", "plain", "hairline"]); | ||
217 | + } else { | ||
218 | + // 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式 | ||
219 | + return this.bem("button", | ||
220 | + ["shape", "size"], | ||
221 | + ["disabled", "plain", "hairline"]); | ||
222 | + } | ||
223 | + }, | ||
224 | + loadingColor() { | ||
225 | + if (this.plain) { | ||
226 | + // 如果有设置color值,则用color值,否则使用type主题颜色 | ||
227 | + return this.color ? this.color : '#3c9cff'; | ||
228 | + } | ||
229 | + if (this.type === "info") { | ||
230 | + return "#c9c9c9"; | ||
231 | + } | ||
232 | + return "rgb(200, 200, 200)"; | ||
233 | + }, | ||
234 | + iconColorCom() { | ||
235 | + // 如果是镂空状态,设置了color就用color值,否则使用主题颜色, | ||
236 | + // uv-icon的color能接受一个主题颜色的值 | ||
237 | + if (this.iconColor) return this.iconColor; | ||
238 | + if (this.plain) { | ||
239 | + return this.color ? this.color : this.type; | ||
240 | + } else { | ||
241 | + return this.type === "info" ? "#000000" : "#ffffff"; | ||
242 | + } | ||
243 | + }, | ||
244 | + baseColor() { | ||
245 | + let style = {}; | ||
246 | + if (this.color) { | ||
247 | + // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色 | ||
248 | + style.color = this.plain ? this.color : "white"; | ||
249 | + if (!this.plain) { | ||
250 | + // 非镂空,背景色使用自定义的颜色 | ||
251 | + style["background-color"] = this.color; | ||
252 | + } | ||
253 | + if (this.color.indexOf("gradient") !== -1) { | ||
254 | + // 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色 | ||
255 | + // weex文档说明可以写borderWidth的形式,为什么这里需要分开写? | ||
256 | + // 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效 | ||
257 | + style.borderTopWidth = 0; | ||
258 | + style.borderRightWidth = 0; | ||
259 | + style.borderBottomWidth = 0; | ||
260 | + style.borderLeftWidth = 0; | ||
261 | + if (!this.plain) { | ||
262 | + style.backgroundImage = this.color; | ||
263 | + } | ||
264 | + } else { | ||
265 | + // 非渐变色,则设置边框相关的属性 | ||
266 | + style.borderColor = this.color; | ||
267 | + style.borderWidth = "1px"; | ||
268 | + style.borderStyle = "solid"; | ||
269 | + } | ||
270 | + } | ||
271 | + return style; | ||
272 | + }, | ||
273 | + // nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置 | ||
274 | + nvueTextStyle() { | ||
275 | + let style = {}; | ||
276 | + // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色 | ||
277 | + if (this.type === "info") { | ||
278 | + style.color = "#323233"; | ||
279 | + } | ||
280 | + if (this.color) { | ||
281 | + style.color = this.plain ? this.color : "white"; | ||
282 | + } | ||
283 | + style.fontSize = this.textSize + "px"; | ||
284 | + return style; | ||
285 | + }, | ||
286 | + // 字体大小 | ||
287 | + textSize() { | ||
288 | + let fontSize = 14, | ||
289 | + { size } = this; | ||
290 | + if (size === "large") fontSize = 16; | ||
291 | + if (size === "normal") fontSize = 14; | ||
292 | + if (size === "small") fontSize = 12; | ||
293 | + if (size === "mini") fontSize = 10; | ||
294 | + return fontSize; | ||
295 | + }, | ||
296 | + // 设置图标大小 | ||
297 | + getIconSize() { | ||
298 | + const size = this.iconSize ? this.iconSize : this.textSize * 1.35; | ||
299 | + return this.$uv.addUnit(size); | ||
300 | + } | ||
301 | + }, | ||
302 | + methods: { | ||
303 | + clickHandler() { | ||
304 | + // 非禁止并且非加载中,才能点击 | ||
305 | + if (!this.disabled && !this.loading) { | ||
306 | + // 进行节流控制,每this.throttle毫秒内,只在开始处执行 | ||
307 | + throttle(() => { | ||
308 | + this.$emit("click"); | ||
309 | + }, this.throttleTime); | ||
310 | + } | ||
311 | + } | ||
312 | + } | ||
313 | + } | ||
314 | +</script> | ||
315 | + | ||
316 | +<style lang="scss" scoped> | ||
317 | +$show-reset-button: 1; | ||
318 | +@import '../../libs/css/variable.scss'; | ||
319 | +@import '../../libs/css/components.scss'; | ||
320 | +@import '../../libs/css/color.scss'; | ||
321 | + | ||
322 | +/* #ifndef APP-NVUE */ | ||
323 | +@import "./vue.scss"; | ||
324 | +/* #endif */ | ||
325 | + | ||
326 | +/* #ifdef APP-NVUE */ | ||
327 | +@import "./nvue.scss"; | ||
328 | +/* #endif */ | ||
329 | + | ||
330 | +$uv-button-uv-button-height: 40px !default; | ||
331 | +$uv-button-text-font-size: 15px !default; | ||
332 | +$uv-button-loading-text-font-size: 15px !default; | ||
333 | +$uv-button-loading-text-margin-left: 4px !default; | ||
334 | +$uv-button-large-width: 100% !default; | ||
335 | +$uv-button-large-height: 50px !default; | ||
336 | +$uv-button-normal-padding: 0 12px !default; | ||
337 | +$uv-button-large-padding: 0 15px !default; | ||
338 | +$uv-button-normal-font-size: 14px !default; | ||
339 | +$uv-button-small-min-width: 60px !default; | ||
340 | +$uv-button-small-height: 30px !default; | ||
341 | +$uv-button-small-padding: 0px 8px !default; | ||
342 | +$uv-button-mini-padding: 0px 8px !default; | ||
343 | +$uv-button-small-font-size: 12px !default; | ||
344 | +$uv-button-mini-height: 22px !default; | ||
345 | +$uv-button-mini-font-size: 10px !default; | ||
346 | +$uv-button-mini-min-width: 50px !default; | ||
347 | +$uv-button-disabled-opacity: 0.5 !default; | ||
348 | +$uv-button-info-color: #323233 !default; | ||
349 | +$uv-button-info-background-color: #fff !default; | ||
350 | +$uv-button-info-border-color: #ebedf0 !default; | ||
351 | +$uv-button-info-border-width: 1px !default; | ||
352 | +$uv-button-info-border-style: solid !default; | ||
353 | +$uv-button-success-color: #fff !default; | ||
354 | +$uv-button-success-background-color: $uv-success !default; | ||
355 | +$uv-button-success-border-color: $uv-button-success-background-color !default; | ||
356 | +$uv-button-success-border-width: 1px !default; | ||
357 | +$uv-button-success-border-style: solid !default; | ||
358 | +$uv-button-primary-color: #fff !default; | ||
359 | +$uv-button-primary-background-color: $uv-primary !default; | ||
360 | +$uv-button-primary-border-color: $uv-button-primary-background-color !default; | ||
361 | +$uv-button-primary-border-width: 1px !default; | ||
362 | +$uv-button-primary-border-style: solid !default; | ||
363 | +$uv-button-error-color: #fff !default; | ||
364 | +$uv-button-error-background-color: $uv-error !default; | ||
365 | +$uv-button-error-border-color: $uv-button-error-background-color !default; | ||
366 | +$uv-button-error-border-width: 1px !default; | ||
367 | +$uv-button-error-border-style: solid !default; | ||
368 | +$uv-button-warning-color: #fff !default; | ||
369 | +$uv-button-warning-background-color: $uv-warning !default; | ||
370 | +$uv-button-warning-border-color: $uv-button-warning-background-color !default; | ||
371 | +$uv-button-warning-border-width: 1px !default; | ||
372 | +$uv-button-warning-border-style: solid !default; | ||
373 | +$uv-button-block-width: 100% !default; | ||
374 | +$uv-button-circle-border-top-right-radius: 100px !default; | ||
375 | +$uv-button-circle-border-top-left-radius: 100px !default; | ||
376 | +$uv-button-circle-border-bottom-left-radius: 100px !default; | ||
377 | +$uv-button-circle-border-bottom-right-radius: 100px !default; | ||
378 | +$uv-button-square-border-top-right-radius: 3px !default; | ||
379 | +$uv-button-square-border-top-left-radius: 3px !default; | ||
380 | +$uv-button-square-border-bottom-left-radius: 3px !default; | ||
381 | +$uv-button-square-border-bottom-right-radius: 3px !default; | ||
382 | +$uv-button-icon-min-width: 1em !default; | ||
383 | +$uv-button-plain-background-color: #fff !default; | ||
384 | +$uv-button-hairline-border-width: 0.5px !default; | ||
385 | + | ||
386 | +.uv-button { | ||
387 | + height: $uv-button-uv-button-height; | ||
388 | + position: relative; | ||
389 | + align-items: center; | ||
390 | + justify-content: center; | ||
391 | + @include flex; | ||
392 | + /* #ifndef APP-NVUE */ | ||
393 | + box-sizing: border-box; | ||
394 | + /* #endif */ | ||
395 | + flex-direction: row; | ||
396 | + | ||
397 | + &__text { | ||
398 | + font-size: $uv-button-text-font-size; | ||
399 | + } | ||
400 | + | ||
401 | + &__loading-text { | ||
402 | + font-size: $uv-button-loading-text-font-size; | ||
403 | + margin-left: $uv-button-loading-text-margin-left; | ||
404 | + } | ||
405 | + | ||
406 | + &--large { | ||
407 | + /* #ifndef APP-NVUE */ | ||
408 | + width: $uv-button-large-width; | ||
409 | + /* #endif */ | ||
410 | + height: $uv-button-large-height; | ||
411 | + padding: $uv-button-large-padding; | ||
412 | + } | ||
413 | + | ||
414 | + &--normal { | ||
415 | + padding: $uv-button-normal-padding; | ||
416 | + font-size: $uv-button-normal-font-size; | ||
417 | + } | ||
418 | + | ||
419 | + &--small { | ||
420 | + /* #ifndef APP-NVUE */ | ||
421 | + min-width: $uv-button-small-min-width; | ||
422 | + /* #endif */ | ||
423 | + height: $uv-button-small-height; | ||
424 | + padding: $uv-button-small-padding; | ||
425 | + font-size: $uv-button-small-font-size; | ||
426 | + } | ||
427 | + | ||
428 | + &--mini { | ||
429 | + height: $uv-button-mini-height; | ||
430 | + font-size: $uv-button-mini-font-size; | ||
431 | + /* #ifndef APP-NVUE */ | ||
432 | + min-width: $uv-button-mini-min-width; | ||
433 | + /* #endif */ | ||
434 | + padding: $uv-button-mini-padding; | ||
435 | + } | ||
436 | + | ||
437 | + &--disabled { | ||
438 | + opacity: $uv-button-disabled-opacity; | ||
439 | + } | ||
440 | + | ||
441 | + &--info { | ||
442 | + color: $uv-button-info-color; | ||
443 | + background-color: $uv-button-info-background-color; | ||
444 | + border-color: $uv-button-info-border-color; | ||
445 | + border-width: $uv-button-info-border-width; | ||
446 | + border-style: $uv-button-info-border-style; | ||
447 | + } | ||
448 | + | ||
449 | + &--success { | ||
450 | + color: $uv-button-success-color; | ||
451 | + background-color: $uv-button-success-background-color; | ||
452 | + border-color: $uv-button-success-border-color; | ||
453 | + border-width: $uv-button-success-border-width; | ||
454 | + border-style: $uv-button-success-border-style; | ||
455 | + } | ||
456 | + | ||
457 | + &--primary { | ||
458 | + color: $uv-button-primary-color; | ||
459 | + background-color: $uv-button-primary-background-color; | ||
460 | + border-color: $uv-button-primary-border-color; | ||
461 | + border-width: $uv-button-primary-border-width; | ||
462 | + border-style: $uv-button-primary-border-style; | ||
463 | + } | ||
464 | + | ||
465 | + &--error { | ||
466 | + color: $uv-button-error-color; | ||
467 | + background-color: $uv-button-error-background-color; | ||
468 | + border-color: $uv-button-error-border-color; | ||
469 | + border-width: $uv-button-error-border-width; | ||
470 | + border-style: $uv-button-error-border-style; | ||
471 | + } | ||
472 | + | ||
473 | + &--warning { | ||
474 | + color: $uv-button-warning-color; | ||
475 | + background-color: $uv-button-warning-background-color; | ||
476 | + border-color: $uv-button-warning-border-color; | ||
477 | + border-width: $uv-button-warning-border-width; | ||
478 | + border-style: $uv-button-warning-border-style; | ||
479 | + } | ||
480 | + | ||
481 | + &--block { | ||
482 | + @include flex; | ||
483 | + width: $uv-button-block-width; | ||
484 | + } | ||
485 | + | ||
486 | + &--circle { | ||
487 | + border-top-right-radius: $uv-button-circle-border-top-right-radius; | ||
488 | + border-top-left-radius: $uv-button-circle-border-top-left-radius; | ||
489 | + border-bottom-left-radius: $uv-button-circle-border-bottom-left-radius; | ||
490 | + border-bottom-right-radius: $uv-button-circle-border-bottom-right-radius; | ||
491 | + } | ||
492 | + | ||
493 | + &--square { | ||
494 | + border-bottom-left-radius: $uv-button-square-border-top-right-radius; | ||
495 | + border-bottom-right-radius: $uv-button-square-border-top-left-radius; | ||
496 | + border-top-left-radius: $uv-button-square-border-bottom-left-radius; | ||
497 | + border-top-right-radius: $uv-button-square-border-bottom-right-radius; | ||
498 | + } | ||
499 | + | ||
500 | + &__icon { | ||
501 | + /* #ifndef APP-NVUE */ | ||
502 | + min-width: $uv-button-icon-min-width; | ||
503 | + line-height: inherit !important; | ||
504 | + vertical-align: top; | ||
505 | + /* #endif */ | ||
506 | + } | ||
507 | + | ||
508 | + &--plain { | ||
509 | + background-color: $uv-button-plain-background-color; | ||
510 | + } | ||
511 | + | ||
512 | + &--hairline { | ||
513 | + border-width: $uv-button-hairline-border-width !important; | ||
514 | + } | ||
515 | +} | ||
516 | +</style> |
1 | +@import '../../libs/css/color.scss'; | ||
2 | +// nvue下hover-class无效 | ||
3 | +$uv-button-before-top:50% !default; | ||
4 | +$uv-button-before-left:50% !default; | ||
5 | +$uv-button-before-width:100% !default; | ||
6 | +$uv-button-before-height:100% !default; | ||
7 | +$uv-button-before-transform:translate(-50%, -50%) !default; | ||
8 | +$uv-button-before-opacity:0 !default; | ||
9 | +$uv-button-before-background-color:#000 !default; | ||
10 | +$uv-button-before-border-color:#000 !default; | ||
11 | +$uv-button-active-before-opacity:.15 !default; | ||
12 | +$uv-button-icon-margin-left:4px !default; | ||
13 | +$uv-button-plain-uv-button-info-color:$uv-info; | ||
14 | +$uv-button-plain-uv-button-success-color:$uv-success; | ||
15 | +$uv-button-plain-uv-button-error-color:$uv-error; | ||
16 | +$uv-button-plain-uv-button-warning-color:$uv-warning; | ||
17 | + | ||
18 | +.uv-button-wrapper { | ||
19 | + position: relative; | ||
20 | + &--dis { | ||
21 | + position: absolute; | ||
22 | + left: 0; | ||
23 | + top: 0; | ||
24 | + right: 0; | ||
25 | + bottom: 0; | ||
26 | + z-index: 9; | ||
27 | + } | ||
28 | +} | ||
29 | + | ||
30 | +.uv-button { | ||
31 | + width: 100%; | ||
32 | + | ||
33 | + &__text { | ||
34 | + white-space: nowrap; | ||
35 | + line-height: 1; | ||
36 | + } | ||
37 | + | ||
38 | + &:before { | ||
39 | + position: absolute; | ||
40 | + top:$uv-button-before-top; | ||
41 | + left:$uv-button-before-left; | ||
42 | + width:$uv-button-before-width; | ||
43 | + height:$uv-button-before-height; | ||
44 | + border: inherit; | ||
45 | + border-radius: inherit; | ||
46 | + transform:$uv-button-before-transform; | ||
47 | + opacity:$uv-button-before-opacity; | ||
48 | + content: " "; | ||
49 | + background-color:$uv-button-before-background-color; | ||
50 | + border-color:$uv-button-before-border-color; | ||
51 | + } | ||
52 | + | ||
53 | + &--active { | ||
54 | + &:before { | ||
55 | + opacity: .15 | ||
56 | + } | ||
57 | + } | ||
58 | + | ||
59 | + &__icon+&__text:not(:empty), | ||
60 | + &__loading-text { | ||
61 | + margin-left:$uv-button-icon-margin-left; | ||
62 | + } | ||
63 | + | ||
64 | + &--plain { | ||
65 | + &.uv-button--primary { | ||
66 | + color: $uv-primary; | ||
67 | + } | ||
68 | + } | ||
69 | + | ||
70 | + &--plain { | ||
71 | + &.uv-button--info { | ||
72 | + color:$uv-button-plain-uv-button-info-color; | ||
73 | + } | ||
74 | + } | ||
75 | + | ||
76 | + &--plain { | ||
77 | + &.uv-button--success { | ||
78 | + color:$uv-button-plain-uv-button-success-color; | ||
79 | + } | ||
80 | + } | ||
81 | + | ||
82 | + &--plain { | ||
83 | + &.uv-button--error { | ||
84 | + color:$uv-button-plain-uv-button-error-color; | ||
85 | + } | ||
86 | + } | ||
87 | + | ||
88 | + &--plain { | ||
89 | + &.uv-button--warning { | ||
90 | + color:$uv-button-plain-uv-button-warning-color; | ||
91 | + } | ||
92 | + } | ||
93 | +} |
1 | +/** | ||
2 | +* @1900-2100区间内的公历、农历互转 | ||
3 | +* @charset UTF-8 | ||
4 | +* @github https://github.com/jjonline/calendar.js | ||
5 | +* @Author Jea杨(JJonline@JJonline.Cn) | ||
6 | +* @Time 2014-7-21 | ||
7 | +* @Time 2016-8-13 Fixed 2033hex、Attribution Annals | ||
8 | +* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug | ||
9 | +* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year | ||
10 | +* @Version 1.0.3 | ||
11 | +* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0] | ||
12 | +* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0] | ||
13 | +*/ | ||
14 | +/* eslint-disable */ | ||
15 | +var calendar = { | ||
16 | + | ||
17 | + /** | ||
18 | + * 农历1900-2100的润大小信息表 | ||
19 | + * @Array Of Property | ||
20 | + * @return Hex | ||
21 | + */ | ||
22 | + lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909 | ||
23 | + 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919 | ||
24 | + 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929 | ||
25 | + 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939 | ||
26 | + 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949 | ||
27 | + 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959 | ||
28 | + 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969 | ||
29 | + 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979 | ||
30 | + 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989 | ||
31 | + 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999 | ||
32 | + 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009 | ||
33 | + 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019 | ||
34 | + 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029 | ||
35 | + 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039 | ||
36 | + 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049 | ||
37 | + /** Add By JJonline@JJonline.Cn**/ | ||
38 | + 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059 | ||
39 | + 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069 | ||
40 | + 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079 | ||
41 | + 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089 | ||
42 | + 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099 | ||
43 | + 0x0d520], // 2100 | ||
44 | + | ||
45 | + /** | ||
46 | + * 公历每个月份的天数普通表 | ||
47 | + * @Array Of Property | ||
48 | + * @return Number | ||
49 | + */ | ||
50 | + solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], | ||
51 | + | ||
52 | + /** | ||
53 | + * 天干地支之天干速查表 | ||
54 | + * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"] | ||
55 | + * @return Cn string | ||
56 | + */ | ||
57 | + Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'], | ||
58 | + | ||
59 | + /** | ||
60 | + * 天干地支之地支速查表 | ||
61 | + * @Array Of Property | ||
62 | + * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"] | ||
63 | + * @return Cn string | ||
64 | + */ | ||
65 | + Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'], | ||
66 | + | ||
67 | + /** | ||
68 | + * 天干地支之地支速查表<=>生肖 | ||
69 | + * @Array Of Property | ||
70 | + * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"] | ||
71 | + * @return Cn string | ||
72 | + */ | ||
73 | + Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'], | ||
74 | + | ||
75 | + /** | ||
76 | + * 24节气速查表 | ||
77 | + * @Array Of Property | ||
78 | + * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"] | ||
79 | + * @return Cn string | ||
80 | + */ | ||
81 | + solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'], | ||
82 | + | ||
83 | + /** | ||
84 | + * 1900-2100各年的24节气日期速查表 | ||
85 | + * @Array Of Property | ||
86 | + * @return 0x string For splice | ||
87 | + */ | ||
88 | + sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', | ||
89 | + '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', | ||
90 | + '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', | ||
91 | + '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', | ||
92 | + 'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f', | ||
93 | + '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa', | ||
94 | + '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', | ||
95 | + '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f', | ||
96 | + '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', | ||
97 | + '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', | ||
98 | + '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', | ||
99 | + '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', | ||
100 | + '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', | ||
101 | + '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', | ||
102 | + '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722', | ||
103 | + '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f', | ||
104 | + '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e', | ||
105 | + '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', | ||
106 | + '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722', | ||
107 | + '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', | ||
108 | + '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', | ||
109 | + '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', | ||
110 | + '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722', | ||
111 | + '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', | ||
112 | + '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', | ||
113 | + '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', | ||
114 | + '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', | ||
115 | + '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e', | ||
116 | + '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', | ||
117 | + '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722', | ||
118 | + '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', | ||
119 | + '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', | ||
120 | + '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', | ||
121 | + '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', | ||
122 | + '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', | ||
123 | + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa', | ||
124 | + '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', | ||
125 | + '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', | ||
126 | + '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721', | ||
127 | + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2', | ||
128 | + '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722', | ||
129 | + '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', | ||
130 | + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd', | ||
131 | + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', | ||
132 | + '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', | ||
133 | + '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', | ||
134 | + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', | ||
135 | + '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', | ||
136 | + '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', | ||
137 | + '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721', | ||
138 | + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5', | ||
139 | + '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722', | ||
140 | + '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', | ||
141 | + '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', | ||
142 | + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', | ||
143 | + '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', | ||
144 | + '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721', | ||
145 | + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd', | ||
146 | + '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35', | ||
147 | + '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', | ||
148 | + '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721', | ||
149 | + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5', | ||
150 | + '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35', | ||
151 | + '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', | ||
152 | + '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', | ||
153 | + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35', | ||
154 | + '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'], | ||
155 | + | ||
156 | + /** | ||
157 | + * 数字转中文速查表 | ||
158 | + * @Array Of Property | ||
159 | + * @trans ['日','一','二','三','四','五','六','七','八','九','十'] | ||
160 | + * @return Cn string | ||
161 | + */ | ||
162 | + nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'], | ||
163 | + | ||
164 | + /** | ||
165 | + * 日期转农历称呼速查表 | ||
166 | + * @Array Of Property | ||
167 | + * @trans ['初','十','廿','卅'] | ||
168 | + * @return Cn string | ||
169 | + */ | ||
170 | + nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'], | ||
171 | + | ||
172 | + /** | ||
173 | + * 月份转农历称呼速查表 | ||
174 | + * @Array Of Property | ||
175 | + * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊'] | ||
176 | + * @return Cn string | ||
177 | + */ | ||
178 | + nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'], | ||
179 | + | ||
180 | + /** | ||
181 | + * 返回农历y年一整年的总天数 | ||
182 | + * @param lunar Year | ||
183 | + * @return Number | ||
184 | + * @eg:var count = calendar.lYearDays(1987) ;//count=387 | ||
185 | + */ | ||
186 | + lYearDays: function (y) { | ||
187 | + var i; var sum = 348 | ||
188 | + for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 } | ||
189 | + return (sum + this.leapDays(y)) | ||
190 | + }, | ||
191 | + | ||
192 | + /** | ||
193 | + * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0 | ||
194 | + * @param lunar Year | ||
195 | + * @return Number (0-12) | ||
196 | + * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6 | ||
197 | + */ | ||
198 | + leapMonth: function (y) { // 闰字编码 \u95f0 | ||
199 | + return (this.lunarInfo[y - 1900] & 0xf) | ||
200 | + }, | ||
201 | + | ||
202 | + /** | ||
203 | + * 返回农历y年闰月的天数 若该年没有闰月则返回0 | ||
204 | + * @param lunar Year | ||
205 | + * @return Number (0、29、30) | ||
206 | + * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29 | ||
207 | + */ | ||
208 | + leapDays: function (y) { | ||
209 | + if (this.leapMonth(y)) { | ||
210 | + return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29) | ||
211 | + } | ||
212 | + return (0) | ||
213 | + }, | ||
214 | + | ||
215 | + /** | ||
216 | + * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法 | ||
217 | + * @param lunar Year | ||
218 | + * @return Number (-1、29、30) | ||
219 | + * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29 | ||
220 | + */ | ||
221 | + monthDays: function (y, m) { | ||
222 | + if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1 | ||
223 | + return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29) | ||
224 | + }, | ||
225 | + | ||
226 | + /** | ||
227 | + * 返回公历(!)y年m月的天数 | ||
228 | + * @param solar Year | ||
229 | + * @return Number (-1、28、29、30、31) | ||
230 | + * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30 | ||
231 | + */ | ||
232 | + solarDays: function (y, m) { | ||
233 | + if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1 | ||
234 | + var ms = m - 1 | ||
235 | + if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29 | ||
236 | + return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28) | ||
237 | + } else { | ||
238 | + return (this.solarMonth[ms]) | ||
239 | + } | ||
240 | + }, | ||
241 | + | ||
242 | + /** | ||
243 | + * 农历年份转换为干支纪年 | ||
244 | + * @param lYear 农历年的年份数 | ||
245 | + * @return Cn string | ||
246 | + */ | ||
247 | + toGanZhiYear: function (lYear) { | ||
248 | + var ganKey = (lYear - 3) % 10 | ||
249 | + var zhiKey = (lYear - 3) % 12 | ||
250 | + if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干 | ||
251 | + if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支 | ||
252 | + return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1] | ||
253 | + }, | ||
254 | + | ||
255 | + /** | ||
256 | + * 公历月、日判断所属星座 | ||
257 | + * @param cMonth [description] | ||
258 | + * @param cDay [description] | ||
259 | + * @return Cn string | ||
260 | + */ | ||
261 | + toAstro: function (cMonth, cDay) { | ||
262 | + var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf' | ||
263 | + var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22] | ||
264 | + return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座 | ||
265 | + }, | ||
266 | + | ||
267 | + /** | ||
268 | + * 传入offset偏移量返回干支 | ||
269 | + * @param offset 相对甲子的偏移量 | ||
270 | + * @return Cn string | ||
271 | + */ | ||
272 | + toGanZhi: function (offset) { | ||
273 | + return this.Gan[offset % 10] + this.Zhi[offset % 12] | ||
274 | + }, | ||
275 | + | ||
276 | + /** | ||
277 | + * 传入公历(!)y年获得该年第n个节气的公历日期 | ||
278 | + * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起 | ||
279 | + * @return day Number | ||
280 | + * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春 | ||
281 | + */ | ||
282 | + getTerm: function (y, n) { | ||
283 | + if (y < 1900 || y > 2100) { return -1 } | ||
284 | + if (n < 1 || n > 24) { return -1 } | ||
285 | + var _table = this.sTermInfo[y - 1900] | ||
286 | + var _info = [ | ||
287 | + parseInt('0x' + _table.substr(0, 5)).toString(), | ||
288 | + parseInt('0x' + _table.substr(5, 5)).toString(), | ||
289 | + parseInt('0x' + _table.substr(10, 5)).toString(), | ||
290 | + parseInt('0x' + _table.substr(15, 5)).toString(), | ||
291 | + parseInt('0x' + _table.substr(20, 5)).toString(), | ||
292 | + parseInt('0x' + _table.substr(25, 5)).toString() | ||
293 | + ] | ||
294 | + var _calday = [ | ||
295 | + _info[0].substr(0, 1), | ||
296 | + _info[0].substr(1, 2), | ||
297 | + _info[0].substr(3, 1), | ||
298 | + _info[0].substr(4, 2), | ||
299 | + | ||
300 | + _info[1].substr(0, 1), | ||
301 | + _info[1].substr(1, 2), | ||
302 | + _info[1].substr(3, 1), | ||
303 | + _info[1].substr(4, 2), | ||
304 | + | ||
305 | + _info[2].substr(0, 1), | ||
306 | + _info[2].substr(1, 2), | ||
307 | + _info[2].substr(3, 1), | ||
308 | + _info[2].substr(4, 2), | ||
309 | + | ||
310 | + _info[3].substr(0, 1), | ||
311 | + _info[3].substr(1, 2), | ||
312 | + _info[3].substr(3, 1), | ||
313 | + _info[3].substr(4, 2), | ||
314 | + | ||
315 | + _info[4].substr(0, 1), | ||
316 | + _info[4].substr(1, 2), | ||
317 | + _info[4].substr(3, 1), | ||
318 | + _info[4].substr(4, 2), | ||
319 | + | ||
320 | + _info[5].substr(0, 1), | ||
321 | + _info[5].substr(1, 2), | ||
322 | + _info[5].substr(3, 1), | ||
323 | + _info[5].substr(4, 2) | ||
324 | + ] | ||
325 | + return parseInt(_calday[n - 1]) | ||
326 | + }, | ||
327 | + | ||
328 | + /** | ||
329 | + * 传入农历数字月份返回汉语通俗表示法 | ||
330 | + * @param lunar month | ||
331 | + * @return Cn string | ||
332 | + * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月' | ||
333 | + */ | ||
334 | + toChinaMonth: function (m) { // 月 => \u6708 | ||
335 | + if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1 | ||
336 | + var s = this.nStr3[m - 1] | ||
337 | + s += '\u6708'// 加上月字 | ||
338 | + return s | ||
339 | + }, | ||
340 | + | ||
341 | + /** | ||
342 | + * 传入农历日期数字返回汉字表示法 | ||
343 | + * @param lunar day | ||
344 | + * @return Cn string | ||
345 | + * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一' | ||
346 | + */ | ||
347 | + toChinaDay: function (d) { // 日 => \u65e5 | ||
348 | + var s | ||
349 | + switch (d) { | ||
350 | + case 10: | ||
351 | + s = '\u521d\u5341'; break | ||
352 | + case 20: | ||
353 | + s = '\u4e8c\u5341'; break | ||
354 | + break | ||
355 | + case 30: | ||
356 | + s = '\u4e09\u5341'; break | ||
357 | + break | ||
358 | + default: | ||
359 | + s = this.nStr2[Math.floor(d / 10)] | ||
360 | + s += this.nStr1[d % 10] | ||
361 | + } | ||
362 | + return (s) | ||
363 | + }, | ||
364 | + | ||
365 | + /** | ||
366 | + * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春” | ||
367 | + * @param y year | ||
368 | + * @return Cn string | ||
369 | + * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔' | ||
370 | + */ | ||
371 | + getAnimal: function (y) { | ||
372 | + return this.Animals[(y - 4) % 12] | ||
373 | + }, | ||
374 | + | ||
375 | + /** | ||
376 | + * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON | ||
377 | + * @param y solar year | ||
378 | + * @param m solar month | ||
379 | + * @param d solar day | ||
380 | + * @return JSON object | ||
381 | + * @eg:console.log(calendar.solar2lunar(1987,11,01)); | ||
382 | + */ | ||
383 | + solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31 | ||
384 | + // 年份限定、上限 | ||
385 | + if (y < 1900 || y > 2100) { | ||
386 | + return -1// undefined转换为数字变为NaN | ||
387 | + } | ||
388 | + // 公历传参最下限 | ||
389 | + if (y == 1900 && m == 1 && d < 31) { | ||
390 | + return -1 | ||
391 | + } | ||
392 | + // 未传参 获得当天 | ||
393 | + if (!y) { | ||
394 | + var objDate = new Date() | ||
395 | + } else { | ||
396 | + var objDate = new Date(y, parseInt(m) - 1, d) | ||
397 | + } | ||
398 | + var i; var leap = 0; var temp = 0 | ||
399 | + // 修正ymd参数 | ||
400 | + var y = objDate.getFullYear() | ||
401 | + var m = objDate.getMonth() + 1 | ||
402 | + var d = objDate.getDate() | ||
403 | + var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000 | ||
404 | + for (i = 1900; i < 2101 && offset > 0; i++) { | ||
405 | + temp = this.lYearDays(i) | ||
406 | + offset -= temp | ||
407 | + } | ||
408 | + if (offset < 0) { | ||
409 | + offset += temp; i-- | ||
410 | + } | ||
411 | + | ||
412 | + // 是否今天 | ||
413 | + var isTodayObj = new Date() | ||
414 | + var isToday = false | ||
415 | + if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) { | ||
416 | + isToday = true | ||
417 | + } | ||
418 | + // 星期几 | ||
419 | + var nWeek = objDate.getDay() | ||
420 | + var cWeek = this.nStr1[nWeek] | ||
421 | + // 数字表示周几顺应天朝周一开始的惯例 | ||
422 | + if (nWeek == 0) { | ||
423 | + nWeek = 7 | ||
424 | + } | ||
425 | + // 农历年 | ||
426 | + var year = i | ||
427 | + var leap = this.leapMonth(i) // 闰哪个月 | ||
428 | + var isLeap = false | ||
429 | + | ||
430 | + // 效验闰月 | ||
431 | + for (i = 1; i < 13 && offset > 0; i++) { | ||
432 | + // 闰月 | ||
433 | + if (leap > 0 && i == (leap + 1) && isLeap == false) { | ||
434 | + --i | ||
435 | + isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数 | ||
436 | + } else { | ||
437 | + temp = this.monthDays(year, i)// 计算农历普通月天数 | ||
438 | + } | ||
439 | + // 解除闰月 | ||
440 | + if (isLeap == true && i == (leap + 1)) { isLeap = false } | ||
441 | + offset -= temp | ||
442 | + } | ||
443 | + // 闰月导致数组下标重叠取反 | ||
444 | + if (offset == 0 && leap > 0 && i == leap + 1) { | ||
445 | + if (isLeap) { | ||
446 | + isLeap = false | ||
447 | + } else { | ||
448 | + isLeap = true; --i | ||
449 | + } | ||
450 | + } | ||
451 | + if (offset < 0) { | ||
452 | + offset += temp; --i | ||
453 | + } | ||
454 | + // 农历月 | ||
455 | + var month = i | ||
456 | + // 农历日 | ||
457 | + var day = offset + 1 | ||
458 | + // 天干地支处理 | ||
459 | + var sm = m - 1 | ||
460 | + var gzY = this.toGanZhiYear(year) | ||
461 | + | ||
462 | + // 当月的两个节气 | ||
463 | + // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year` | ||
464 | + var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始 | ||
465 | + var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始 | ||
466 | + | ||
467 | + // 依据12节气修正干支月 | ||
468 | + var gzM = this.toGanZhi((y - 1900) * 12 + m + 11) | ||
469 | + if (d >= firstNode) { | ||
470 | + gzM = this.toGanZhi((y - 1900) * 12 + m + 12) | ||
471 | + } | ||
472 | + | ||
473 | + // 传入的日期的节气与否 | ||
474 | + var isTerm = false | ||
475 | + var Term = null | ||
476 | + if (firstNode == d) { | ||
477 | + isTerm = true | ||
478 | + Term = this.solarTerm[m * 2 - 2] | ||
479 | + } | ||
480 | + if (secondNode == d) { | ||
481 | + isTerm = true | ||
482 | + Term = this.solarTerm[m * 2 - 1] | ||
483 | + } | ||
484 | + // 日柱 当月一日与 1900/1/1 相差天数 | ||
485 | + var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10 | ||
486 | + var gzD = this.toGanZhi(dayCyclical + d - 1) | ||
487 | + // 该日期所属的星座 | ||
488 | + var astro = this.toAstro(m, d) | ||
489 | + | ||
490 | + return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro } | ||
491 | + }, | ||
492 | + | ||
493 | + /** | ||
494 | + * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON | ||
495 | + * @param y lunar year | ||
496 | + * @param m lunar month | ||
497 | + * @param d lunar day | ||
498 | + * @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可] | ||
499 | + * @return JSON object | ||
500 | + * @eg:console.log(calendar.lunar2solar(1987,9,10)); | ||
501 | + */ | ||
502 | + lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1 | ||
503 | + var isLeapMonth = !!isLeapMonth | ||
504 | + var leapOffset = 0 | ||
505 | + var leapMonth = this.leapMonth(y) | ||
506 | + var leapDay = this.leapDays(y) | ||
507 | + if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同 | ||
508 | + if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值 | ||
509 | + var day = this.monthDays(y, m) | ||
510 | + var _day = day | ||
511 | + // bugFix 2016-9-25 | ||
512 | + // if month is leap, _day use leapDays method | ||
513 | + if (isLeapMonth) { | ||
514 | + _day = this.leapDays(y, m) | ||
515 | + } | ||
516 | + if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验 | ||
517 | + | ||
518 | + // 计算农历的时间差 | ||
519 | + var offset = 0 | ||
520 | + for (var i = 1900; i < y; i++) { | ||
521 | + offset += this.lYearDays(i) | ||
522 | + } | ||
523 | + var leap = 0; var isAdd = false | ||
524 | + for (var i = 1; i < m; i++) { | ||
525 | + leap = this.leapMonth(y) | ||
526 | + if (!isAdd) { // 处理闰月 | ||
527 | + if (leap <= i && leap > 0) { | ||
528 | + offset += this.leapDays(y); isAdd = true | ||
529 | + } | ||
530 | + } | ||
531 | + offset += this.monthDays(y, i) | ||
532 | + } | ||
533 | + // 转换闰月农历 需补充该年闰月的前一个月的时差 | ||
534 | + if (isLeapMonth) { offset += day } | ||
535 | + // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点) | ||
536 | + var stmap = Date.UTC(1900, 1, 30, 0, 0, 0) | ||
537 | + var calObj = new Date((offset + d - 31) * 86400000 + stmap) | ||
538 | + var cY = calObj.getUTCFullYear() | ||
539 | + var cM = calObj.getUTCMonth() + 1 | ||
540 | + var cD = calObj.getUTCDate() | ||
541 | + | ||
542 | + return this.solar2lunar(cY, cM, cD) | ||
543 | + } | ||
544 | +} | ||
545 | + | ||
546 | +export default calendar |
1 | +<template> | ||
2 | + <view class="uv-calendar-header uv-border-bottom"> | ||
3 | + <text | ||
4 | + class="uv-calendar-header__title" | ||
5 | + v-if="showTitle" | ||
6 | + >{{ title }}</text> | ||
7 | + <text | ||
8 | + class="uv-calendar-header__subtitle" | ||
9 | + v-if="showSubtitle" | ||
10 | + >{{ subtitle }}</text> | ||
11 | + <view class="uv-calendar-header__weekdays"> | ||
12 | + <text class="uv-calendar-header__weekdays__weekday">一</text> | ||
13 | + <text class="uv-calendar-header__weekdays__weekday">二</text> | ||
14 | + <text class="uv-calendar-header__weekdays__weekday">三</text> | ||
15 | + <text class="uv-calendar-header__weekdays__weekday">四</text> | ||
16 | + <text class="uv-calendar-header__weekdays__weekday">五</text> | ||
17 | + <text class="uv-calendar-header__weekdays__weekday">六</text> | ||
18 | + <text class="uv-calendar-header__weekdays__weekday">日</text> | ||
19 | + </view> | ||
20 | + </view> | ||
21 | +</template> | ||
22 | + | ||
23 | +<script> | ||
24 | + import mpMixin from '../../libs/mixin/mpMixin.js' | ||
25 | + import mixin from '../../libs/mixin/mixin.js' | ||
26 | + export default { | ||
27 | + name: 'uv-calendar-header', | ||
28 | + mixins: [mpMixin, mixin], | ||
29 | + props: { | ||
30 | + // 标题 | ||
31 | + title: { | ||
32 | + type: String, | ||
33 | + default: '' | ||
34 | + }, | ||
35 | + // 副标题 | ||
36 | + subtitle: { | ||
37 | + type: [String,null], | ||
38 | + default: '' | ||
39 | + }, | ||
40 | + // 是否显示标题 | ||
41 | + showTitle: { | ||
42 | + type: Boolean, | ||
43 | + default: true | ||
44 | + }, | ||
45 | + // 是否显示副标题 | ||
46 | + showSubtitle: { | ||
47 | + type: Boolean, | ||
48 | + default: true | ||
49 | + }, | ||
50 | + }, | ||
51 | + data() { | ||
52 | + return { | ||
53 | + | ||
54 | + } | ||
55 | + }, | ||
56 | + methods: { | ||
57 | + name() { | ||
58 | + | ||
59 | + } | ||
60 | + }, | ||
61 | + } | ||
62 | +</script> | ||
63 | + | ||
64 | +<style lang="scss" scoped> | ||
65 | + $show-border: 1; | ||
66 | + $show-border-bottom: 1; | ||
67 | + @import '../../libs/css/variable.scss'; | ||
68 | + @import '../../libs/css/components.scss'; | ||
69 | + @import '../../libs/css/color.scss'; | ||
70 | + .uv-calendar-header { | ||
71 | + padding-bottom: 4px; | ||
72 | + | ||
73 | + &__title { | ||
74 | + font-size: 16px; | ||
75 | + color: $uv-main-color; | ||
76 | + text-align: center; | ||
77 | + height: 42px; | ||
78 | + line-height: 42px; | ||
79 | + font-weight: bold; | ||
80 | + } | ||
81 | + | ||
82 | + &__subtitle { | ||
83 | + font-size: 14px; | ||
84 | + color: $uv-main-color; | ||
85 | + height: 40px; | ||
86 | + text-align: center; | ||
87 | + line-height: 40px; | ||
88 | + font-weight: bold; | ||
89 | + } | ||
90 | + | ||
91 | + &__weekdays { | ||
92 | + @include flex; | ||
93 | + justify-content: space-between; | ||
94 | + | ||
95 | + &__weekday { | ||
96 | + font-size: 13px; | ||
97 | + color: $uv-main-color; | ||
98 | + line-height: 30px; | ||
99 | + flex: 1; | ||
100 | + text-align: center; | ||
101 | + } | ||
102 | + } | ||
103 | + } | ||
104 | +</style> |
1 | +<template> | ||
2 | + <view class="uv-calendar-month-wrapper" ref="uv-calendar-month-wrapper"> | ||
3 | + <view v-for="(item, index) in months" :key="index" :class="[`uv-calendar-month-${index}`]" | ||
4 | + :ref="`uv-calendar-month-${index}`" :id="`month-${index}`"> | ||
5 | + <text v-if="index !== 0" class="uv-calendar-month__title">{{ item.year }}年{{ item.month }}月</text> | ||
6 | + <view class="uv-calendar-month__days"> | ||
7 | + <view v-if="showMark" class="uv-calendar-month__days__month-mark-wrapper"> | ||
8 | + <text class="uv-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text> | ||
9 | + </view> | ||
10 | + <view class="uv-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1" | ||
11 | + :style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)" | ||
12 | + :class="[item1.selected && 'uv-calendar-month__days__day__select--selected']"> | ||
13 | + <view class="uv-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]"> | ||
14 | + <text v-if="getTopInfo(index, index1, item1)" | ||
15 | + class="uv-calendar-month__days__day__select__top-info" | ||
16 | + :class="[item1.disabled && 'uv-calendar-month__days__day__select__top-info--disabled']" | ||
17 | + :style="[textStyle(item1)]" | ||
18 | + >{{ getTopInfo(index, index1, item1) }}</text> | ||
19 | + <text class="uv-calendar-month__days__day__select__info" | ||
20 | + :class="[item1.disabled && 'uv-calendar-month__days__day__select__info--disabled']" | ||
21 | + :style="[textStyle(item1)]">{{ item1.day }}</text> | ||
22 | + <text v-if="getBottomInfo(index, index1, item1)" | ||
23 | + class="uv-calendar-month__days__day__select__buttom-info" | ||
24 | + :class="[item1.disabled && 'uv-calendar-month__days__day__select__buttom-info--disabled']" | ||
25 | + :style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text> | ||
26 | + <text v-if="item1.dot" class="uv-calendar-month__days__day__select__dot"></text> | ||
27 | + </view> | ||
28 | + </view> | ||
29 | + </view> | ||
30 | + </view> | ||
31 | + </view> | ||
32 | +</template> | ||
33 | + | ||
34 | +<script> | ||
35 | + // #ifdef APP-NVUE | ||
36 | + // 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度 | ||
37 | + const dom = uni.requireNativePlugin('dom') | ||
38 | + // #endif | ||
39 | + import { colorGradient } from '../../libs/function/colorGradient.js'; | ||
40 | + import mpMixin from '../../libs/mixin/mpMixin.js' | ||
41 | + import mixin from '../../libs/mixin/mixin.js' | ||
42 | + import dayjs from '../../libs/util/dayjs.js' | ||
43 | + export default { | ||
44 | + name: 'uv-calendar-month', | ||
45 | + emits:['monthSelected','updateMonthTop','change'], | ||
46 | + mixins: [mpMixin, mixin], | ||
47 | + props: { | ||
48 | + // 是否显示月份背景色 | ||
49 | + showMark: { | ||
50 | + type: Boolean, | ||
51 | + default: true | ||
52 | + }, | ||
53 | + // 主题色,对底部按钮和选中日期有效 | ||
54 | + color: { | ||
55 | + type: String, | ||
56 | + default: '#3c9cff' | ||
57 | + }, | ||
58 | + // 月份数据 | ||
59 | + months: { | ||
60 | + type: Array, | ||
61 | + default: () => [] | ||
62 | + }, | ||
63 | + // 日期选择类型 | ||
64 | + mode: { | ||
65 | + type: String, | ||
66 | + default: 'single' | ||
67 | + }, | ||
68 | + // 日期行高 | ||
69 | + rowHeight: { | ||
70 | + type: [String, Number], | ||
71 | + default: 58 | ||
72 | + }, | ||
73 | + // mode=multiple时,最多可选多少个日期 | ||
74 | + maxCount: { | ||
75 | + type: [String, Number], | ||
76 | + default: Infinity | ||
77 | + }, | ||
78 | + // mode=range时,第一个日期底部的提示文字 | ||
79 | + startText: { | ||
80 | + type: String, | ||
81 | + default: '开始' | ||
82 | + }, | ||
83 | + // mode=range时,最后一个日期底部的提示文字 | ||
84 | + endText: { | ||
85 | + type: String, | ||
86 | + default: '结束' | ||
87 | + }, | ||
88 | + // 默认选中的日期,mode为multiple或range是必须为数组格式 | ||
89 | + defaultDate: { | ||
90 | + type: [Array, String, Date], | ||
91 | + default: null | ||
92 | + }, | ||
93 | + // 最小的可选日期 | ||
94 | + minDate: { | ||
95 | + type: [String, Number], | ||
96 | + default: 0 | ||
97 | + }, | ||
98 | + // 最大可选日期 | ||
99 | + maxDate: { | ||
100 | + type: [String, Number], | ||
101 | + default: 0 | ||
102 | + }, | ||
103 | + // 如果没有设置maxDate,则往后推多少个月 | ||
104 | + maxMonth: { | ||
105 | + type: [String, Number], | ||
106 | + default: 2 | ||
107 | + }, | ||
108 | + // 是否为只读状态,只读状态下禁止选择日期 | ||
109 | + readonly: { | ||
110 | + type: Boolean, | ||
111 | + default: false | ||
112 | + }, | ||
113 | + // 日期区间最多可选天数,默认无限制,mode = range时有效 | ||
114 | + maxRange: { | ||
115 | + type: [Number, String], | ||
116 | + default: Infinity | ||
117 | + }, | ||
118 | + // 范围选择超过最多可选天数时的提示文案,mode = range时有效 | ||
119 | + rangePrompt: { | ||
120 | + type: String, | ||
121 | + default: '' | ||
122 | + }, | ||
123 | + // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 | ||
124 | + showRangePrompt: { | ||
125 | + type: Boolean, | ||
126 | + default: true | ||
127 | + }, | ||
128 | + // 是否允许日期范围的起止时间为同一天,mode = range时有效 | ||
129 | + allowSameDay: { | ||
130 | + type: Boolean, | ||
131 | + default: false | ||
132 | + } | ||
133 | + }, | ||
134 | + data() { | ||
135 | + return { | ||
136 | + // 每个日期的宽度 | ||
137 | + width: 0, | ||
138 | + // 当前选中的日期item | ||
139 | + item: {}, | ||
140 | + selected: [] | ||
141 | + } | ||
142 | + }, | ||
143 | + watch: { | ||
144 | + selectedChange: { | ||
145 | + immediate: true, | ||
146 | + handler(n) { | ||
147 | + this.setDefaultDate() | ||
148 | + } | ||
149 | + } | ||
150 | + }, | ||
151 | + computed: { | ||
152 | + // 多个条件的变化,会引起选中日期的变化,这里统一管理监听 | ||
153 | + selectedChange() { | ||
154 | + return [this.minDate, this.maxDate, this.defaultDate] | ||
155 | + }, | ||
156 | + dayStyle(index1, index2, item) { | ||
157 | + return (index1, index2, item) => { | ||
158 | + const style = {} | ||
159 | + let week = item.week | ||
160 | + // 不进行四舍五入的形式保留2位小数 | ||
161 | + const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1)) | ||
162 | + // 得出每个日期的宽度 | ||
163 | + // #ifdef APP-NVUE | ||
164 | + style.width = this.$uv.addUnit(dayWidth) | ||
165 | + // #endif | ||
166 | + style.height = this.$uv.addUnit(this.rowHeight) | ||
167 | + if (index2 === 0) { | ||
168 | + // 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数 | ||
169 | + week = (week === 0 ? 7 : week) - 1 | ||
170 | + style.marginLeft = this.$uv.addUnit(week * dayWidth) | ||
171 | + } | ||
172 | + if (this.mode === 'range') { | ||
173 | + // 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug | ||
174 | + style.paddingLeft = 0 | ||
175 | + style.paddingRight = 0 | ||
176 | + style.paddingBottom = 0 | ||
177 | + style.paddingTop = 0 | ||
178 | + } | ||
179 | + return style | ||
180 | + } | ||
181 | + }, | ||
182 | + daySelectStyle() { | ||
183 | + return (index1, index2, item) => { | ||
184 | + let date = dayjs(item.date).format("YYYY-MM-DD"), | ||
185 | + style = {} | ||
186 | + // 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断 | ||
187 | + if (this.selected.some(item => this.dateSame(item, date))) { | ||
188 | + style.backgroundColor = this.color | ||
189 | + } | ||
190 | + if (this.mode === 'single') { | ||
191 | + if (date === this.selected[0]) { | ||
192 | + // 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等 | ||
193 | + style.borderTopLeftRadius = '3px' | ||
194 | + style.borderBottomLeftRadius = '3px' | ||
195 | + style.borderTopRightRadius = '3px' | ||
196 | + style.borderBottomRightRadius = '3px' | ||
197 | + } | ||
198 | + } else if (this.mode === 'range') { | ||
199 | + if (this.selected.length >= 2) { | ||
200 | + const len = this.selected.length - 1 | ||
201 | + // 第一个日期设置左上角和左下角的圆角 | ||
202 | + if (this.dateSame(date, this.selected[0])) { | ||
203 | + style.borderTopLeftRadius = '3px' | ||
204 | + style.borderBottomLeftRadius = '3px' | ||
205 | + } | ||
206 | + // 最后一个日期设置右上角和右下角的圆角 | ||
207 | + if (this.dateSame(date, this.selected[len])) { | ||
208 | + style.borderTopRightRadius = '3px' | ||
209 | + style.borderBottomRightRadius = '3px' | ||
210 | + } | ||
211 | + // 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值 | ||
212 | + if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this | ||
213 | + .selected[len]))) { | ||
214 | + style.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90] | ||
215 | + // 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符 | ||
216 | + style.opacity = 0.7 | ||
217 | + } | ||
218 | + } else if (this.selected.length === 1) { | ||
219 | + // 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug | ||
220 | + // 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现 | ||
221 | + style.borderTopLeftRadius = '3px' | ||
222 | + style.borderBottomLeftRadius = '3px' | ||
223 | + } | ||
224 | + } else { | ||
225 | + if (this.selected.some(item => this.dateSame(item, date))) { | ||
226 | + style.borderTopLeftRadius = '3px' | ||
227 | + style.borderBottomLeftRadius = '3px' | ||
228 | + style.borderTopRightRadius = '3px' | ||
229 | + style.borderBottomRightRadius = '3px' | ||
230 | + } | ||
231 | + } | ||
232 | + return style | ||
233 | + } | ||
234 | + }, | ||
235 | + // 某个日期是否被选中 | ||
236 | + textStyle() { | ||
237 | + return (item) => { | ||
238 | + const date = dayjs(item.date).format("YYYY-MM-DD"), | ||
239 | + style = {} | ||
240 | + // 选中的日期,提示文字设置白色 | ||
241 | + if (this.selected.some(item => this.dateSame(item, date))) { | ||
242 | + style.color = '#ffffff' | ||
243 | + } | ||
244 | + if (this.mode === 'range') { | ||
245 | + const len = this.selected.length - 1 | ||
246 | + // 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色 | ||
247 | + if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this | ||
248 | + .selected[len]))) { | ||
249 | + style.color = this.color | ||
250 | + } | ||
251 | + } | ||
252 | + return style | ||
253 | + } | ||
254 | + }, | ||
255 | + // 获取顶部的提示文字 | ||
256 | + getTopInfo() { | ||
257 | + return (index1, index2, item) => { | ||
258 | + return item.topInfo; | ||
259 | + } | ||
260 | + }, | ||
261 | + // 获取底部的提示文字 | ||
262 | + getBottomInfo() { | ||
263 | + return (index1, index2, item) => { | ||
264 | + const date = dayjs(item.date).format("YYYY-MM-DD") | ||
265 | + const bottomInfo = item.bottomInfo | ||
266 | + // 当为日期范围模式时,且选择的日期个数大于0时 | ||
267 | + if (this.mode === 'range' && this.selected.length > 0) { | ||
268 | + if (this.selected.length === 1) { | ||
269 | + // 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始” | ||
270 | + if (this.dateSame(date, this.selected[0])) return this.startText | ||
271 | + else return bottomInfo | ||
272 | + } else { | ||
273 | + const len = this.selected.length - 1 | ||
274 | + // 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期 | ||
275 | + if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) && | ||
276 | + len === 1) { | ||
277 | + // 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中 | ||
278 | + return `${this.startText}/${this.endText}` | ||
279 | + } else if (this.dateSame(date, this.selected[0])) { | ||
280 | + return this.startText | ||
281 | + } else if (this.dateSame(date, this.selected[len])) { | ||
282 | + return this.endText | ||
283 | + } else { | ||
284 | + return bottomInfo | ||
285 | + } | ||
286 | + } | ||
287 | + } else { | ||
288 | + return bottomInfo | ||
289 | + } | ||
290 | + } | ||
291 | + } | ||
292 | + }, | ||
293 | + mounted() { | ||
294 | + this.init() | ||
295 | + }, | ||
296 | + methods: { | ||
297 | + init() { | ||
298 | + // 初始化默认选中 | ||
299 | + this.$emit('monthSelected', this.selected) | ||
300 | + this.$nextTick(() => { | ||
301 | + // 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度 | ||
302 | + // 因为nvue下,$nextTick并不是100%可靠的 | ||
303 | + this.$uv.sleep(10).then(() => { | ||
304 | + this.getWrapperWidth() | ||
305 | + this.getMonthRect() | ||
306 | + }) | ||
307 | + }) | ||
308 | + }, | ||
309 | + // 判断两个日期是否相等 | ||
310 | + dateSame(date1, date2) { | ||
311 | + return dayjs(date1).isSame(dayjs(date2)) | ||
312 | + }, | ||
313 | + // 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度 | ||
314 | + getWrapperWidth() { | ||
315 | + // #ifdef APP-NVUE | ||
316 | + dom.getComponentRect(this.$refs['uv-calendar-month-wrapper'], res => { | ||
317 | + this.width = res.size.width | ||
318 | + }) | ||
319 | + // #endif | ||
320 | + // #ifndef APP-NVUE | ||
321 | + this.$uvGetRect('.uv-calendar-month-wrapper').then(size => { | ||
322 | + this.width = size.width | ||
323 | + }) | ||
324 | + // #endif | ||
325 | + }, | ||
326 | + getMonthRect() { | ||
327 | + // 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份 | ||
328 | + const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise( | ||
329 | + `uv-calendar-month-${index}`)) | ||
330 | + // 一次性返回 | ||
331 | + Promise.all(promiseAllArr).then( | ||
332 | + sizes => { | ||
333 | + let height = 1 | ||
334 | + const topArr = [] | ||
335 | + for (let i = 0; i < this.months.length; i++) { | ||
336 | + // 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份 | ||
337 | + topArr[i] = height | ||
338 | + height += sizes[i].height | ||
339 | + } | ||
340 | + // 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出 | ||
341 | + this.$emit('updateMonthTop', topArr) | ||
342 | + }) | ||
343 | + }, | ||
344 | + // 获取每个月份区域的尺寸 | ||
345 | + getMonthRectByPromise(el) { | ||
346 | + // #ifndef APP-NVUE | ||
347 | + // $uvGetRect为uvui自带的节点查询简化方法,详见文档介绍:https://www.uvui.cn/js/getRect.html | ||
348 | + // 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同 | ||
349 | + return new Promise(resolve => { | ||
350 | + this.$uvGetRect(`.${el}`).then(size => { | ||
351 | + resolve(size) | ||
352 | + }) | ||
353 | + }) | ||
354 | + // #endif | ||
355 | + | ||
356 | + // #ifdef APP-NVUE | ||
357 | + // nvue下,使用dom模块查询元素高度 | ||
358 | + // 返回一个promise,让调用此方法的主体能使用then回调 | ||
359 | + return new Promise(resolve => { | ||
360 | + dom.getComponentRect(this.$refs[el][0], res => { | ||
361 | + resolve(res.size) | ||
362 | + }) | ||
363 | + }) | ||
364 | + // #endif | ||
365 | + }, | ||
366 | + // 点击某一个日期 | ||
367 | + clickHandler(index1, index2, item) { | ||
368 | + if (this.readonly) { | ||
369 | + return; | ||
370 | + } | ||
371 | + this.item = item | ||
372 | + const date = dayjs(item.date).format("YYYY-MM-DD") | ||
373 | + if (item.disabled) return | ||
374 | + // 对上一次选择的日期数组进行深度克隆 | ||
375 | + let selected = this.$uv.deepClone(this.selected) | ||
376 | + if (this.mode === 'single') { | ||
377 | + // 单选情况下,让数组中的元素为当前点击的日期 | ||
378 | + selected = [date] | ||
379 | + } else if (this.mode === 'multiple') { | ||
380 | + if (selected.some(item => this.dateSame(item, date))) { | ||
381 | + // 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果 | ||
382 | + const itemIndex = selected.findIndex(item => dayjs(item).format("YYYY-MM-DD") === dayjs(date).format("YYYY-MM-DD")) | ||
383 | + selected.splice(itemIndex, 1) | ||
384 | + } else { | ||
385 | + // 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去 | ||
386 | + if (selected.length < this.maxCount) selected.push(date) | ||
387 | + } | ||
388 | + } else { | ||
389 | + // 选择区间形式 | ||
390 | + if (selected.length === 0 || selected.length >= 2) { | ||
391 | + // 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期 | ||
392 | + selected = [date] | ||
393 | + } else if (selected.length === 1) { | ||
394 | + // 如果已经选择了开始日期 | ||
395 | + const existsDate = selected[0] | ||
396 | + // 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期 | ||
397 | + if (dayjs(date).isBefore(existsDate)) { | ||
398 | + selected = [date] | ||
399 | + } else if (dayjs(date).isAfter(existsDate)) { | ||
400 | + // 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示 | ||
401 | + if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) { | ||
402 | + if(this.rangePrompt) { | ||
403 | + this.$uv.toast(this.rangePrompt) | ||
404 | + } else { | ||
405 | + this.$uv.toast(`选择天数不能超过 ${this.maxRange} 天`) | ||
406 | + } | ||
407 | + return | ||
408 | + } | ||
409 | + // 如果当前日期大于已有日期,将当前的添加到数组尾部 | ||
410 | + selected.push(date) | ||
411 | + const startDate = selected[0] | ||
412 | + const endDate = selected[1] | ||
413 | + const arr = [] | ||
414 | + let i = 0 | ||
415 | + do { | ||
416 | + // 将开始和结束日期之间的日期添加到数组中 | ||
417 | + arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD")) | ||
418 | + i++ | ||
419 | + // 累加的日期小于结束日期时,继续下一次的循环 | ||
420 | + } while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate))) | ||
421 | + // 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来 | ||
422 | + arr.push(endDate) | ||
423 | + selected = arr | ||
424 | + } else { | ||
425 | + // 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己 | ||
426 | + if (selected[0] === date && !this.allowSameDay) return | ||
427 | + selected.push(date) | ||
428 | + } | ||
429 | + } | ||
430 | + } | ||
431 | + this.setSelected(selected) | ||
432 | + this.$emit('change',{ | ||
433 | + day: date, | ||
434 | + selected: selected | ||
435 | + }); | ||
436 | + }, | ||
437 | + // 设置默认日期 | ||
438 | + setDefaultDate() { | ||
439 | + if (!this.defaultDate) { | ||
440 | + // 如果没有设置默认日期,则将当天日期设置为默认选中的日期 | ||
441 | + const selected = [dayjs().format("YYYY-MM-DD")] | ||
442 | + return this.setSelected(selected, false) | ||
443 | + } | ||
444 | + let defaultDate = [] | ||
445 | + const minDate = this.minDate || dayjs().format("YYYY-MM-DD") | ||
446 | + const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD") | ||
447 | + if (this.mode === 'single') { | ||
448 | + // 单选模式,可以是字符串或数组,Date对象等 | ||
449 | + if (!this.$uv.test.array(this.defaultDate)) { | ||
450 | + defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")] | ||
451 | + } else { | ||
452 | + defaultDate = [this.defaultDate[0]] | ||
453 | + } | ||
454 | + } else { | ||
455 | + // 如果为非数组,则不执行 | ||
456 | + if (!this.$uv.test.array(this.defaultDate)) return | ||
457 | + defaultDate = this.defaultDate | ||
458 | + } | ||
459 | + // 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素 | ||
460 | + defaultDate = defaultDate.filter(item => { | ||
461 | + return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs( | ||
462 | + maxDate).add(1, 'day')) | ||
463 | + }) | ||
464 | + this.setSelected(defaultDate, false) | ||
465 | + }, | ||
466 | + setSelected(selected, event = true) { | ||
467 | + this.selected = selected | ||
468 | + event && this.$emit('monthSelected', this.selected) | ||
469 | + } | ||
470 | + } | ||
471 | + } | ||
472 | +</script> | ||
473 | + | ||
474 | +<style lang="scss" scoped> | ||
475 | + @import '../../libs/css/components.scss'; | ||
476 | + @import '../../libs/css/color.scss'; | ||
477 | + .uv-calendar-month-wrapper { | ||
478 | + margin-top: 4px; | ||
479 | + } | ||
480 | + | ||
481 | + .uv-calendar-month { | ||
482 | + | ||
483 | + &__title { | ||
484 | + font-size: 14px; | ||
485 | + line-height: 42px; | ||
486 | + height: 42px; | ||
487 | + color: $uv-main-color; | ||
488 | + text-align: center; | ||
489 | + font-weight: bold; | ||
490 | + } | ||
491 | + | ||
492 | + &__days { | ||
493 | + position: relative; | ||
494 | + @include flex; | ||
495 | + flex-wrap: wrap; | ||
496 | + | ||
497 | + &__month-mark-wrapper { | ||
498 | + position: absolute; | ||
499 | + top: 0; | ||
500 | + bottom: 0; | ||
501 | + left: 0; | ||
502 | + right: 0; | ||
503 | + @include flex; | ||
504 | + justify-content: center; | ||
505 | + align-items: center; | ||
506 | + | ||
507 | + &__text { | ||
508 | + font-size: 155px; | ||
509 | + color: rgba(231, 232, 234, 0.83); | ||
510 | + } | ||
511 | + } | ||
512 | + | ||
513 | + &__day { | ||
514 | + @include flex; | ||
515 | + padding: 2px; | ||
516 | + /* #ifndef APP-NVUE */ | ||
517 | + // vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移 | ||
518 | + width: calc(100% / 7); | ||
519 | + box-sizing: border-box; | ||
520 | + /* #endif */ | ||
521 | + | ||
522 | + &__select { | ||
523 | + flex: 1; | ||
524 | + @include flex; | ||
525 | + align-items: center; | ||
526 | + justify-content: center; | ||
527 | + position: relative; | ||
528 | + | ||
529 | + &__dot { | ||
530 | + width: 7px; | ||
531 | + height: 7px; | ||
532 | + border-radius: 100px; | ||
533 | + background-color: $uv-error; | ||
534 | + position: absolute; | ||
535 | + top: 12px; | ||
536 | + right: 7px; | ||
537 | + } | ||
538 | + | ||
539 | + &__top-info { | ||
540 | + color: $uv-content-color; | ||
541 | + text-align: center; | ||
542 | + position: absolute; | ||
543 | + top: 2px; | ||
544 | + font-size: 10px; | ||
545 | + text-align: center; | ||
546 | + left: 0; | ||
547 | + right: 0; | ||
548 | + &--selected { | ||
549 | + color: #ffffff; | ||
550 | + } | ||
551 | + | ||
552 | + &--disabled { | ||
553 | + color: #cacbcd; | ||
554 | + } | ||
555 | + } | ||
556 | + | ||
557 | + &__buttom-info { | ||
558 | + color: $uv-content-color; | ||
559 | + text-align: center; | ||
560 | + position: absolute; | ||
561 | + bottom: 5px; | ||
562 | + font-size: 10px; | ||
563 | + text-align: center; | ||
564 | + left: 0; | ||
565 | + right: 0; | ||
566 | + | ||
567 | + &--selected { | ||
568 | + color: #ffffff; | ||
569 | + } | ||
570 | + | ||
571 | + &--disabled { | ||
572 | + color: #cacbcd; | ||
573 | + } | ||
574 | + } | ||
575 | + | ||
576 | + &__info { | ||
577 | + text-align: center; | ||
578 | + font-size: 16px; | ||
579 | + | ||
580 | + &--selected { | ||
581 | + color: #ffffff; | ||
582 | + } | ||
583 | + | ||
584 | + &--disabled { | ||
585 | + color: #cacbcd; | ||
586 | + } | ||
587 | + } | ||
588 | + | ||
589 | + &--selected { | ||
590 | + background-color: $uv-primary; | ||
591 | + @include flex; | ||
592 | + justify-content: center; | ||
593 | + align-items: center; | ||
594 | + flex: 1; | ||
595 | + border-radius: 3px; | ||
596 | + } | ||
597 | + | ||
598 | + &--range-selected { | ||
599 | + opacity: 0.3; | ||
600 | + border-radius: 0; | ||
601 | + } | ||
602 | + | ||
603 | + &--range-start-selected { | ||
604 | + border-top-right-radius: 0; | ||
605 | + border-bottom-right-radius: 0; | ||
606 | + } | ||
607 | + | ||
608 | + &--range-end-selected { | ||
609 | + border-top-left-radius: 0; | ||
610 | + border-bottom-left-radius: 0; | ||
611 | + } | ||
612 | + } | ||
613 | + } | ||
614 | + } | ||
615 | + } | ||
616 | +</style> |
1 | +export default { | ||
2 | + props: { | ||
3 | + // 日历顶部标题 | ||
4 | + title: { | ||
5 | + type: String, | ||
6 | + default: '日期选择' | ||
7 | + }, | ||
8 | + // 是否显示标题 | ||
9 | + showTitle: { | ||
10 | + type: Boolean, | ||
11 | + default: true | ||
12 | + }, | ||
13 | + // 是否显示副标题 | ||
14 | + showSubtitle: { | ||
15 | + type: Boolean, | ||
16 | + default: true | ||
17 | + }, | ||
18 | + // 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 | ||
19 | + mode: { | ||
20 | + type: String, | ||
21 | + default: 'single' | ||
22 | + }, | ||
23 | + // mode=range时,第一个日期底部的提示文字 | ||
24 | + startText: { | ||
25 | + type: String, | ||
26 | + default: '开始' | ||
27 | + }, | ||
28 | + // mode=range时,最后一个日期底部的提示文字 | ||
29 | + endText: { | ||
30 | + type: String, | ||
31 | + default: '结束' | ||
32 | + }, | ||
33 | + // 自定义列表 | ||
34 | + customList: { | ||
35 | + type: Array, | ||
36 | + default: () => [] | ||
37 | + }, | ||
38 | + // 主题色,对底部按钮和选中日期有效 | ||
39 | + color: { | ||
40 | + type: String, | ||
41 | + default: '#3c9cff' | ||
42 | + }, | ||
43 | + // 最小的可选日期 | ||
44 | + minDate: { | ||
45 | + type: [String, Number], | ||
46 | + default: 0 | ||
47 | + }, | ||
48 | + // 最大可选日期 | ||
49 | + maxDate: { | ||
50 | + type: [String, Number], | ||
51 | + default: 0 | ||
52 | + }, | ||
53 | + // 默认选中的日期,mode为multiple或range是必须为数组格式 | ||
54 | + defaultDate: { | ||
55 | + type: [Array, String, Date, null], | ||
56 | + default: null | ||
57 | + }, | ||
58 | + // mode=multiple时,最多可选多少个日期 | ||
59 | + maxCount: { | ||
60 | + type: [String, Number], | ||
61 | + default: Number.MAX_SAFE_INTEGER | ||
62 | + }, | ||
63 | + // 日期行高 | ||
64 | + rowHeight: { | ||
65 | + type: [String, Number], | ||
66 | + default: 56 | ||
67 | + }, | ||
68 | + // 日期格式化函数 | ||
69 | + formatter: { | ||
70 | + type: [Function, null], | ||
71 | + default: null | ||
72 | + }, | ||
73 | + // 是否显示农历 | ||
74 | + showLunar: { | ||
75 | + type: Boolean, | ||
76 | + default: false | ||
77 | + }, | ||
78 | + // 是否显示月份背景色 | ||
79 | + showMark: { | ||
80 | + type: Boolean, | ||
81 | + default: true | ||
82 | + }, | ||
83 | + // 确定按钮的文字 | ||
84 | + confirmText: { | ||
85 | + type: String, | ||
86 | + default: '确定' | ||
87 | + }, | ||
88 | + // 确认按钮处于禁用状态时的文字 | ||
89 | + confirmDisabledText: { | ||
90 | + type: String, | ||
91 | + default: '确定' | ||
92 | + }, | ||
93 | + // 是否允许点击遮罩关闭日历 | ||
94 | + closeOnClickOverlay: { | ||
95 | + type: Boolean, | ||
96 | + default: false | ||
97 | + }, | ||
98 | + // 是否允许点击确认按钮关闭日历 | ||
99 | + closeOnClickConfirm: { | ||
100 | + type: Boolean, | ||
101 | + default: true | ||
102 | + }, | ||
103 | + // 是否为只读状态,只读状态下禁止选择日期 | ||
104 | + readonly: { | ||
105 | + type: Boolean, | ||
106 | + default: false | ||
107 | + }, | ||
108 | + // 是否展示确认按钮 | ||
109 | + showConfirm: { | ||
110 | + type: Boolean, | ||
111 | + default: true | ||
112 | + }, | ||
113 | + // 日期区间最多可选天数,默认无限制,mode = range时有效 Infinity | ||
114 | + maxRange: { | ||
115 | + type: [Number, String], | ||
116 | + default: Number.MAX_SAFE_INTEGER | ||
117 | + }, | ||
118 | + // 范围选择超过最多可选天数时的提示文案,mode = range时有效 | ||
119 | + rangePrompt: { | ||
120 | + type: String, | ||
121 | + default: '' | ||
122 | + }, | ||
123 | + // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 | ||
124 | + showRangePrompt: { | ||
125 | + type: Boolean, | ||
126 | + default: true | ||
127 | + }, | ||
128 | + // 是否允许日期范围的起止时间为同一天,mode = range时有效 | ||
129 | + allowSameDay: { | ||
130 | + type: Boolean, | ||
131 | + default: false | ||
132 | + }, | ||
133 | + // 圆角值 | ||
134 | + round: { | ||
135 | + type: [Boolean, String, Number], | ||
136 | + default: 0 | ||
137 | + }, | ||
138 | + // 最多展示月份数量 | ||
139 | + monthNum: { | ||
140 | + type: [Number, String], | ||
141 | + default: 3 | ||
142 | + }, | ||
143 | + ...uni.$uv?.props?.calendar | ||
144 | + } | ||
145 | +} |
1 | +<template> | ||
2 | + <uv-popup | ||
3 | + ref="calendarPopup" | ||
4 | + mode="bottom" | ||
5 | + closeable | ||
6 | + :round="round" | ||
7 | + :closeOnClickOverlay="closeOnClickOverlay" | ||
8 | + @change="popupChange" | ||
9 | + > | ||
10 | + <view class="uv-calendar"> | ||
11 | + <uvHeader | ||
12 | + :title="title" | ||
13 | + :subtitle="subtitle" | ||
14 | + :showSubtitle="showSubtitle" | ||
15 | + :showTitle="showTitle" | ||
16 | + ></uvHeader> | ||
17 | + <scroll-view | ||
18 | + :style="{ height: $uv.addUnit(listHeight) }" | ||
19 | + scroll-y | ||
20 | + @scroll="onScroll" | ||
21 | + :scroll-top="scrollTop" | ||
22 | + :scrollIntoView="scrollIntoView" | ||
23 | + > | ||
24 | + <uvMonth | ||
25 | + :color="color" | ||
26 | + :rowHeight="rowHeight" | ||
27 | + :showMark="showMark" | ||
28 | + :months="months" | ||
29 | + :mode="mode" | ||
30 | + :maxCount="maxCount" | ||
31 | + :startText="startText" | ||
32 | + :endText="endText" | ||
33 | + :defaultDate="defaultDate" | ||
34 | + :minDate="innerMinDate" | ||
35 | + :maxDate="innerMaxDate" | ||
36 | + :maxMonth="monthNum" | ||
37 | + :readonly="readonly" | ||
38 | + :maxRange="maxRange" | ||
39 | + :rangePrompt="rangePrompt" | ||
40 | + :showRangePrompt="showRangePrompt" | ||
41 | + :allowSameDay="allowSameDay" | ||
42 | + ref="month" | ||
43 | + @monthSelected="monthSelected" | ||
44 | + @updateMonthTop="updateMonthTop" | ||
45 | + @change="changeDay" | ||
46 | + ></uvMonth> | ||
47 | + </scroll-view> | ||
48 | + <slot name="footer" v-if="showConfirm"> | ||
49 | + <view class="uv-calendar__confirm"> | ||
50 | + <uv-button | ||
51 | + shape="circle" | ||
52 | + :text="buttonDisabled ? confirmDisabledText : confirmText" | ||
53 | + :color="color" | ||
54 | + @click="confirm" | ||
55 | + :disabled="buttonDisabled" | ||
56 | + ></uv-button> | ||
57 | + </view> | ||
58 | + </slot> | ||
59 | + </view> | ||
60 | + </uv-popup> | ||
61 | +</template> | ||
62 | + | ||
63 | +<script> | ||
64 | +import mpMixin from '../../libs/mixin/mpMixin.js' | ||
65 | +import mixin from '../../libs/mixin/mixin.js' | ||
66 | +import uvHeader from './header.vue' | ||
67 | +import uvMonth from './month.vue' | ||
68 | +import props from './props.js' | ||
69 | +import dayjs from '../../libs/util/dayjs.js' | ||
70 | +import Calendar from './calendar.js' | ||
71 | +/** | ||
72 | + * Calendar 日历 | ||
73 | + * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中. | ||
74 | + * @tutorial https://www.uvui.cn/components/calendar.html | ||
75 | + * | ||
76 | + * @property {String} title 标题内容 (默认 日期选择 ) | ||
77 | + * @property {Boolean} showTitle 是否显示标题 (默认 true ) | ||
78 | + * @property {Boolean} showSubtitle 是否显示副标题 (默认 true ) | ||
79 | + * @property {String} mode 日期类型选择 single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' ) | ||
80 | + * @property {String} startText mode=range时,第一个日期底部的提示文字 (默认 '开始' ) | ||
81 | + * @property {String} endText mode=range时,最后一个日期底部的提示文字 (默认 '结束' ) | ||
82 | + * @property {Array} customList 自定义列表 | ||
83 | + * @property {String} color 主题色,对底部按钮和选中日期有效 (默认 ‘#3c9cff' ) | ||
84 | + * @property {String | Number} minDate 最小的可选日期 (默认 0 ) | ||
85 | + * @property {String | Number} maxDate 最大可选日期 (默认 0 ) | ||
86 | + * @property {Array | String| Date} defaultDate 默认选中的日期,mode为multiple或range是必须为数组格式 | ||
87 | + * @property {String | Number} maxCount mode=multiple时,最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER ) | ||
88 | + * @property {String | Number} rowHeight 日期行高 (默认 56 ) | ||
89 | + * @property {Function} formatter 日期格式化函数 | ||
90 | + * @property {Boolean} showLunar 是否显示农历 (默认 false ) | ||
91 | + * @property {Boolean} showMark 是否显示月份背景色 (默认 true ) | ||
92 | + * @property {String} confirmText 确定按钮的文字 (默认 '确定' ) | ||
93 | + * @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' ) | ||
94 | + * @property {Boolean} show 是否显示日历弹窗 (默认 false ) | ||
95 | + * @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false ) | ||
96 | + * @property {Boolean} closeOnClickConfirm 是否允许点击确认按钮关闭日历,设置为false不影响confirm事件返回 (默认 true ) | ||
97 | + * @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false ) | ||
98 | + * @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效 | ||
99 | + * @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效 | ||
100 | + * @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true ) | ||
101 | + * @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false ) | ||
102 | + * @property {Number|String} round 圆角值,默认无圆角 (默认 0 ) | ||
103 | + * @property {Number|String} monthNum 最多展示的月份数量 (默认 3 ) | ||
104 | + * | ||
105 | + * @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数 | ||
106 | + * @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件 | ||
107 | + * @example <uv-calendar ref="calendar" :defaultDate="defaultDateMultiple" mode="multiple" @confirm="confirm"> | ||
108 | + </uv-calendar> | ||
109 | + * */ | ||
110 | +export default { | ||
111 | + name: 'uv-calendar', | ||
112 | + emits:['confirm','close','change'], | ||
113 | + mixins: [mpMixin, mixin, props], | ||
114 | + components: { | ||
115 | + uvHeader, | ||
116 | + uvMonth | ||
117 | + }, | ||
118 | + data() { | ||
119 | + return { | ||
120 | + // 需要显示的月份的数组 | ||
121 | + months: [], | ||
122 | + // 在月份滚动区域中,当前视图中月份的index索引 | ||
123 | + monthIndex: 0, | ||
124 | + // 月份滚动区域的高度 | ||
125 | + listHeight: 0, | ||
126 | + // month组件中选择的日期数组 | ||
127 | + selected: [], | ||
128 | + scrollIntoView: '', | ||
129 | + scrollTop:0, | ||
130 | + // 过滤处理方法 | ||
131 | + innerFormatter: (value) => value | ||
132 | + } | ||
133 | + }, | ||
134 | + watch: { | ||
135 | + selectedChange: { | ||
136 | + immediate: true, | ||
137 | + handler(n) { | ||
138 | + this.setMonth() | ||
139 | + } | ||
140 | + } | ||
141 | + }, | ||
142 | + computed: { | ||
143 | + // 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理 | ||
144 | + innerMaxDate() { | ||
145 | + return this.$uv.test.number(this.maxDate) | ||
146 | + ? Number(this.maxDate) | ||
147 | + : this.maxDate | ||
148 | + }, | ||
149 | + innerMinDate() { | ||
150 | + return this.$uv.test.number(this.minDate) | ||
151 | + ? Number(this.minDate) | ||
152 | + : this.minDate | ||
153 | + }, | ||
154 | + // 多个条件的变化,会引起选中日期的变化,这里统一管理监听 | ||
155 | + selectedChange() { | ||
156 | + return [this.innerMinDate, this.innerMaxDate, this.defaultDate] | ||
157 | + }, | ||
158 | + subtitle() { | ||
159 | + // 初始化时,this.months为空数组,所以需要特别判断处理 | ||
160 | + if (this.months.length) { | ||
161 | + return `${this.months[this.monthIndex].year}年${ | ||
162 | + this.months[this.monthIndex].month | ||
163 | + }月` | ||
164 | + } else { | ||
165 | + return '' | ||
166 | + } | ||
167 | + }, | ||
168 | + buttonDisabled() { | ||
169 | + // 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态 | ||
170 | + if (this.mode === 'range') { | ||
171 | + if (this.selected.length <= 1) { | ||
172 | + return true | ||
173 | + } else { | ||
174 | + return false | ||
175 | + } | ||
176 | + } else { | ||
177 | + return false | ||
178 | + } | ||
179 | + } | ||
180 | + }, | ||
181 | + mounted() { | ||
182 | + this.start = Date.now() | ||
183 | + this.init() | ||
184 | + }, | ||
185 | + methods: { | ||
186 | + // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用 | ||
187 | + setFormatter(e) { | ||
188 | + this.innerFormatter = e | ||
189 | + }, | ||
190 | + // 点击日期框触发 | ||
191 | + changeDay(e) { | ||
192 | + this.$emit('change',e); | ||
193 | + }, | ||
194 | + // month组件内部选择日期后,通过事件通知给父组件 | ||
195 | + monthSelected(e) { | ||
196 | + this.selected = e | ||
197 | + if (!this.showConfirm) { | ||
198 | + // 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还 | ||
199 | + if ( | ||
200 | + this.mode === 'multiple' || | ||
201 | + this.mode === 'single' || | ||
202 | + (this.mode === 'range' && this.selected.length >= 2) | ||
203 | + ) { | ||
204 | + this.$emit('confirm', this.selected) | ||
205 | + } | ||
206 | + } | ||
207 | + }, | ||
208 | + init() { | ||
209 | + // 校验maxDate,不能小于minDate | ||
210 | + if ( | ||
211 | + this.innerMaxDate && | ||
212 | + this.innerMinDate && | ||
213 | + new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime() | ||
214 | + ) { | ||
215 | + return this.$uv.error('maxDate不能小于minDate') | ||
216 | + } | ||
217 | + // 滚动区域的高度 | ||
218 | + this.listHeight = this.rowHeight * 5 + 30 | ||
219 | + this.setMonth() | ||
220 | + }, | ||
221 | + open() { | ||
222 | + this.setMonth() | ||
223 | + this.$refs.calendarPopup.open(); | ||
224 | + }, | ||
225 | + popupChange(e) { | ||
226 | + if(!e.show) { | ||
227 | + this.$emit('close'); | ||
228 | + } | ||
229 | + }, | ||
230 | + // 点击确定按钮 | ||
231 | + confirm() { | ||
232 | + if (!this.buttonDisabled) { | ||
233 | + this.$emit('confirm', this.selected) | ||
234 | + } | ||
235 | + if (this.closeOnClickConfirm) { | ||
236 | + this.$refs.calendarPopup.close(); | ||
237 | + } | ||
238 | + }, | ||
239 | + // 获得两个日期之间的月份数 | ||
240 | + getMonths(minDate, maxDate) { | ||
241 | + const minYear = dayjs(minDate).year() | ||
242 | + const minMonth = dayjs(minDate).month() + 1 | ||
243 | + const maxYear = dayjs(maxDate).year() | ||
244 | + const maxMonth = dayjs(maxDate).month() + 1 | ||
245 | + return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1 | ||
246 | + }, | ||
247 | + // 设置月份数据 | ||
248 | + setMonth() { | ||
249 | + // 最小日期的毫秒数 | ||
250 | + const minDate = this.innerMinDate || dayjs().valueOf() | ||
251 | + // 如果没有指定最大日期,则往后推3个月 | ||
252 | + const maxDate = | ||
253 | + this.innerMaxDate || | ||
254 | + dayjs(minDate) | ||
255 | + .add(this.monthNum - 1, 'month') | ||
256 | + .valueOf() | ||
257 | + // 最大最小月份之间的共有多少个月份, | ||
258 | + const months = this.$uv.range( | ||
259 | + 1, | ||
260 | + this.monthNum, | ||
261 | + this.getMonths(minDate, maxDate) | ||
262 | + ) | ||
263 | + // 先清空数组 | ||
264 | + this.months = [] | ||
265 | + for (let i = 0; i < months; i++) { | ||
266 | + this.months.push({ | ||
267 | + date: new Array( | ||
268 | + dayjs(minDate).add(i, 'month').daysInMonth() | ||
269 | + ) | ||
270 | + .fill(1) | ||
271 | + .map((item, index) => { | ||
272 | + // 日期,取值1-31 | ||
273 | + let day = index + 1 | ||
274 | + // 星期,0-6,0为周日 | ||
275 | + const week = dayjs(minDate) | ||
276 | + .add(i, 'month') | ||
277 | + .date(day) | ||
278 | + .day() | ||
279 | + const date = dayjs(minDate) | ||
280 | + .add(i, 'month') | ||
281 | + .date(day) | ||
282 | + .format('YYYY-MM-DD') | ||
283 | + let topInfo = '' | ||
284 | + let bottomInfo = '' | ||
285 | + if (this.showLunar) { | ||
286 | + // 将日期转为农历格式 | ||
287 | + const lunar = Calendar.solar2lunar( | ||
288 | + dayjs(date).year(), | ||
289 | + dayjs(date).month() + 1, | ||
290 | + dayjs(date).date() | ||
291 | + ) | ||
292 | + bottomInfo = lunar.IDayCn | ||
293 | + } | ||
294 | + let config = { | ||
295 | + day, | ||
296 | + week, | ||
297 | + // 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态 | ||
298 | + disabled: | ||
299 | + dayjs(date).isBefore( | ||
300 | + dayjs(minDate).format('YYYY-MM-DD') | ||
301 | + ) || | ||
302 | + dayjs(date).isAfter( | ||
303 | + dayjs(maxDate).format('YYYY-MM-DD') | ||
304 | + ), | ||
305 | + // 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理 | ||
306 | + date: new Date(date), | ||
307 | + topInfo, | ||
308 | + bottomInfo, | ||
309 | + dot: false, | ||
310 | + month: | ||
311 | + dayjs(minDate).add(i, 'month').month() + 1 | ||
312 | + } | ||
313 | + const formatter = | ||
314 | + this.formatter || this.innerFormatter | ||
315 | + return formatter(config) | ||
316 | + }), | ||
317 | + // 当前所属的月份 | ||
318 | + month: dayjs(minDate).add(i, 'month').month() + 1, | ||
319 | + // 当前年份 | ||
320 | + year: dayjs(minDate).add(i, 'month').year() | ||
321 | + }) | ||
322 | + } | ||
323 | + | ||
324 | + }, | ||
325 | + // 滚动到默认设置的月份 | ||
326 | + scrollIntoDefaultMonth(selected) { | ||
327 | + // 查询默认日期在可选列表的下标 | ||
328 | + const _index = this.months.findIndex(({ | ||
329 | + year, | ||
330 | + month | ||
331 | + }) => { | ||
332 | + month = this.$uv.padZero(month) | ||
333 | + return `${year}-${month}` === selected | ||
334 | + }) | ||
335 | + if (_index !== -1) { | ||
336 | + // #ifndef MP-WEIXIN | ||
337 | + this.$nextTick(() => { | ||
338 | + this.scrollIntoView = `month-${_index}` | ||
339 | + }) | ||
340 | + // #endif | ||
341 | + // #ifdef MP-WEIXIN | ||
342 | + this.scrollTop = this.months[_index].top || 0; | ||
343 | + // #endif | ||
344 | + } | ||
345 | + }, | ||
346 | + // scroll-view滚动监听 | ||
347 | + onScroll(event) { | ||
348 | + // 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值 | ||
349 | + const scrollTop = Math.max(0, event.detail.scrollTop) | ||
350 | + // 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引 | ||
351 | + for (let i = 0; i < this.months.length; i++) { | ||
352 | + if (scrollTop >= (this.months[i].top || this.listHeight)) { | ||
353 | + this.monthIndex = i | ||
354 | + } | ||
355 | + } | ||
356 | + }, | ||
357 | + // 更新月份的top值 | ||
358 | + updateMonthTop(topArr = []) { | ||
359 | + // 设置对应月份的top值,用于onScroll方法更新月份 | ||
360 | + topArr.map((item, index) => { | ||
361 | + this.months[index].top = item | ||
362 | + }) | ||
363 | + | ||
364 | + // 获取默认日期的下标 | ||
365 | + if (!this.defaultDate) { | ||
366 | + // 如果没有设置默认日期,则将当天日期设置为默认选中的日期 | ||
367 | + const selected = dayjs().format("YYYY-MM") | ||
368 | + this.scrollIntoDefaultMonth(selected) | ||
369 | + return | ||
370 | + } | ||
371 | + let selected = dayjs().format("YYYY-MM"); | ||
372 | + // 单选模式,可以是字符串或数组,Date对象等 | ||
373 | + if (!this.$uv.test.array(this.defaultDate)) { | ||
374 | + selected = dayjs(this.defaultDate).format("YYYY-MM") | ||
375 | + } else { | ||
376 | + selected = dayjs(this.defaultDate[0]).format("YYYY-MM"); | ||
377 | + } | ||
378 | + this.scrollIntoDefaultMonth(selected) | ||
379 | + } | ||
380 | + } | ||
381 | +} | ||
382 | +</script> | ||
383 | + | ||
384 | +<style lang="scss" scoped> | ||
385 | +.uv-calendar { | ||
386 | + &__confirm { | ||
387 | + padding: 7px 18px; | ||
388 | + } | ||
389 | +} | ||
390 | +</style> |
-
请 注册 或 登录 后发表评论