作者 左学珍

创建:项目初始化

正在显示 31 个修改的文件 包含 4730 行增加0 行删除

要显示太多修改。

为保证性能只显示 31 of 31+ 个文件。

  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 +}
  1 +<script>
  2 + export default {
  3 + onLaunch: function() {
  4 + console.log('App Launch')
  5 + },
  6 + onShow: function() {
  7 + console.log('App Show')
  8 + },
  9 + onHide: function() {
  10 + console.log('App Hide')
  11 + }
  12 + }
  13 +</script>
  14 +
  15 +<style>
  16 + /*每个页面公共css */
  17 +</style>
  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>
  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
  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 +}
  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 +}
  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.
  1 +<p align="center">
  2 + <span>&nbsp;&nbsp;&nbsp;&nbsp;</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
  1 +## 1.1.11(2023-09-03)
  2 +1. npm发布
  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>