@@ -23,8 +23,8 @@
>
> 😜 给项目点点 Star 吧,这对我们真的很重要!
-* 前端 Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
-* 前端 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin)
+* 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
+* 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
* 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson
* 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等
* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录
@@ -156,8 +156,9 @@ ps:核心功能已经实现,正在对接微信小程序中...
| `yudao-dependencies` | Maven 依赖版本管理 |
| `yudao-framework` | Java 框架拓展 |
| `yudao-server` | 管理后台 + 用户 APP 的服务端 |
-| `yudao-ui-admin` | Vue2 管理后台的 UI 界面 |
-| `yudao-ui-admin-vue3` | Vue3 管理后台的 UI 界面 |
+| `yudao-ui-admin` | 管理后台的 Vue2 前端项目 |
+| `yudao-ui-admin-vue3` | 管理后台的 Vue3 前端项目 |
+| `yudao-ui-admin-uniapp` | 管理后台的 uni-app 多端项目 |
| `yudao-ui-app` | 用户 APP 的 UI 界面 |
| `yudao-module-system` | 系统功能的 Module 模块 |
| `yudao-module-member` | 会员中心的 Module 模块 |
@@ -192,14 +193,14 @@ ps:核心功能已经实现,正在对接微信小程序中...
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.0.0 | - |
-### [Vue2 前端](./yudao-ui-admin)
+### [管理后台 Vue2 前端](./yudao-ui-admin)
| 框架 | 说明 | 版本 |
|------------------------------------------------------------------------------|---------------|--------|
| [Vue](https://cn.vuejs.org/index.html) | JavaScript 框架 | 2.6.12 |
| [Vue Element Admin](https://panjiachen.github.io/vue-element-admin-site/zh/) | 后台前端解决方案 | - |
-### [Vue3 前端](./yudao-ui-admin-vue3)
+### [管理后台 Vue3 前端](./yudao-ui-admin-vue3)
|----------------------------------------------------------------------|------------------|--------|
@@ -212,6 +213,13 @@ ps:核心功能已经实现,正在对接微信小程序中...
| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |
| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 |
+### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
+
+| 框架 | 说明 | 版本 |
+|----------------------------------------------------------------------|------------------|--------|
+| [uni-app](hhttps://github.com/dcloudio/uni-app) | 跨平台框架 | 2.0.0 |
+| [uni-ui](https://github.com/dcloudio/uni-ui) | 基于 uni-app 的 UI 框架 | 1.4.20 |
## 🐷 演示图
### 系统功能
@@ -262,3 +270,13 @@ ps:核心功能已经实现,正在对接微信小程序中...
| 模块 | biu | biu | biu |
|---------|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
| 报表设计器 |  |  |  |
+### 移动端(管理后台)
+| biu | biu | biu |
+|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
+|  |  |  |
+|  |  |  |
+|  |  |  |
+目前已经实现登录、我的、工作台、编辑资料、头像修改、密码修改、常见问题、关于我们等基础功能。
@@ -95,7 +95,7 @@ public class UserProfileController {
return success(true);
}
- @PutMapping("/update-avatar")
+ @RequestMapping(value = "/update-avatar", method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题
@ApiOperation("上传用户个人头像")
public CommonResult<String> updateUserAvatar(@RequestParam("avatarFile") MultipartFile file) throws Exception {
if (file.isEmpty()) {
@@ -0,0 +1,16 @@
+######################################################################
+# Build Tools
+/unpackage/*
+/node_modules/*
+# Development Tools
+/.idea/*
+/.vscode/*
+/.hbuilderx/*
+package-lock.json
+yarn.lock
@@ -0,0 +1,34 @@
+<script>
+ import config from './config'
+ import store from '@/store'
+ import { getAccessToken } from '@/utils/auth'
+ export default {
+ onLaunch: function() {
+ this.initApp()
+ },
+ methods: {
+ // 初始化应用
+ initApp() {
+ // 初始化应用配置
+ this.initConfig()
+ // 检查用户登录状态
+ //#ifdef H5
+ this.checkLogin()
+ //#endif
+ initConfig() {
+ this.globalData.config = config
+ checkLogin() {
+ if (!getAccessToken()) {
+ this.$tab.reLaunch('/pages/login')
+ }
+</script>
+<style lang="scss">
+ @import '@/static/scss/index.scss'
+</style>
@@ -0,0 +1,21 @@
+MIT License
+Copyright (c) 2022 芋道
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
@@ -0,0 +1,47 @@
+import request from '@/utils/request'
+// 登录方法
+export function login(username, password, code, uuid) {
+ const data = {
+ username,
+ password,
+ code,
+ uuid
+ return request({
+ url: '/system/auth/login',
+ headers: {
+ isToken: false
+ 'method': 'post',
+ 'data': data
+ })
+}
+// 获取用户详细信息
+export function getInfo() {
+ url: '/system/auth/get-permission-info',
+ 'method': 'get'
+// 退出方法
+export function logout() {
+ url: '/system/auth/logout',
+ 'method': 'post'
+// 获取验证码
+export function getCodeImg() {
+ url: '/system/captcha/get-image',
+ method: 'get',
+ timeout: 20000
@@ -0,0 +1,42 @@
+import upload from '@/utils/upload'
+// 用户密码重置
+export function updateUserPwd(oldPassword, newPassword) {
+ oldPassword,
+ newPassword
+ url: '/system/user/profile/update-password',
+ method: 'put',
+ params: data
+// 查询用户个人信息
+export function getUserProfile() {
+ url: '/system/user/profile/get',
+ method: 'get'
+// 修改用户个人信息
+export function updateUserProfile(data) {
+ url: '/system/user/profile/update',
+ data: data
+// 用户头像上传
+export function uploadAvatar(data) {
+ return upload({
+ url: '/system/user/profile/update-avatar',
+ name: data.name,
+ filePath: data.filePath
@@ -0,0 +1,167 @@
+<template>
+ <view class="uni-section">
+ <view class="uni-section-header" @click="onClick">
+ <view class="uni-section-header__decoration" v-if="type" :class="type" />
+ <slot v-else name="decoration"></slot>
+ <view class="uni-section-header__content">
+ <text :style="{'font-size':titleFontSize,'color':titleColor}" class="uni-section__content-title" :class="{'distraction':!subTitle}">{{ title }}</text>
+ <text v-if="subTitle" :style="{'font-size':subTitleFontSize,'color':subTitleColor}" class="uni-section-header__content-sub">{{ subTitle }}</text>
+ </view>
+ <view class="uni-section-header__slot-right">
+ <slot name="right"></slot>
+ <view class="uni-section-content" :style="{padding: _padding}">
+ <slot />
+</template>
+ /**
+ * Section 标题栏
+ * @description 标题栏
+ * @property {String} type = [line|circle|square] 标题装饰类型
+ * @value line 竖线
+ * @value circle 圆形
+ * @value square 正方形
+ * @property {String} title 主标题
+ * @property {String} titleFontSize 主标题字体大小
+ * @property {String} titleColor 主标题字体颜色
+ * @property {String} subTitle 副标题
+ * @property {String} subTitleFontSize 副标题字体大小
+ * @property {String} subTitleColor 副标题字体颜色
+ * @property {String} padding 默认插槽 padding
+ */
+ name: 'UniSection',
+ emits:['click'],
+ props: {
+ type: {
+ type: String,
+ default: ''
+ title: {
+ required: true,
+ titleFontSize: {
+ default: '14px'
+ titleColor:{
+ default: '#333'
+ subTitle: {
+ subTitleFontSize: {
+ default: '12px'
+ subTitleColor: {
+ default: '#999'
+ padding: {
+ type: [Boolean, String],
+ default: false
+ computed:{
+ _padding(){
+ if(typeof this.padding === 'string'){
+ return this.padding
+ return this.padding?'10px':''
+ watch: {
+ title(newVal) {
+ if (uni.report && newVal !== '') {
+ uni.report('title', newVal)
+ onClick() {
+ this.$emit('click')
+<style lang="scss" >
+ $uni-primary: #2979ff !default;
+ .uni-section {
+ background-color: #fff;
+ .uni-section-header {
+ position: relative;
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ flex-direction: row;
+ align-items: center;
+ padding: 12px 10px;
+ font-weight: normal;
+ &__decoration{
+ margin-right: 6px;
+ background-color: $uni-primary;
+ &.line {
+ width: 4px;
+ height: 12px;
+ border-radius: 10px;
+ &.circle {
+ width: 8px;
+ height: 8px;
+ border-top-right-radius: 50px;
+ border-top-left-radius: 50px;
+ border-bottom-left-radius: 50px;
+ border-bottom-right-radius: 50px;
+ &.square {
+ &__content {
+ flex-direction: column;
+ flex: 1;
+ color: #333;
+ .distraction {
+ &-sub {
+ margin-top: 2px;
+ &__slot-right{
+ font-size: 14px;
+ .uni-section-content{
@@ -0,0 +1,26 @@
+// 应用全局配置
+module.exports = {
+ // baseUrl: 'http://localhost:8080',
+ baseUrl: 'http://localhost:48080/admin-api',
+ // 应用信息
+ appInfo: {
+ // 应用名称
+ name: "yudao-app",
+ // 应用版本
+ version: "1.0.0",
+ // 应用logo
+ logo: "/static/logo.png",
+ // 官方网站
+ site_url: "https://iocoder.cn",
+ // 政策协议
+ agreements: [{
+ title: "隐私政策",
+ url: "https://iocoder.cn"
+ {
+ title: "用户服务协议",
+ ]
@@ -0,0 +1,17 @@
+import Vue from 'vue'
+import App from './App'
+import store from './store' // store
+import plugins from './plugins' // plugins
+import './permission' // permission
+Vue.use(plugins)
+Vue.config.productionTip = false
+Vue.prototype.$store = store
+App.mpType = 'app'
+const app = new Vue({
+ ...App
+})
+app.$mount()
@@ -0,0 +1,69 @@
+{
+ "name" : "芋道移动端",
+ "appid" : "__UNI__25A9D80",
+ "description" : "",
+ "versionName" : "1.0.0",
+ "versionCode" : "100",
+ "transformPx" : false,
+ "app-plus" : {
+ "usingComponents" : true,
+ "nvueCompiler" : "uni-app",
+ "splashscreen" : {
+ "alwaysShowBeforeRender" : true,
+ "waiting" : true,
+ "autoclose" : true,
+ "delay" : 0
+ "modules" : {},
+ "distribute" : {
+ "android" : {
+ "permissions" : [
+ "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+ "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+ "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+ "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+ "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+ "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+ "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+ "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+ "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+ "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+ "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+ "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+ "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+ "<uses-feature android:name=\"android.hardware.camera\"/>",
+ "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+ "ios" : {},
+ "sdkConfigs" : {}
+ "quickapp" : {},
+ "mp-weixin" : {
+ "appid" : "wxccd7e2a0911b3397",
+ "setting" : {
+ "urlCheck" : false,
+ "es6" : false,
+ "minified" : true,
+ "postcss" : true
+ "optimization" : {
+ "subPackages" : true
+ "usingComponents" : true
+ "vueVersion" : "2",
+ "h5" : {
+ "template" : "static/index.html",
+ "devServer" : {
+ "port" : 9090,
+ "https" : false
+ "title" : "Yudao-App",
+ "router" : {
+ "mode" : "hash",
+ "base" : "./"
@@ -0,0 +1,97 @@
+ "pages": [{
+ "path": "pages/login",
+ "style": {
+ "navigationBarTitleText": "登录"
+ }, {
+ "path": "pages/index",
+ "navigationBarTitleText": "芋道移动端框架",
+ "navigationStyle": "custom"
+ "path": "pages/work/index",
+ "navigationBarTitleText": "工作台"
+ "path": "pages/mine/index",
+ "navigationBarTitleText": "我的"
+ "path": "pages/mine/avatar/index",
+ "navigationBarTitleText": "修改头像"
+ "path": "pages/mine/info/index",
+ "navigationBarTitleText": "个人信息"
+ "path": "pages/mine/info/edit",
+ "navigationBarTitleText": "编辑资料"
+ "path": "pages/mine/pwd/index",
+ "navigationBarTitleText": "修改密码"
+ "path": "pages/mine/setting/index",
+ "navigationBarTitleText": "应用设置"
+ "path": "pages/mine/help/index",
+ "navigationBarTitleText": "常见问题"
+ "path": "pages/mine/about/index",
+ "navigationBarTitleText": "关于我们"
+ "path": "pages/common/webview/index",
+ "navigationBarTitleText": "浏览网页"
+ "path": "pages/common/textview/index",
+ "navigationBarTitleText": "浏览文本"
+ }],
+ "tabBar": {
+ "color": "#000000",
+ "selectedColor": "#000000",
+ "borderStyle": "white",
+ "backgroundColor": "#ffffff",
+ "list": [{
+ "pagePath": "pages/index",
+ "iconPath": "static/images/tabbar/home.png",
+ "selectedIconPath": "static/images/tabbar/home_.png",
+ "text": "首页"
+ "pagePath": "pages/work/index",
+ "iconPath": "static/images/tabbar/work.png",
+ "selectedIconPath": "static/images/tabbar/work_.png",
+ "text": "工作台"
+ "pagePath": "pages/mine/index",
+ "iconPath": "static/images/tabbar/mine.png",
+ "selectedIconPath": "static/images/tabbar/mine_.png",
+ "text": "我的"
+ "globalStyle": {
+ "navigationBarTextStyle": "black",
+ "navigationBarTitleText": "RuoYi",
+ "navigationBarBackgroundColor": "#FFFFFF"
@@ -0,0 +1,43 @@
+ <view>
+ <uni-card class="view-title" :title="title">
+ <text class="uni-body view-content">{{ content }}</text>
+ </uni-card>
+ data() {
+ return {
+ title: '',
+ content: ''
+ onLoad(options) {
+ this.title = options.title
+ this.content = options.content
+ uni.setNavigationBarTitle({
+ title: options.title
+<style scoped>
+ page {
+ background-color: #ffffff;
+ .view-title {
+ font-weight: bold;
+ .view-content {
+ font-size: 26rpx;
+ padding: 12px 5px 0;
+ line-height: 24px;
+ <view v-if="params.url">
+ <web-view :webview-styles="webviewStyles" :src="`${params.url}`"></web-view>
+ params: {},
+ webviewStyles: {
+ progress: {
+ color: "#FF3333"
+ src: {
+ type: [String],
+ default: null
+ onLoad(event) {
+ this.params = event
+ if (event.title) {
+ title: event.title
+ <view class="content">
+ <image class="logo" src="/static/logo.png"></image>
+ <view class="text-area">
+ <text class="title">Hello 芋道</text>
+ onLoad: function() {
+<style>
+ .content {
+ justify-content: center;
+ .logo {
+ height: 200rpx;
+ width: 200rpx;
+ margin-top: 200rpx;
+ margin-left: auto;
+ margin-right: auto;
+ margin-bottom: 50rpx;
+ .text-area {
+ .title {
+ font-size: 36rpx;
+ color: #8f8f94;
@@ -0,0 +1,182 @@
+ <view class="normal-login-container">
+ <view class="logo-content align-center justify-center flex">
+ <image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
+ </image>
+ <text class="title">芋道移动端登录</text>
+ <view class="login-form-content">
+ <view class="input-item flex align-center">
+ <view class="iconfont icon-user icon"></view>
+ <input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
+ <view class="iconfont icon-password icon"></view>
+ <input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
+ <view class="input-item flex align-center" v-if="captchaEnabled">
+ <view class="iconfont icon-code icon"></view>
+ <input v-model="loginForm.code" class="input" placeholder="请输入验证码" maxlength="5" />
+ <image :src="codeUrl" @click="getCode" class="login-code-img"></image>
+ <view class="action-btn">
+ <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
+ <view class="xieyi text-center">
+ <text class="text-grey1">登录即代表同意</text>
+ <text @click="handleUserAgrement" class="text-blue">《用户协议》</text>
+ <text @click="handlePrivacy" class="text-blue">《隐私协议》</text>
+ import { getCodeImg } from '@/api/login'
+ codeUrl: "",
+ captchaEnabled: true,
+ globalConfig: getApp().globalData.config,
+ loginForm: {
+ username: "admin",
+ password: "admin123",
+ code: "",
+ uuid: ''
+ created() {
+ this.getCode()
+ // 隐私协议
+ handlePrivacy() {
+ let site = this.globalConfig.appInfo.agreements[0]
+ this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
+ // 用户协议
+ handleUserAgrement() {
+ let site = this.globalConfig.appInfo.agreements[1]
+ // 获取图形验证码
+ getCode() {
+ getCodeImg().then(res => {
+ res = res.data;
+ this.captchaEnable = res.enable;
+ if (this.captchaEnable) {
+ this.codeUrl = "data:image/gif;base64," + res.img;
+ this.loginForm.uuid = res.uuid;
+ // 登录方法
+ async handleLogin() {
+ if (this.loginForm.username === "") {
+ this.$modal.msgError("请输入您的账号")
+ } else if (this.loginForm.password === "") {
+ this.$modal.msgError("请输入您的密码")
+ } else if (this.loginForm.code === "" && this.captchaEnabled) {
+ this.$modal.msgError("请输入验证码")
+ } else {
+ this.$modal.loading("登录中,请耐心等待...")
+ this.pwdLogin()
+ // 密码登录
+ async pwdLogin() {
+ this.$store.dispatch('Login', this.loginForm).then(() => {
+ this.$modal.closeLoading()
+ this.loginSuccess()
+ }).catch(() => {
+ if (this.captchaEnabled) {
+ // 登录成功后,处理函数
+ loginSuccess(result) {
+ // 设置用户信息
+ this.$store.dispatch('GetInfo').then(res => {
+ this.$tab.reLaunch('/pages/index')
+ .normal-login-container {
+ width: 100%;
+ .logo-content {
+ font-size: 21px;
+ text-align: center;
+ padding-top: 15%;
+ image {
+ border-radius: 4px;
+ margin-left: 10px;
+ .login-form-content {
+ margin: 20px auto;
+ margin-top: 15%;
+ width: 80%;
+ .input-item {
+ background-color: #f5f6f7;
+ height: 45px;
+ border-radius: 20px;
+ .icon {
+ font-size: 38rpx;
+ color: #999;
+ .input {
+ line-height: 20px;
+ text-align: left;
+ padding-left: 15px;
+ .login-btn {
+ margin-top: 40px;
+ .xieyi {
+ margin-top: 20px;
+ .easyinput {
+ .login-code-img {
@@ -0,0 +1,75 @@
+ <view class="about-container">
+ <view class="header-section text-center">
+ <image style="width: 150rpx;height: 150rpx;" src="/static/logo200.png" mode="widthFix">
+ <uni-title type="h2" title="芋道移动端"></uni-title>
+ <view class="content-section">
+ <view class="menu-list">
+ <view class="list-cell list-cell-arrow">
+ <view class="menu-item-box">
+ <view>版本信息</view>
+ <view class="text-right">v{{version}}</view>
+ <view>官方邮箱</view>
+ <view class="text-right">7685413@qq.com</view>
+ <view>服务热线</view>
+ <view class="text-right">400-999-9999</view>
+ <view>公司网站</view>
+ <view class="text-right">
+ <uni-link :href="url" :text="url" showUnderLine="false"></uni-link>
+ <view class="copyright">
+ <view>Copyright © 2022 iocoder.cn All Rights Reserved.</view>
+ url: getApp().globalData.config.appInfo.site_url,
+ version: getApp().globalData.config.appInfo.version
+ background-color: #f8f8f8;
+ .copyright {
+ margin-top: 50rpx;
+ line-height: 60rpx;
+ .header-section {
+ padding: 30rpx 0 0;
@@ -0,0 +1,631 @@
+ <view class="container">
+ <view class="page-body uni-content-info">
+ <view class='cropper-content'>
+ <view v-if="isShowImg" class="uni-corpper" :style="'width:'+cropperInitW+'px;height:'+cropperInitH+'px;background:#000'">
+ <view class="uni-corpper-content" :style="'width:'+cropperW+'px;height:'+cropperH+'px;left:'+cropperL+'px;top:'+cropperT+'px'">
+ <image :src="imageSrc" :style="'width:'+cropperW+'px;height:'+cropperH+'px'"></image>
+ <view class="uni-corpper-crop-box" @touchstart.stop="contentStartMove" @touchmove.stop="contentMoveing" @touchend.stop="contentTouchEnd"
+ :style="'left:'+cutL+'px;top:'+cutT+'px;right:'+cutR+'px;bottom:'+cutB+'px'">
+ <view class="uni-cropper-view-box">
+ <view class="uni-cropper-dashed-h"></view>
+ <view class="uni-cropper-dashed-v"></view>
+ <view class="uni-cropper-line-t" data-drag="top" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
+ <view class="uni-cropper-line-r" data-drag="right" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
+ <view class="uni-cropper-line-b" data-drag="bottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
+ <view class="uni-cropper-line-l" data-drag="left" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
+ <view class="uni-cropper-point point-t" data-drag="top" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
+ <view class="uni-cropper-point point-tr" data-drag="topTight"></view>
+ <view class="uni-cropper-point point-r" data-drag="right" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
+ <view class="uni-cropper-point point-rb" data-drag="rightBottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
+ <view class="uni-cropper-point point-b" data-drag="bottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove" @touchend.stop="dragEnd"></view>
+ <view class="uni-cropper-point point-bl" data-drag="bottomLeft"></view>
+ <view class="uni-cropper-point point-l" data-drag="left" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
+ <view class="uni-cropper-point point-lt" data-drag="leftTop"></view>
+ <view class='cropper-config'>
+ <button type="primary reverse" @click="getImage" style='margin-top: 30rpx;'> 选择头像 </button>
+ <button type="warn" @click="getImageInfo" style='margin-top: 30rpx;'> 提交 </button>
+ <canvas canvas-id="myCanvas" :style="'position:absolute;border: 1px solid red; width:'+imageW+'px;height:'+imageH+'px;top:-9999px;left:-9999px;'"></canvas>
+ import config from '@/config'
+ import store from "@/store"
+ import { uploadAvatar } from "@/api/system/user"
+ const baseUrl = config.baseUrl
+ let sysInfo = uni.getSystemInfoSync()
+ let SCREEN_WIDTH = sysInfo.screenWidth
+ let PAGE_X, // 手按下的x位置
+ PAGE_Y, // 手按下y的位置
+ PR = sysInfo.pixelRatio, // dpi
+ T_PAGE_X, // 手移动的时候x的位置
+ T_PAGE_Y, // 手移动的时候Y的位置
+ CUT_L, // 初始化拖拽元素的left值
+ CUT_T, // 初始化拖拽元素的top值
+ CUT_R, // 初始化拖拽元素的
+ CUT_B, // 初始化拖拽元素的
+ CUT_W, // 初始化拖拽元素的宽度
+ CUT_H, // 初始化拖拽元素的高度
+ IMG_RATIO, // 图片比例
+ IMG_REAL_W, // 图片实际的宽度
+ IMG_REAL_H, // 图片实际的高度
+ DRAFG_MOVE_RATIO = 1, //移动时候的比例,
+ INIT_DRAG_POSITION = 100, // 初始化屏幕宽度和裁剪区域的宽度之差,用于设置初始化裁剪的宽度
+ DRAW_IMAGE_W = sysInfo.screenWidth // 设置生成的图片宽度
+ * 页面的初始数据
+ imageSrc: store.getters.avatar,
+ isShowImg: false,
+ // 初始化的宽高
+ cropperInitW: SCREEN_WIDTH,
+ cropperInitH: SCREEN_WIDTH,
+ // 动态的宽高
+ cropperW: SCREEN_WIDTH,
+ cropperH: SCREEN_WIDTH,
+ // 动态的left top值
+ cropperL: 0,
+ cropperT: 0,
+ transL: 0,
+ transT: 0,
+ // 图片缩放值
+ scaleP: 0,
+ imageW: 0,
+ imageH: 0,
+ // 裁剪框 宽高
+ cutL: 0,
+ cutT: 0,
+ cutB: SCREEN_WIDTH,
+ cutR: '100%',
+ qualityWidth: DRAW_IMAGE_W,
+ innerAspectRadio: DRAFG_MOVE_RATIO
+ * 生命周期函数--监听页面初次渲染完成
+ onReady: function () {
+ this.loadImage()
+ setData: function (obj) {
+ let that = this
+ Object.keys(obj).forEach(function (key) {
+ that.$set(that.$data, key, obj[key])
+ getImage: function () {
+ var _this = this
+ uni.chooseImage({
+ success: function (res) {
+ _this.setData({
+ imageSrc: res.tempFilePaths[0],
+ _this.loadImage()
+ loadImage: function () {
+ uni.getImageInfo({
+ src: _this.imageSrc,
+ success: function success(res) {
+ IMG_RATIO = 1 / 1
+ if (IMG_RATIO >= 1) {
+ IMG_REAL_W = SCREEN_WIDTH
+ IMG_REAL_H = SCREEN_WIDTH / IMG_RATIO
+ IMG_REAL_W = SCREEN_WIDTH * IMG_RATIO
+ IMG_REAL_H = SCREEN_WIDTH
+ let minRange = IMG_REAL_W > IMG_REAL_H ? IMG_REAL_W : IMG_REAL_H
+ INIT_DRAG_POSITION = minRange > INIT_DRAG_POSITION ? INIT_DRAG_POSITION : minRange
+ // 根据图片的宽高显示不同的效果 保证图片可以正常显示
+ let cutT = Math.ceil((SCREEN_WIDTH / IMG_RATIO - (SCREEN_WIDTH / IMG_RATIO - INIT_DRAG_POSITION)) / 2)
+ let cutB = cutT
+ let cutL = Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH + INIT_DRAG_POSITION) / 2)
+ let cutR = cutL
+ cropperH: SCREEN_WIDTH / IMG_RATIO,
+ // 初始化left right
+ cropperL: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH) / 2),
+ cropperT: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH / IMG_RATIO) / 2),
+ cutL: cutL,
+ cutT: cutT,
+ cutR: cutR,
+ cutB: cutB,
+ imageW: IMG_REAL_W,
+ imageH: IMG_REAL_H,
+ scaleP: IMG_REAL_W / SCREEN_WIDTH,
+ innerAspectRadio: IMG_RATIO
+ let cutL = Math.ceil((SCREEN_WIDTH * IMG_RATIO - (SCREEN_WIDTH * IMG_RATIO)) / 2)
+ let cutT = Math.ceil((SCREEN_WIDTH - INIT_DRAG_POSITION) / 2)
+ cropperW: SCREEN_WIDTH * IMG_RATIO,
+ cropperL: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH * IMG_RATIO) / 2),
+ cropperT: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH) / 2),
+ isShowImg: true
+ uni.hideLoading()
+ // 拖动时候触发的touchStart事件
+ contentStartMove(e) {
+ PAGE_X = e.touches[0].pageX
+ PAGE_Y = e.touches[0].pageY
+ // 拖动时候触发的touchMove事件
+ contentMoveing(e) {
+ var dragLengthX = (PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
+ var dragLengthY = (PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
+ // 左移
+ if (dragLengthX > 0) {
+ if (this.cutL - dragLengthX < 0) dragLengthX = this.cutL
+ if (this.cutR + dragLengthX < 0) dragLengthX = -this.cutR
+ if (dragLengthY > 0) {
+ if (this.cutT - dragLengthY < 0) dragLengthY = this.cutT
+ if (this.cutB + dragLengthY < 0) dragLengthY = -this.cutB
+ this.setData({
+ cutL: this.cutL - dragLengthX,
+ cutT: this.cutT - dragLengthY,
+ cutR: this.cutR + dragLengthX,
+ cutB: this.cutB + dragLengthY
+ contentTouchEnd() {
+ // 获取图片
+ getImageInfo() {
+ uni.showLoading({
+ title: '图片生成中...',
+ // 将图片写入画布
+ const ctx = uni.createCanvasContext('myCanvas')
+ ctx.drawImage(_this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H)
+ ctx.draw(true, () => {
+ // 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio)
+ var canvasW = ((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) * IMG_REAL_W
+ var canvasH = ((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) * IMG_REAL_H
+ var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W
+ var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H
+ uni.canvasToTempFilePath({
+ x: canvasL,
+ y: canvasT,
+ width: canvasW,
+ height: canvasH,
+ destWidth: canvasW,
+ destHeight: canvasH,
+ quality: 0.5,
+ canvasId: 'myCanvas',
+ let data = {name: 'avatarFile', filePath: res.tempFilePath}
+ uploadAvatar(data).then(response => {
+ store.commit('SET_AVATAR', response.data)
+ uni.showToast({ title: "修改成功", icon: 'success' })
+ uni.navigateBack()
+ // 设置大小的时候触发的touchStart事件
+ dragStart(e) {
+ T_PAGE_X = e.touches[0].pageX
+ T_PAGE_Y = e.touches[0].pageY
+ CUT_L = this.cutL
+ CUT_R = this.cutR
+ CUT_B = this.cutB
+ CUT_T = this.cutT
+ // 设置大小的时候触发的touchMove事件
+ dragMove(e) {
+ var dragType = e.target.dataset.drag
+ switch (dragType) {
+ case 'right':
+ var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
+ if (CUT_R + dragLength < 0) dragLength = -CUT_R
+ cutR: CUT_R + dragLength
+ break
+ case 'left':
+ if (CUT_L - dragLength < 0) dragLength = CUT_L
+ if ((CUT_L - dragLength) > (this.cropperW - this.cutR)) dragLength = CUT_L - (this.cropperW - this.cutR)
+ cutL: CUT_L - dragLength
+ case 'top':
+ var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
+ if (CUT_T - dragLength < 0) dragLength = CUT_T
+ if ((CUT_T - dragLength) > (this.cropperH - this.cutB)) dragLength = CUT_T - (this.cropperH - this.cutB)
+ cutT: CUT_T - dragLength
+ case 'bottom':
+ if (CUT_B + dragLength < 0) dragLength = -CUT_B
+ cutB: CUT_B + dragLength
+ case 'rightBottom':
+ var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
+ var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
+ if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B
+ if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R
+ let cutB = CUT_B + dragLengthY
+ let cutR = CUT_R + dragLengthX
+ cutR: cutR
+ default:
+ /* pages/uni-cropper/index.wxss */
+ .uni-content-info {
+ /* position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: block;
+ flex-direction: column; */
+ .cropper-config {
+ padding: 20rpx 40rpx;
+ .cropper-content {
+ min-height: 750rpx;
+ .uni-corpper {
+ overflow: hidden;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-touch-callout: none;
+ box-sizing: border-box;
+ .uni-corpper-content {
+ .uni-corpper-content image {
+ min-width: 0 !important;
+ max-width: none !important;
+ height: 100%;
+ min-height: 0 !important;
+ max-height: none !important;
+ image-orientation: 0deg !important;
+ margin: 0 auto;
+ /* 移动图片效果 */
+ .uni-cropper-drag-box {
+ position: absolute;
+ cursor: move;
+ background: rgba(0, 0, 0, 0.6);
+ z-index: 1;
+ /* 内部的信息 */
+ .uni-corpper-crop-box {
+ background: rgba(255, 255, 255, 0.3);
+ z-index: 2;
+ .uni-corpper-crop-box .uni-cropper-view-box {
+ overflow: visible;
+ outline: 1rpx solid #69f;
+ outline-color: rgba(102, 153, 255, .75)
+ /* 横向虚线 */
+ .uni-cropper-dashed-h {
+ top: 33.33333333%;
+ height: 33.33333333%;
+ border-top: 1rpx dashed rgba(255, 255, 255, 0.5);
+ border-bottom: 1rpx dashed rgba(255, 255, 255, 0.5);
+ /* 纵向虚线 */
+ .uni-cropper-dashed-v {
+ left: 33.33333333%;
+ width: 33.33333333%;
+ border-left: 1rpx dashed rgba(255, 255, 255, 0.5);
+ border-right: 1rpx dashed rgba(255, 255, 255, 0.5);
+ /* 四个方向的线 为了之后的拖动事件*/
+ .uni-cropper-line-t {
+ background-color: #69f;
+ height: 1rpx;
+ opacity: 0.1;
+ cursor: n-resize;
+ .uni-cropper-line-t::before {
+ content: '';
+ top: 50%;
+ right: 0rpx;
+ -webkit-transform: translate3d(0, -50%, 0);
+ transform: translate3d(0, -50%, 0);
+ height: 41rpx;
+ background: transparent;
+ z-index: 11;
+ .uni-cropper-line-r {
+ width: 1rpx;
+ cursor: e-resize;
+ .uni-cropper-line-r::before {
+ left: 50%;
+ width: 41rpx;
+ -webkit-transform: translate3d(-50%, 0, 0);
+ transform: translate3d(-50%, 0, 0);
+ .uni-cropper-line-b {
+ cursor: s-resize;
+ .uni-cropper-line-b::before {
+ .uni-cropper-line-l {
+ cursor: w-resize;
+ .uni-cropper-line-l::before {
+ .uni-cropper-point {
+ width: 5rpx;
+ height: 5rpx;
+ opacity: .75;
+ z-index: 3;
+ .point-t {
+ top: -3rpx;
+ margin-left: -3rpx;
+ .point-tr {
+ left: 100%;
+ .point-r {
+ margin-top: -3rpx;
+ .point-rb {
+ top: 100%;
+ -webkit-transform: translate3d(-50%, -50%, 0);
+ transform: translate3d(-50%, -50%, 0);
+ width: 36rpx;
+ height: 36rpx;
+ z-index: 1112;
+ opacity: 1;
+ .point-b {
+ .point-bl {
+ left: 0%;
+ .point-l {
+ .point-lt {
+ top: 0%;
+ /* 裁剪框预览内容 */
+ .uni-cropper-viewer {
+ .uni-cropper-viewer image {
@@ -0,0 +1,112 @@
+ <view class="help-container">
+ <view v-for="(item, findex) in list" :key="findex" :title="item.title" class="list-title">
+ <view class="text-title">
+ <view :class="item.icon"></view>{{ item.title }}
+ <view class="childList">
+ <view v-for="(child, zindex) in item.childList" :key="zindex" class="question" hover-class="hover"
+ @click="handleText(child)">
+ <view class="text-item">{{ child.title }}</view>
+ <view class="line" v-if="zindex !== item.childList.length - 1"></view>
+ list: [{
+ icon: 'iconfont icon-github',
+ title: '芋道问题',
+ childList: [{
+ title: '芋道开源吗?',
+ content: '开源'
+ title: '芋道可以商用吗?',
+ content: '可以'
+ title: '芋道官网地址多少?',
+ content: 'https://www.iocoder.cn'
+ title: '芋道文档地址多少?',
+ content: 'https://doc.iocoder.cn'
+ }]
+ icon: 'iconfont icon-help',
+ title: '其他问题',
+ title: '如何退出登录?',
+ content: '请点击[我的] - [应用设置] - [退出登录]即可退出登录',
+ title: '如何修改用户头像?',
+ content: '请点击[我的] - [选择头像] - [点击提交]即可更换用户头像',
+ title: '如何修改登录密码?',
+ content: '请点击[我的] - [应用设置] - [修改密码]即可修改登录密码',
+ handleText(item) {
+ this.$tab.navigateTo(`/pages/common/textview/index?title=${item.title}&content=${item.content}`)
+<style lang="scss" scoped>
+ .help-container {
+ margin-bottom: 100rpx;
+ padding: 30rpx;
+ .list-title {
+ margin-bottom: 30rpx;
+ .childList {
+ background: #ffffff;
+ box-shadow: 0px 0px 10rpx rgba(193, 193, 193, 0.2);
+ border-radius: 16rpx;
+ margin-top: 10rpx;
+ .line {
+ background-color: #F5F5F5;
+ .text-title {
+ color: #303133;
+ font-size: 32rpx;
+ margin-left: 10rpx;
+ .iconfont {
+ font-size: 16px;
+ margin-right: 10rpx;
+ .text-item {
+ font-size: 28rpx;
+ padding: 24rpx;
+ .question {
+ color: #606266;
@@ -0,0 +1,198 @@
+ <view class="mine-container" :style="{height: `${windowHeight}px`}">
+ <!--顶部个人信息栏-->
+ <view class="header-section">
+ <view class="flex padding justify-between">
+ <view class="flex align-center">
+ <view v-if="!avatar" class="cu-avatar xl round bg-white">
+ <view class="iconfont icon-people text-gray icon"></view>
+ <image v-if="avatar" @click="handleToAvatar" :src="avatar" class="cu-avatar xl round" mode="widthFix">
+ <view v-if="!name" @click="handleToLogin" class="login-tip">
+ 点击登录
+ <view v-if="name" @click="handleToInfo" class="user-info">
+ <view class="u_title">
+ 用户名:{{ name }}
+ <view @click="handleToInfo" class="flex align-center">
+ <text>个人信息</text>
+ <view class="iconfont icon-right"></view>
+ <view class="mine-actions grid col-4 text-center">
+ <view class="action-item" @click="handleJiaoLiuQun">
+ <view class="iconfont icon-friendfill text-pink icon"></view>
+ <text class="text">交流群</text>
+ <view class="action-item" @click="handleBuilding">
+ <view class="iconfont icon-service text-blue icon"></view>
+ <text class="text">在线客服</text>
+ <view class="iconfont icon-community text-mauve icon"></view>
+ <text class="text">反馈社区</text>
+ <view class="iconfont icon-dianzan text-green icon"></view>
+ <text class="text">点赞我们</text>
+ <view class="list-cell list-cell-arrow" @click="handleToEditInfo">
+ <view class="iconfont icon-user menu-icon"></view>
+ <view>编辑资料</view>
+ <view class="list-cell list-cell-arrow" @click="handleHelp">
+ <view class="iconfont icon-help menu-icon"></view>
+ <view>常见问题</view>
+ <view class="list-cell list-cell-arrow" @click="handleAbout">
+ <view class="iconfont icon-aixin menu-icon"></view>
+ <view>关于我们</view>
+ <view class="list-cell list-cell-arrow" @click="handleToSetting">
+ <view class="iconfont icon-setting menu-icon"></view>
+ <view>应用设置</view>
+ import storage from '@/utils/storage'
+ name: this.$store.state.user.name,
+ computed: {
+ avatar() {
+ return this.$store.state.user.avatar
+ windowHeight() {
+ return uni.getSystemInfoSync().windowHeight - 50
+ handleToInfo() {
+ this.$tab.navigateTo('/pages/mine/info/index')
+ handleToEditInfo() {
+ this.$tab.navigateTo('/pages/mine/info/edit')
+ handleToSetting() {
+ this.$tab.navigateTo('/pages/mine/setting/index')
+ handleToLogin() {
+ handleToAvatar() {
+ this.$tab.navigateTo('/pages/mine/avatar/index')
+ handleLogout() {
+ this.$modal.confirm('确定注销并退出系统吗?').then(() => {
+ this.$store.dispatch('LogOut').then(() => {
+ handleHelp() {
+ this.$tab.navigateTo('/pages/mine/help/index')
+ handleAbout() {
+ this.$tab.navigateTo('/pages/mine/about/index')
+ handleJiaoLiuQun() {
+ this.$modal.showToast('微信搜索 naidaguo 后,添加好友后拉你进技术交流群')
+ handleBuilding() {
+ this.$modal.showToast('模块建设中~')
+ .mine-container {
+ padding: 15px 15px 45px 15px;
+ background-color: #3c96f3;
+ color: white;
+ .login-tip {
+ font-size: 18px;
+ .cu-avatar {
+ border: 2px solid #eaeaea;
+ font-size: 40px;
+ .user-info {
+ margin-left: 15px;
+ .u_title {
+ line-height: 30px;
+ .content-section {
+ top: -50px;
+ .mine-actions {
+ margin: 15px 15px;
+ padding: 20px 0px;
+ border-radius: 8px;
+ background-color: white;
+ .action-item {
+ font-size: 28px;
+ .text {
+ font-size: 13px;
+ margin: 8px 0px;
@@ -0,0 +1,128 @@
+ <view class="example">
+ <uni-forms ref="form" :model="user" labelWidth="80px">
+ <uni-forms-item label="用户昵称" name="nickname">
+ <uni-easyinput v-model="user.nickname" placeholder="请输入昵称" />
+ </uni-forms-item>
+ <uni-forms-item label="手机号码" name="mobile">
+ <uni-easyinput v-model="user.mobile" placeholder="请输入手机号码" />
+ <uni-forms-item label="邮箱" name="email">
+ <uni-easyinput v-model="user.email" placeholder="请输入邮箱" />
+ <!-- TODO 芋艿:uni-data-checkbox 存在问题 -->
+ <uni-forms-item label="性别" name="sex" required>
+<!-- <uni-data-checkbox v-model="user.sex" :localdata="sexs" />-->
+ </uni-forms>
+ <button type="primary" @click="submit">提交</button>
+ import { getUserProfile } from "@/api/system/user"
+ import { updateUserProfile } from "@/api/system/user"
+ user: {
+ nickname: "",
+ mobile: "",
+ email: "",
+ sex: ""
+ sexs: [{
+ text: '男',
+ value: "1"
+ text: '女',
+ value: "2"
+ rules: {
+ nickname: {
+ rules: [{
+ errorMessage: '用户昵称不能为空'
+ mobile: {
+ errorMessage: '手机号码不能为空'
+ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
+ errorMessage: '请输入正确的手机号码'
+ email: {
+ errorMessage: '邮箱地址不能为空'
+ format: 'email',
+ errorMessage: '请输入正确的邮箱地址'
+ onLoad() {
+ this.getUser()
+ onReady() {
+ this.$refs.form.setRules(this.rules)
+ getUser() {
+ getUserProfile().then(response => {
+ this.user = response.data
+ submit(ref) {
+ this.$refs.form.validate().then(res => {
+ updateUserProfile(this.user).then(response => {
+ this.$modal.msgSuccess("修改成功")
+ .example {
+ padding: 15px;
+ .segmented-control {
+ margin-bottom: 15px;
+ .button-group {
+ margin-top: 15px;
+ justify-content: space-around;
+ .form-item {
+ .button {
+ height: 35px;
+ line-height: 35px;
@@ -0,0 +1,44 @@
+ <uni-list>
+ <uni-list-item showExtraIcon="true" :extraIcon="{type: 'person-filled'}" title="昵称" :rightText="user.nickname" />
+ <uni-list-item showExtraIcon="true" :extraIcon="{type: 'phone-filled'}" title="手机号码" :rightText="user.mobile" />
+ <uni-list-item showExtraIcon="true" :extraIcon="{type: 'email-filled'}" title="邮箱" :rightText="user.email" />
+ <uni-list-item showExtraIcon="true" :extraIcon="{type: 'auth-filled'}" title="岗位" :rightText="(user.posts || []).map(post => post.name).join(',')" />
+ <uni-list-item showExtraIcon="true" :extraIcon="{type: 'staff-filled'}" title="角色" :rightText="(user.roles || []).map(role => role.name).join(',')" />
+ <uni-list-item showExtraIcon="true" :extraIcon="{type: 'calendar-filled'}" title="创建日期" :rightText="this.parseTime(user.createTime)" />
+ </uni-list>
+ import { parseTime } from "@/utils/ruoyi"
+ user: {}
+ parseTime(time) {
+ return parseTime(time)
@@ -0,0 +1,85 @@
+ <view class="pwd-retrieve-container">
+ <uni-forms ref="form" :value="user" labelWidth="80px">
+ <uni-forms-item name="oldPassword" label="旧密码">
+ <uni-easyinput type="password" v-model="user.oldPassword" placeholder="请输入旧密码" />
+ <uni-forms-item name="newPassword" label="新密码">
+ <uni-easyinput type="password" v-model="user.newPassword" placeholder="请输入新密码" />
+ <uni-forms-item name="confirmPassword" label="确认密码">
+ <uni-easyinput type="password" v-model="user.confirmPassword" placeholder="请确认新密码" />
+ import { updateUserPwd } from "@/api/system/user"
+ oldPassword: undefined,
+ newPassword: undefined,
+ confirmPassword: undefined
+ oldPassword: {
+ errorMessage: '旧密码不能为空'
+ newPassword: {
+ errorMessage: '新密码不能为空',
+ minLength: 6,
+ maxLength: 20,
+ errorMessage: '长度在 6 到 20 个字符'
+ confirmPassword: {
+ errorMessage: '确认密码不能为空'
+ validateFunction: (rule, value, data) => data.newPassword === value,
+ errorMessage: '两次输入的密码不一致'
+ submit() {
+ updateUserPwd(this.user.oldPassword, this.user.newPassword).then(response => {
+ .pwd-retrieve-container {
+ padding-top: 36rpx;
@@ -0,0 +1,78 @@
+ <view class="setting-container" :style="{height: `${windowHeight}px`}">
+ <view class="list-cell list-cell-arrow" @click="handleToPwd">
+ <view class="iconfont icon-password menu-icon"></view>
+ <view>修改密码</view>
+ <view class="list-cell list-cell-arrow" @click="handleToUpgrade">
+ <view class="iconfont icon-refresh menu-icon"></view>
+ <view>检查更新</view>
+ <view class="list-cell list-cell-arrow" @click="handleCleanTmp">
+ <view class="iconfont icon-clean menu-icon"></view>
+ <view>清理缓存</view>
+ <view class="cu-list menu">
+ <view class="cu-item item-box">
+ <view class="content text-center" @click="handleLogout">
+ <text class="text-black">退出登录</text>
+ windowHeight: uni.getSystemInfoSync().windowHeight
+ handleToPwd() {
+ this.$tab.navigateTo('/pages/mine/pwd/index')
+ handleToUpgrade() {
+ handleCleanTmp() {
+ .page {
+ .item-box {
+ background-color: #FFFFFF;
+ margin: 30rpx;
+ padding: 10rpx;
+ border-radius: 8rpx;
@@ -0,0 +1,183 @@
+ <view class="work-container">
+ <!-- 轮播图 -->
+ <uni-swiper-dot class="uni-swiper-dot-box" :info="data" :current="current" field="content">
+ <swiper class="swiper-box" :current="swiperDotIndex" @change="changeSwiper">
+ <swiper-item v-for="(item, index) in data" :key="index">
+ <view class="swiper-item" @click="clickBannerItem(item)">
+ <image :src="item.image" mode="aspectFill" :draggable="false" />
+ </swiper-item>
+ </swiper>
+ </uni-swiper-dot>
+ <!-- 宫格组件 -->
+ <uni-section title="系统管理" type="line"></uni-section>
+ <view class="grid-body">
+ <uni-grid :column="4" :showBorder="false" @change="changeGrid">
+ <uni-grid-item>
+ <view class="grid-item-box">
+ <uni-icons type="person-filled" size="30"></uni-icons>
+ <text class="text">用户管理</text>
+ </uni-grid-item>
+ <uni-icons type="staff-filled" size="30"></uni-icons>
+ <text class="text">角色管理</text>
+ <uni-icons type="color" size="30"></uni-icons>
+ <text class="text">菜单管理</text>
+ <uni-icons type="settings-filled" size="30"></uni-icons>
+ <text class="text">部门管理</text>
+ <uni-icons type="heart-filled" size="30"></uni-icons>
+ <text class="text">岗位管理</text>
+ <uni-icons type="bars" size="30"></uni-icons>
+ <text class="text">字典管理</text>
+ <uni-icons type="gear-filled" size="30"></uni-icons>
+ <text class="text">参数设置</text>
+ <uni-icons type="chat-filled" size="30"></uni-icons>
+ <text class="text">通知公告</text>
+ <uni-icons type="wallet-filled" size="30"></uni-icons>
+ <text class="text">日志管理</text>
+ </uni-grid>
+ current: 0,
+ swiperDotIndex: 0,
+ data: [{
+ image: '/static/images/banner/banner01.jpg'
+ image: '/static/images/banner/banner02.jpg'
+ image: '/static/images/banner/banner03.jpg'
+ clickBannerItem(item) {
+ console.info(item)
+ changeSwiper(e) {
+ this.current = e.detail.current
+ changeGrid(e) {
+ min-height: 100%;
+ height: auto;
+ view {
+ line-height: inherit;
+ .grid-item-box {
+ padding: 15px 0;
+ .uni-margin-wrap {
+ width: 690rpx;
+ ;
+ .swiper {
+ height: 300rpx;
+ .swiper-box {
+ height: 150px;
+ .swiper-item {
+ color: #fff;
+ line-height: 300rpx;
+ @media screen and (min-width: 500px) {
+ .uni-swiper-dot-box {
+ width: 400px;
+ margin-top: 8px;
+ .image {
@@ -0,0 +1,39 @@
+import { getAccessToken } from '@/utils/auth'
+// 登录页面
+const loginPage = "/pages/login"
+// 页面白名单
+const whiteList = [
+ '/pages/login', '/pages/common/webview/index'
+]
+// 检查地址白名单
+function checkWhite(url) {
+ const path = url.split('?')[0]
+ return whiteList.indexOf(path) !== -1
+// 页面跳转验证拦截器
+let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"]
+list.forEach(item => {
+ uni.addInterceptor(item, {
+ invoke(to) {
+ if (getAccessToken()) {
+ if (to.path === loginPage) {
+ uni.reLaunch({ url: "/" })
+ return true
+ if (checkWhite(to.url)) {
+ uni.reLaunch({ url: loginPage })
+ return false
+ fail(err) {
+ console.log(err)
@@ -0,0 +1,60 @@
+import store from '@/store'
+function authPermission(permission) {
+ const all_permission = "*:*:*"
+ const permissions = store.getters && store.getters.permissions
+ if (permission && permission.length > 0) {
+ return permissions.some(v => {
+ return all_permission === v || v === permission
+function authRole(role) {
+ const super_admin = "admin"
+ const roles = store.getters && store.getters.roles
+ if (role && role.length > 0) {
+ return roles.some(v => {
+ return super_admin === v || v === role
+export default {
+ // 验证用户是否具备某权限
+ hasPermi(permission) {
+ return authPermission(permission)
+ // 验证用户是否含有指定权限,只需包含其中一个
+ hasPermiOr(permissions) {
+ return permissions.some(item => {
+ return authPermission(item)
+ // 验证用户是否含有指定权限,必须全部拥有
+ hasPermiAnd(permissions) {
+ return permissions.every(item => {
+ // 验证用户是否具备某角色
+ hasRole(role) {
+ return authRole(role)
+ // 验证用户是否含有指定角色,只需包含其中一个
+ hasRoleOr(roles) {
+ return roles.some(item => {
+ return authRole(item)
+ // 验证用户是否含有指定角色,必须全部拥有
+ hasRoleAnd(roles) {
+ return roles.every(item => {
@@ -0,0 +1,14 @@
+import tab from './tab'
+import auth from './auth'
+import modal from './modal'
+ install(Vue) {
+ // 页签操作
+ Vue.prototype.$tab = tab
+ // 认证对象
+ Vue.prototype.$auth = auth
+ // 模态框对象
+ Vue.prototype.$modal = modal
@@ -0,0 +1,74 @@
+ // 消息提示
+ msg(content) {
+ uni.showToast({
+ title: content,
+ icon: 'none'
+ // 错误消息
+ msgError(content) {
+ icon: 'error'
+ // 成功消息
+ msgSuccess(content) {
+ icon: 'success'
+ // 隐藏消息
+ hideMsg(content) {
+ uni.hideToast()
+ // 弹出提示
+ alert(content) {
+ uni.showModal({
+ title: '提示',
+ content: content,
+ showCancel: false
+ // 确认窗体
+ confirm(content) {
+ return new Promise((resolve, reject) => {
+ title: '系统提示',
+ cancelText: '取消',
+ confirmText: '确定',
+ success: function(res) {
+ if (res.confirm) {
+ resolve(res.confirm)
+ // 提示信息
+ showToast(option) {
+ if (typeof option === "object") {
+ uni.showToast(option)
+ title: option,
+ icon: "none",
+ duration: 2500
+ // 打开遮罩层
+ loading(content) {
+ // 关闭遮罩层
+ closeLoading() {
@@ -0,0 +1,30 @@
+ // 关闭所有页面,打开到应用内的某个页面
+ reLaunch(url) {
+ return uni.reLaunch({
+ url: url
+ // 跳转到tabBar页面,并关闭其他所有非tabBar页面
+ switchTab(url) {
+ return uni.switchTab({
+ // 关闭当前页面,跳转到应用内的某个页面
+ redirectTo(url) {
+ return uni.redirectTo({
+ // 保留当前页面,跳转到应用内的某个页面
+ navigateTo(url) {
+ return uni.navigateTo({
+ // 关闭当前页面,返回上一页面或多级页面
+ navigateBack() {
+ return uni.navigateBack()
@@ -0,0 +1,90 @@
+@font-face {
+ font-family: "iconfont";
+ src: url('/static/font/iconfont.ttf') format('truetype');
+.iconfont {
+ font-family: "iconfont" !important;
+ display: inline-block;
+ font-style: normal;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+.icon-user:before {
+ content: "\e7ae";
+.icon-password:before {
+ content: "\e8b2";
+.icon-code:before {
+ content: "\e699";
+.icon-setting:before {
+ content: "\e6cc";
+.icon-share:before {
+ content: "\e739";
+.icon-edit:before {
+ content: "\e60c";
+.icon-version:before {
+ content: "\e63f";
+.icon-service:before {
+ content: "\e6ff";
+.icon-friendfill:before {
+ content: "\e726";
+.icon-community:before {
+ content: "\e741";
+.icon-people:before {
+ content: "\e736";
+.icon-dianzan:before {
+ content: "\ec7f";
+.icon-right:before {
+ content: "\e7eb";
+.icon-logout:before {
+ content: "\e61d";
+.icon-help:before {
+ content: "\e616";
+.icon-github:before {
+ content: "\e628";
+.icon-aixin:before {
+ content: "\e601";
+.icon-clean:before {
+ content: "\e607";
+.icon-refresh:before {
+ content: "\e604";
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="renderer" content="webkit">
+ <title><%= htmlWebpackPlugin.options.title %></title>
+ <link rel="shortcut icon" type="image/x-icon" href="<%= BASE_URL %>static/favicon.ico">
+ <script>
+ var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
+ document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+ </script>
+ <link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
+ </head>
+ <body>
+ <noscript>
+ <strong>本站点必须要开启JavaScript才能运行.</strong>
+ </noscript>
+ <div id="app"></div>
+</html>
@@ -0,0 +1,3912 @@
+/*
+ ColorUi for uniApp v2.1.6 | by 文晓港 2019-05-31 10:44:24
+ 仅供学习交流,如作它用所承受的法律责任一概与作者无关
+ *使用ColorUi开发扩展与插件时,请注明基于ColorUi开发
+ (QQ交流群:240787041)
+*/
+/* ==================
+ 初始化
+ ==================== */
+body {
+ background-color: #f1f1f1;
+ font-size: 28upx;
+ color: #333333;
+ font-family: Helvetica Neue, Helvetica, sans-serif;
+view,
+scroll-view,
+swiper,
+button,
+input,
+textarea,
+label,
+navigator,
+image {
+.round {
+ border-radius: 5000upx;
+.radius {
+ border-radius: 6upx;
+ 图片
+ max-width: 100%;
+ z-index: 0;
+image.loading::before {
+ content: "";
+ background-color: #f5f5f5;
+ z-index: -2;
+image.loading::after {
+ content: "\e7f1";
+ font-family: "cuIcon";
+ width: 32upx;
+ height: 32upx;
+ line-height: 32upx;
+ z-index: -1;
+ font-size: 32upx;
+ margin: auto;
+ color: #ccc;
+ -webkit-animation: cuIcon-spin 2s infinite linear;
+ animation: cuIcon-spin 2s infinite linear;
+.response {
+ 开关
+switch,
+checkbox,
+radio {
+switch::after,
+switch::before {
+ content: "\e645";
+ color: #ffffff !important;
+ left: 0upx;
+ font-size: 26upx;
+ line-height: 26px;
+ width: 50%;
+ pointer-events: none;
+ transform: scale(0, 0);
+ transition: all 0.3s ease-in-out 0s;
+ z-index: 9;
+ height: 26px;
+ content: "\e646";
+ transform: scale(1, 1);
+ left: auto;
+switch[checked]::after,
+switch.checked::after {
+switch[checked]::before,
+switch.checked::before {
+/* #ifndef MP-ALIPAY */
+radio::before,
+checkbox::before {
+ margin-top: -8px;
+ right: 5px;
+ line-height: 16px;
+radio .wx-radio-input,
+checkbox .wx-checkbox-input,
+radio .uni-radio-input,
+checkbox .uni-checkbox-input {
+ margin: 0;
+ width: 24px;
+ height: 24px;
+checkbox.round .wx-checkbox-input,
+checkbox.round .uni-checkbox-input {
+ border-radius: 100upx;
+/* #endif */
+switch[checked]::before {
+switch .wx-switch-input,
+switch .uni-switch-input {
+ border: none;
+ padding: 0 24px;
+ width: 48px;
+switch .wx-switch-input:not([class*="bg-"]),
+switch .uni-switch-input:not([class*="bg-"]) {
+ background: #8799a3 !important;
+switch .wx-switch-input::after,
+switch .uni-switch-input::after {
+ width: 26px;
+ top: 0upx;
+ bottom: 0upx;
+ transform: scale(0.9, 0.9);
+ transition: all 0.1s ease-in-out 0s;
+switch .wx-switch-input.wx-switch-input-checked::after,
+switch .uni-switch-input.uni-switch-input-checked::after {
+ left: 22px;
+ box-shadow: none;
+radio-group {
+switch.radius .wx-switch-input::after,
+switch.radius .wx-switch-input,
+switch.radius .wx-switch-input::before,
+switch.radius .uni-switch-input::after,
+switch.radius .uni-switch-input,
+switch.radius .uni-switch-input::before {
+ border-radius: 10upx;
+switch .wx-switch-input::before,
+radio.radio::before,
+checkbox .wx-checkbox-input::before,
+radio .wx-radio-input::before,
+switch .uni-switch-input::before,
+checkbox .uni-checkbox-input::before,
+radio .uni-radio-input::before {
+ display: none;
+radio.radio[checked]::after,
+radio.radio .uni-radio-input-checked::after {
+ background-color: transparent;
+ z-index: 999;
+ border-radius: 200upx;
+ /* #ifndef MP */
+ border: 7px solid #ffffff !important;
+ /* #ifdef MP */
+ border: 8px solid #ffffff !important;
+.switch-sex::after {
+ content: "\e71c";
+.switch-sex::before {
+ content: "\e71a";
+.switch-sex .wx-switch-input,
+.switch-sex .uni-switch-input {
+ background: #e54d42 !important;
+ border-color: #e54d42 !important;
+.switch-sex[checked] .wx-switch-input,
+.switch-sex.checked .uni-switch-input {
+ background: #0081ff !important;
+ border-color: #0081ff !important;
+switch.red[checked] .wx-switch-input.wx-switch-input-checked,
+checkbox.red[checked] .wx-checkbox-input,
+radio.red[checked] .wx-radio-input,
+switch.red.checked .uni-switch-input.uni-switch-input-checked,
+checkbox.red.checked .uni-checkbox-input,
+radio.red.checked .uni-radio-input {
+ background-color: #e54d42 !important;
+switch.orange[checked] .wx-switch-input,
+checkbox.orange[checked] .wx-checkbox-input,
+radio.orange[checked] .wx-radio-input,
+switch.orange.checked .uni-switch-input,
+checkbox.orange.checked .uni-checkbox-input,
+radio.orange.checked .uni-radio-input {
+ background-color: #f37b1d !important;
+ border-color: #f37b1d !important;
+switch.yellow[checked] .wx-switch-input,
+checkbox.yellow[checked] .wx-checkbox-input,
+radio.yellow[checked] .wx-radio-input,
+switch.yellow.checked .uni-switch-input,
+checkbox.yellow.checked .uni-checkbox-input,
+radio.yellow.checked .uni-radio-input {
+ background-color: #fbbd08 !important;
+ border-color: #fbbd08 !important;
+ color: #333333 !important;
+switch.olive[checked] .wx-switch-input,
+checkbox.olive[checked] .wx-checkbox-input,
+radio.olive[checked] .wx-radio-input,
+switch.olive.checked .uni-switch-input,
+checkbox.olive.checked .uni-checkbox-input,
+radio.olive.checked .uni-radio-input {
+ background-color: #8dc63f !important;
+ border-color: #8dc63f !important;
+switch.green[checked] .wx-switch-input,
+switch[checked] .wx-switch-input,
+checkbox.green[checked] .wx-checkbox-input,
+checkbox[checked] .wx-checkbox-input,
+radio.green[checked] .wx-radio-input,
+radio[checked] .wx-radio-input,
+switch.green.checked .uni-switch-input,
+switch.checked .uni-switch-input,
+checkbox.green.checked .uni-checkbox-input,
+checkbox.checked .uni-checkbox-input,
+radio.green.checked .uni-radio-input,
+radio.checked .uni-radio-input {
+ background-color: #39b54a !important;
+ border-color: #39b54a !important;
+ border-color: #39B54A !important;
+switch.cyan[checked] .wx-switch-input,
+checkbox.cyan[checked] .wx-checkbox-input,
+radio.cyan[checked] .wx-radio-input,
+switch.cyan.checked .uni-switch-input,
+checkbox.cyan.checked .uni-checkbox-input,
+radio.cyan.checked .uni-radio-input {
+ background-color: #1cbbb4 !important;
+ border-color: #1cbbb4 !important;
+switch.blue[checked] .wx-switch-input,
+checkbox.blue[checked] .wx-checkbox-input,
+radio.blue[checked] .wx-radio-input,
+switch.blue.checked .uni-switch-input,
+checkbox.blue.checked .uni-checkbox-input,
+radio.blue.checked .uni-radio-input {
+ background-color: #0081ff !important;
+switch.purple[checked] .wx-switch-input,
+checkbox.purple[checked] .wx-checkbox-input,
+radio.purple[checked] .wx-radio-input,
+switch.purple.checked .uni-switch-input,
+checkbox.purple.checked .uni-checkbox-input,
+radio.purple.checked .uni-radio-input {
+ background-color: #6739b6 !important;
+ border-color: #6739b6 !important;
+switch.mauve[checked] .wx-switch-input,
+checkbox.mauve[checked] .wx-checkbox-input,
+radio.mauve[checked] .wx-radio-input,
+switch.mauve.checked .uni-switch-input,
+checkbox.mauve.checked .uni-checkbox-input,
+radio.mauve.checked .uni-radio-input {
+ background-color: #9c26b0 !important;
+ border-color: #9c26b0 !important;
+switch.pink[checked] .wx-switch-input,
+checkbox.pink[checked] .wx-checkbox-input,
+radio.pink[checked] .wx-radio-input,
+switch.pink.checked .uni-switch-input,
+checkbox.pink.checked .uni-checkbox-input,
+radio.pink.checked .uni-radio-input {
+ background-color: #e03997 !important;
+ border-color: #e03997 !important;
+switch.brown[checked] .wx-switch-input,
+checkbox.brown[checked] .wx-checkbox-input,
+radio.brown[checked] .wx-radio-input,
+switch.brown.checked .uni-switch-input,
+checkbox.brown.checked .uni-checkbox-input,
+radio.brown.checked .uni-radio-input {
+ background-color: #a5673f !important;
+ border-color: #a5673f !important;
+switch.grey[checked] .wx-switch-input,
+checkbox.grey[checked] .wx-checkbox-input,
+radio.grey[checked] .wx-radio-input,
+switch.grey.checked .uni-switch-input,
+checkbox.grey.checked .uni-checkbox-input,
+radio.grey.checked .uni-radio-input {
+ background-color: #8799a3 !important;
+ border-color: #8799a3 !important;
+switch.gray[checked] .wx-switch-input,
+checkbox.gray[checked] .wx-checkbox-input,
+radio.gray[checked] .wx-radio-input,
+switch.gray.checked .uni-switch-input,
+checkbox.gray.checked .uni-checkbox-input,
+radio.gray.checked .uni-radio-input {
+ background-color: #f0f0f0 !important;
+ border-color: #f0f0f0 !important;
+switch.black[checked] .wx-switch-input,
+checkbox.black[checked] .wx-checkbox-input,
+radio.black[checked] .wx-radio-input,
+switch.black.checked .uni-switch-input,
+checkbox.black.checked .uni-checkbox-input,
+radio.black.checked .uni-radio-input {
+ background-color: #333333 !important;
+ border-color: #333333 !important;
+switch.white[checked] .wx-switch-input,
+checkbox.white[checked] .wx-checkbox-input,
+radio.white[checked] .wx-radio-input,
+switch.white.checked .uni-switch-input,
+checkbox.white.checked .uni-checkbox-input,
+radio.white.checked .uni-radio-input {
+ background-color: #ffffff !important;
+ border-color: #ffffff !important;
+ 边框
+/* -- 实线 -- */
+.solid,
+.solid-top,
+.solid-right,
+.solid-bottom,
+.solid-left,
+.solids,
+.solids-top,
+.solids-right,
+.solids-bottom,
+.solids-left,
+.dashed,
+.dashed-top,
+.dashed-right,
+.dashed-bottom,
+.dashed-left {
+.solid::after,
+.solid-top::after,
+.solid-right::after,
+.solid-bottom::after,
+.solid-left::after,
+.solids::after,
+.solids-top::after,
+.solids-right::after,
+.solids-bottom::after,
+.solids-left::after,
+.dashed::after,
+.dashed-top::after,
+.dashed-right::after,
+.dashed-bottom::after,
+.dashed-left::after {
+ content: " ";
+ width: 200%;
+ height: 200%;
+ border-radius: inherit;
+ transform: scale(0.5);
+ transform-origin: 0 0;
+.solid::after {
+ border: 1upx solid rgba(0, 0, 0, 0.1);
+.solid-top::after {
+ border-top: 1upx solid rgba(0, 0, 0, 0.1);
+.solid-right::after {
+ border-right: 1upx solid rgba(0, 0, 0, 0.1);
+.solid-bottom::after {
+ border-bottom: 1upx solid rgba(0, 0, 0, 0.1);
+.solid-left::after {
+ border-left: 1upx solid rgba(0, 0, 0, 0.1);
+.solids::after {
+ border: 8upx solid #eee;
+.solids-top::after {
+ border-top: 8upx solid #eee;
+.solids-right::after {
+ border-right: 8upx solid #eee;
+.solids-bottom::after {
+ border-bottom: 8upx solid #eee;
+.solids-left::after {
+ border-left: 8upx solid #eee;
+/* -- 虚线 -- */
+.dashed::after {
+ border: 1upx dashed #ddd;
+.dashed-top::after {
+ border-top: 1upx dashed #ddd;
+.dashed-right::after {
+ border-right: 1upx dashed #ddd;
+.dashed-bottom::after {
+ border-bottom: 1upx dashed #ddd;
+ border-left: 1upx dashed #ddd;
+/* -- 阴影 -- */
+.shadow[class*='white'] {
+ --ShadowSize: 0 1upx 6upx;
+.shadow-lg {
+ --ShadowSize: 0upx 40upx 100upx 0upx;
+.shadow-warp {
+ box-shadow: 0 0 10upx rgba(0, 0, 0, 0.1);
+.shadow-warp:before,
+.shadow-warp:after {
+ top: 20upx;
+ bottom: 30upx;
+ left: 20upx;
+ box-shadow: 0 30upx 20upx rgba(0, 0, 0, 0.2);
+ transform: rotate(-3deg);
+ right: 20upx;
+ transform: rotate(3deg);
+.shadow-blur {
+.shadow-blur::before {
+ background: inherit;
+ filter: blur(10upx);
+ top: 10upx;
+ left: 10upx;
+ opacity: 0.4;
+ 按钮
+.cu-btn {
+ border: 0upx;
+ display: inline-flex;
+ padding: 0 30upx;
+ height: 64upx;
+ line-height: 1;
+ text-decoration: none;
+ margin-left: initial;
+ transform: translate(0upx, 0upx);
+ margin-right: initial;
+.cu-btn::after {
+.cu-btn:not([class*="bg-"]) {
+ background-color: #f0f0f0;
+.cu-btn[class*="line"] {
+.cu-btn[class*="line"]::after {
+ border: 1upx solid currentColor;
+ border-radius: 12upx;
+.cu-btn.round[class*="line"]::after {
+ border-radius: 1000upx;
+.cu-btn[class*="lines"]::after {
+ border: 6upx solid currentColor;
+.cu-btn[class*="bg-"]::after {
+.cu-btn.sm {
+ padding: 0 20upx;
+ font-size: 20upx;
+ height: 48upx;
+.cu-btn.lg {
+ padding: 0 40upx;
+ height: 80upx;
+.cu-btn.cuIcon.sm {
+ width: 48upx;
+.cu-btn.cuIcon {
+ width: 64upx;
+ border-radius: 500upx;
+ padding: 0;
+button.cuIcon.lg {
+ width: 80upx;
+.cu-btn.shadow-blur::before {
+ top: 4upx;
+ left: 4upx;
+ filter: blur(6upx);
+ opacity: 0.6;
+.cu-btn.button-hover {
+ transform: translate(1upx, 1upx);
+.block {
+.cu-btn.block {
+.cu-btn[disabled] {
+ color: #ffffff;
+ 徽章
+.cu-tag {
+ font-size: 24upx;
+ vertical-align: middle;
+ padding: 0upx 16upx;
+ white-space: nowrap;
+.cu-tag:not([class*="bg"]):not([class*="line"]) {
+.cu-tag[class*="line-"]::after {
+.cu-tag.radius[class*="line"]::after {
+.cu-tag.round[class*="line"]::after {
+ border-radius: 0;
+.cu-tag+.cu-tag {
+ margin-left: 10upx;
+.cu-tag.sm {
+ padding: 0upx 12upx;
+.cu-capsule {
+.cu-capsule+.cu-capsule {
+.cu-capsule .cu-tag {
+.cu-capsule .cu-tag[class*="line-"]:last-child::after {
+ border-left: 0upx solid transparent;
+.cu-capsule .cu-tag[class*="line-"]:first-child::after {
+ border-right: 0upx solid transparent;
+.cu-capsule.radius .cu-tag:first-child {
+ border-top-left-radius: 6upx;
+ border-bottom-left-radius: 6upx;
+.cu-capsule.radius .cu-tag:last-child::after,
+.cu-capsule.radius .cu-tag[class*="line-"] {
+ border-top-right-radius: 12upx;
+ border-bottom-right-radius: 12upx;
+.cu-capsule.round .cu-tag:first-child {
+ border-top-left-radius: 200upx;
+ border-bottom-left-radius: 200upx;
+ text-indent: 4upx;
+.cu-capsule.round .cu-tag:last-child::after,
+.cu-capsule.round .cu-tag:last-child {
+ border-top-right-radius: 200upx;
+ border-bottom-right-radius: 200upx;
+ text-indent: -4upx;
+.cu-tag.badge {
+ top: -10upx;
+ right: -10upx;
+ padding: 0upx 10upx;
+ height: 28upx;
+.cu-tag.badge:not([class*="bg-"]) {
+ background-color: #dd514c;
+.cu-tag:empty:not([class*="cuIcon-"]) {
+ padding: 0upx;
+ width: 16upx;
+ height: 16upx;
+ top: -4upx;
+ right: -4upx;
+.cu-tag[class*="cuIcon-"] {
+ 头像
+.cu-avatar {
+ font-variant: small-caps;
+ background-color: #ccc;
+ background-size: cover;
+ background-position: center;
+ font-size: 1.5em;
+.cu-avatar.sm {
+ font-size: 1em;
+.cu-avatar.lg {
+ width: 96upx;
+ height: 96upx;
+ font-size: 2em;
+.cu-avatar.xl {
+ width: 128upx;
+ height: 128upx;
+ font-size: 2.5em;
+.cu-avatar .avatar-text {
+ font-size: 0.4em;
+.cu-avatar-group {
+ direction: rtl;
+ unicode-bidi: bidi-override;
+ padding: 0 10upx 0 40upx;
+.cu-avatar-group .cu-avatar {
+ margin-left: -30upx;
+ border: 4upx solid #f1f1f1;
+.cu-avatar-group .cu-avatar.sm {
+ margin-left: -20upx;
+ border: 1upx solid #f1f1f1;
+ 进度条
+.cu-progress {
+ background-color: #ebeef5;
+.cu-progress+view,
+.cu-progress+text {
+.cu-progress.xs {
+ height: 10upx;
+.cu-progress.sm {
+ height: 20upx;
+.cu-progress view {
+ width: 0;
+ justify-items: flex-end;
+ transition: width 0.6s ease;
+.cu-progress text {
+ text-indent: 10upx;
+.cu-progress.text-progress {
+ padding-right: 60upx;
+.cu-progress.striped view {
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-size: 72upx 72upx;
+.cu-progress.active view {
+ animation: progress-stripes 2s linear infinite;
+@keyframes progress-stripes {
+ from {
+ background-position: 72upx 0;
+ to {
+ background-position: 0 0;
+ 加载
+.cu-load {
+ line-height: 3em;
+.cu-load::before {
+ margin-right: 6upx;
+.cu-load.loading::before {
+ content: "\e67a";
+.cu-load.loading::after {
+ content: "加载中...";
+.cu-load.over::before {
+ content: "\e64a";
+.cu-load.over::after {
+ content: "没有更多了";
+.cu-load.erro::before {
+ content: "\e658";
+.cu-load.erro::after {
+ content: "加载失败";
+.cu-load.load-cuIcon::before {
+.cu-load.load-cuIcon::after {
+.cu-load.load-cuIcon.over {
+.cu-load.load-modal {
+ position: fixed;
+ bottom: 140upx;
+ width: 260upx;
+ height: 260upx;
+ box-shadow: 0 0 0upx 2000upx rgba(0, 0, 0, 0.5);
+ z-index: 9999;
+ line-height: 2.4em;
+.cu-load.load-modal [class*="cuIcon-"] {
+ font-size: 60upx;
+.cu-load.load-modal image {
+ width: 70upx;
+ height: 70upx;
+.cu-load.load-modal::after {
+ border-radius: 50%;
+ width: 200upx;
+ height: 200upx;
+ font-size: 10px;
+ border-top: 6upx solid rgba(0, 0, 0, 0.05);
+ border-right: 6upx solid rgba(0, 0, 0, 0.05);
+ border-bottom: 6upx solid rgba(0, 0, 0, 0.05);
+ border-left: 6upx solid #f37b1d;
+ animation: cuIcon-spin 1s infinite linear;
+.load-progress {
+ z-index: 2000;
+.load-progress.hide {
+.load-progress .load-progress-bar {
+ height: 4upx;
+ transition: all 200ms ease 0s;
+.load-progress .load-progress-spinner {
+ right: 10upx;
+.load-progress .load-progress-spinner::after {
+ width: 24upx;
+ height: 24upx;
+ -webkit-box-sizing: border-box;
+ border: solid 4upx transparent;
+ border-top-color: inherit;
+ border-left-color: inherit;
+ -webkit-animation: load-progress-spinner 0.4s linear infinite;
+ animation: load-progress-spinner 0.4s linear infinite;
+@-webkit-keyframes load-progress-spinner {
+ 0% {
+ -webkit-transform: rotate(0);
+ transform: rotate(0);
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+@keyframes load-progress-spinner {
+ 列表
+.grayscale {
+ filter: grayscale(1);
+.cu-list+.cu-list {
+ margin-top: 30upx
+.cu-list>.cu-item {
+ transition: all .6s ease-in-out 0s;
+ transform: translateX(0upx)
+.cu-list>.cu-item.move-cur {
+ transform: translateX(-260upx)
+.cu-list>.cu-item .move {
+ transform: translateX(100%)
+.cu-list>.cu-item .move view {
+ align-items: center
+.cu-list.menu-avatar {
+.cu-list.menu-avatar>.cu-item {
+ padding-right: 10upx;
+ height: 140upx;
+ justify-content: flex-end;
+.cu-list.menu-avatar>.cu-item>.cu-avatar {
+ left: 30upx
+.cu-list.menu-avatar>.cu-item .flex .text-cut {
+ max-width: 510upx
+.cu-list.menu-avatar>.cu-item .content {
+ left: 146upx;
+ width: calc(100% - 96upx - 60upx - 120upx - 20upx);
+ line-height: 1.6em;
+.cu-list.menu-avatar>.cu-item .content.flex-sub {
+ width: calc(100% - 96upx - 60upx - 20upx);
+.cu-list.menu-avatar>.cu-item .content>view:first-child {
+ font-size: 30upx;
+.cu-list.menu-avatar>.cu-item .content .cu-tag.sm {
+ font-size: 16upx;
+ line-height: 32upx
+.cu-list.menu-avatar>.cu-item .action {
+ width: 100upx;
+ text-align: center
+.cu-list.menu-avatar>.cu-item .action view+view {
+ margin-top: 10upx
+.cu-list.menu-avatar.comment>.cu-item .content {
+ width: auto;
+.cu-list.menu-avatar.comment>.cu-item {
+ padding: 30upx 30upx 30upx 120upx;
+ height: auto
+.cu-list.menu-avatar.comment .cu-avatar {
+ align-self: flex-start
+.cu-list.menu>.cu-item {
+ min-height: 100upx;
+ justify-content: space-between;
+.cu-list.menu>.cu-item:last-child:after {
+ border: none
+.cu-list.menu-avatar>.cu-item:after,
+.cu-list.menu>.cu-item:after {
+ border-bottom: 1upx solid #ddd;
+ transform: scale(.5);
+ pointer-events: none
+.cu-list.menu>.cu-item.grayscale {
+ background-color: #f5f5f5
+.cu-list.menu>.cu-item.cur {
+ background-color: #fcf7e9
+.cu-list.menu>.cu-item.arrow {
+ padding-right: 90upx
+.cu-list.menu>.cu-item.arrow:before {
+ right: 30upx;
+ width: 30upx;
+ height: 30upx;
+ color: #8799a3;
+ content: "\e6a3";
+ font-size: 34upx;
+ font-family: cuIcon;
+ line-height: 30upx
+.cu-list.menu>.cu-item button.content {
+ justify-content: flex-start
+.cu-list.menu>.cu-item button.content:after {
+ display: none
+.cu-list.menu>.cu-item .cu-avatar-group .cu-avatar {
+ border-color: #ffffff
+.cu-list.menu>.cu-item .content>view:first-child {
+.cu-list.menu>.cu-item .content>text[class*=cuIcon] {
+ margin-right: 10upx;
+ width: 1.6em;
+.cu-list.menu>.cu-item .content>image {
+ height: 1.6em;
+ vertical-align: middle
+.cu-list.menu>.cu-item .content {
+ flex: 1
+.cu-list.menu>.cu-item .content .cu-tag.sm {
+.cu-list.menu>.cu-item .action .cu-tag:empty {
+ right: 10upx
+.cu-list.menu {
+ overflow: hidden
+.cu-list.menu.sm-border>.cu-item:after {
+ left: 30upx;
+ width: calc(200% - 120upx)
+.cu-list.grid>.cu-item {
+ padding: 20upx 0 30upx;
+ transition-duration: 0s;
+ flex-direction: column
+.cu-list.grid>.cu-item:after {
+ border-right: 1px solid rgba(0, 0, 0, .1);
+ border-bottom: 1px solid rgba(0, 0, 0, .1);
+.cu-list.grid>.cu-item text {
+ margin-top: 10upx;
+ color: #888;
+ line-height: 40upx
+.cu-list.grid>.cu-item [class*=cuIcon] {
+ margin-top: 20upx;
+ font-size: 48upx
+.cu-list.grid>.cu-item .cu-tag {
+ right: auto;
+ margin-left: 20upx
+.cu-list.grid {
+.cu-list.grid.no-border>.cu-item {
+ padding-top: 10upx;
+ padding-bottom: 20upx
+.cu-list.grid.no-border>.cu-item:after {
+.cu-list.grid.no-border {
+ padding: 20upx 10upx
+.cu-list.grid.col-3>.cu-item:nth-child(3n):after,
+.cu-list.grid.col-4>.cu-item:nth-child(4n):after,
+.cu-list.grid.col-5>.cu-item:nth-child(5n):after {
+ border-right-width: 0
+.cu-list.card-menu {
+ margin-right: 30upx;
+ margin-left: 30upx;
+ border-radius: 20upx
+ 操作条
+.cu-bar {
+.cu-bar .action {
+.cu-bar .action.border-title {
+.cu-bar .action.border-title text[class*="bg-"]:last-child {
+ bottom: -0.5rem;
+ min-width: 2rem;
+ height: 6upx;
+.cu-bar .action.sub-title {
+ top: -0.2rem;
+.cu-bar .action.sub-title text {
+.cu-bar .action.sub-title text[class*="bg-"]:last-child {
+ bottom: -0.2rem;
+ height: 0.6rem;
+ left: 0.6rem;
+ opacity: 0.3;
+.cu-bar .action.sub-title text[class*="text-"]:last-child {
+ bottom: -0.7rem;
+ left: 0.5rem;
+ opacity: 0.2;
+ text-align: right;
+ font-weight: 900;
+ font-size: 36upx;
+.cu-bar.justify-center .action.border-title text:last-child,
+.cu-bar.justify-center .action.sub-title text:last-child {
+.cu-bar .action:first-child {
+.cu-bar .action text.text-cut {
+.cu-bar .cu-avatar:first-child {
+ margin-left: 20upx;
+.cu-bar .action:first-child>text[class*="cuIcon-"] {
+ margin-left: -0.3em;
+ margin-right: 0.3em;
+.cu-bar .action:last-child {
+.cu-bar .action>text[class*="cuIcon-"],
+.cu-bar .action>view[class*="cuIcon-"] {
+.cu-bar .action>text[class*="cuIcon-"]+text[class*="cuIcon-"] {
+ margin-left: 0.5em;
+.cu-bar .content {
+ width: calc(100% - 340upx);
+ height: 60upx;
+ line-height: 60upx;
+ cursor: none;
+ text-overflow: ellipsis;
+.cu-bar.ios .content {
+ bottom: 7px;
+ height: 30px;
+.cu-bar.btn-group {
+.cu-bar.btn-group button {
+ padding: 20upx 32upx;
+ margin: 0 20upx;
+ max-width: 50%;
+.cu-bar .search-form {
+ line-height: 64upx;
+ margin: 0 30upx;
+.cu-bar .search-form+.action {
+.cu-bar .search-form input {
+ padding-right: 30upx;
+.cu-bar .search-form [class*="cuIcon-"] {
+ margin: 0 0.5em 0 0.8em;
+.cu-bar .search-form [class*="cuIcon-"]::before {
+.cu-bar.fixed,
+.nav.fixed {
+ z-index: 1024;
+ box-shadow: 0 1upx 6upx rgba(0, 0, 0, 0.1);
+.cu-bar.foot {
+ box-shadow: 0 -1upx 6upx rgba(0, 0, 0, 0.1);
+.cu-bar.tabbar {
+ height: calc(100upx + env(safe-area-inset-bottom) / 2);
+ padding-bottom: calc(env(safe-area-inset-bottom) / 2);
+.cu-tabbar-height {
+.cu-bar.tabbar.shadow {
+.cu-bar.tabbar .action {
+ font-size: 22upx;
+ background-color: inherit;
+ overflow: initial;
+.cu-bar.tabbar.shop .action {
+ width: 140upx;
+ flex: initial;
+.cu-bar.tabbar .action.add-action {
+ padding-top: 50upx;
+.cu-bar.tabbar .action.add-action [class*="cuIcon-"] {
+ line-height: 70upx;
+ font-size: 50upx;
+ top: -35upx;
+.cu-bar.tabbar .action.add-action::after {
+ height: 100upx;
+ top: -50upx;
+ box-shadow: 0 -3upx 8upx rgba(0, 0, 0, 0.08);
+ border-radius: 50upx;
+.cu-bar.tabbar .action.add-action::before {
+.cu-bar.tabbar .btn-group {
+ padding: 0 10upx;
+.cu-bar.tabbar button.action::after {
+ border: 0;
+.cu-bar.tabbar .action [class*="cuIcon-"] {
+ margin: 0 auto 10upx;
+ font-size: 40upx;
+.cu-bar.tabbar .action .cuIcon-cu-image {
+.cu-bar.tabbar .action .cuIcon-cu-image image {
+ width: 50upx;
+ height: 50upx;
+.cu-bar.tabbar .submit {
+ flex: 2;
+ align-self: stretch;
+.cu-bar.tabbar .submit:last-child {
+ flex: 2.6;
+.cu-bar.tabbar .submit+.submit {
+.cu-bar.tabbar.border .action::before {
+.cu-bar.tabbar.border .action:last-child:before {
+.cu-bar.input {
+ padding-right: 20upx;
+.cu-bar.input input {
+ min-height: 64upx;
+.cu-bar.input .action {
+.cu-bar.input .action [class*="cuIcon-"] {
+ font-size: 48upx;
+.cu-bar.input input+.action {
+ margin-right: 20upx;
+ margin-left: 0upx;
+.cu-bar.input .action:first-child [class*="cuIcon-"] {
+.cu-custom {
+.cu-custom .cu-bar .content {
+ width: calc(100% - 440upx);
+/* #ifdef MP-ALIPAY */
+.cu-custom .cu-bar .action .cuIcon-back {
+ opacity: 0;
+.cu-custom .cu-bar .content image {
+ width: 240upx;
+.cu-custom .cu-bar {
+ min-height: 0px;
+ /* #ifdef MP-WEIXIN */
+ padding-right: 220upx;
+ /* #ifdef MP-ALIPAY */
+ padding-right: 150upx;
+ box-shadow: 0upx 0upx 0upx;
+.cu-custom .cu-bar .border-custom {
+ background: rgba(0, 0, 0, 0.15);
+.cu-custom .cu-bar .border-custom::after {
+ border: 1upx solid #ffffff;
+ opacity: 0.5;
+.cu-custom .cu-bar .border-custom::before {
+ width: 1upx;
+ height: 110%;
+ top: 22.5%;
+.cu-custom .cu-bar .border-custom text {
+ margin: auto !important;
+ 导航栏
+.nav {
+::-webkit-scrollbar {
+.nav .cu-item {
+ height: 90upx;
+ line-height: 90upx;
+ margin: 0 10upx;
+.nav .cu-item.cur {
+ border-bottom: 4upx solid;
+ 时间轴
+.cu-timeline {
+.cu-timeline .cu-time {
+ width: 120upx;
+ padding: 20upx 0;
+.cu-timeline>.cu-item {
+.cu-timeline>.cu-item:not([class*="text-"]) {
+.cu-timeline>.cu-item::after {
+ background-color: #ddd;
+ left: 60upx;
+ z-index: 8;
+.cu-timeline>.cu-item::before {
+ top: 36upx;
+ line-height: 50upx;
+ left: 36upx;
+.cu-timeline>.cu-item:not([class*="cuIcon-"])::before {
+ content: "\e763";
+.cu-timeline>.cu-item[class*="cuIcon-"]::before {
+.cu-timeline>.cu-item>.content {
+ padding: 30upx;
+ line-height: 1.6;
+.cu-timeline>.cu-item>.content:not([class*="bg-"]) {
+.cu-timeline>.cu-item>.content+.content {
+ 聊天
+.cu-chat {
+.cu-chat .cu-item {
+ padding: 30upx 30upx 70upx;
+.cu-chat .cu-item>.cu-avatar {
+.cu-chat .cu-item>.main {
+ max-width: calc(100% - 260upx);
+ margin: 0 40upx;
+.cu-chat .cu-item>image {
+ height: 320upx;
+.cu-chat .cu-item>.main .content {
+ padding: 20upx;
+ min-height: 80upx;
+ line-height: 40upx;
+.cu-chat .cu-item>.main .content:not([class*="bg-"]) {
+.cu-chat .cu-item .date {
+ width: calc(100% - 320upx);
+ bottom: 20upx;
+ left: 160upx;
+.cu-chat .cu-item .action {
+.cu-chat .cu-item>.main .content::after {
+ top: 27upx;
+ transform: rotate(45deg);
+ z-index: 100;
+ left: -12upx;
+ right: initial;
+.cu-chat .cu-item.self>.main .content::after {
+ right: -12upx;
+.cu-chat .cu-item>.main .content::before {
+ top: 30upx;
+ filter: blur(5upx);
+.cu-chat .cu-item>.main .content:not([class*="bg-"])::before {
+ background-color: #333333;
+.cu-chat .cu-item.self>.main .content::before {
+.cu-chat .cu-item.self {
+.cu-chat .cu-info {
+ margin: 20upx auto;
+ padding: 8upx 12upx;
+ background-color: rgba(0, 0, 0, 0.2);
+ max-width: 400upx;
+ line-height: 1.4;
+ 卡片
+.cu-card {
+.cu-card>.cu-item {
+ margin: 30upx;
+.cu-card>.cu-item.shadow-blur {
+.cu-card.no-card>.cu-item {
+ margin: 0upx;
+ border-radius: 0upx;
+.cu-card .grid.grid-square {
+ margin-bottom: -20upx;
+.cu-card.case .image {
+.cu-card.case .image image {
+.cu-card.case .image .cu-tag {
+.cu-card.case .image .cu-bar {
+ padding: 0upx 30upx;
+.cu-card.case.no-card .image {
+ margin: 30upx 30upx 0;
+.cu-card.dynamic {
+.cu-card.dynamic>.cu-item {
+.cu-card.dynamic>.cu-item>.text-content {
+ padding: 0 30upx 0;
+ max-height: 6.4em;
+ margin-bottom: 20upx;
+.cu-card.dynamic>.cu-item .square-img {
+.cu-card.dynamic>.cu-item .only-img {
+/* card.dynamic>.cu-item .comment {
+ margin: 0 30upx 30upx;
+} */
+.cu-card.article {
+.cu-card.article>.cu-item {
+ padding-bottom: 30upx;
+.cu-card.article>.cu-item .title {
+ line-height: 100upx;
+.cu-card.article>.cu-item .content {
+.cu-card.article>.cu-item .content>image {
+ height: 6.4em;
+.cu-card.article>.cu-item .content .desc {
+.cu-card.article>.cu-item .content .text-content {
+ height: 4.8em;
+ 表单
+.cu-form-group {
+ padding: 1upx 30upx;
+.cu-form-group+.cu-form-group {
+ border-top: 1upx solid #eee;
+.cu-form-group .title {
+ text-align: justify;
+.cu-form-group input {
+ color: #555;
+.cu-form-group>text[class*="cuIcon-"] {
+.cu-form-group textarea {
+ margin: 32upx 0 30upx;
+ height: 4.6em;
+ line-height: 1.2em;
+.cu-form-group.align-start .title {
+ height: 1em;
+ margin-top: 32upx;
+ line-height: 1em;
+.cu-form-group picker {
+ padding-right: 40upx;
+.cu-form-group picker .picker {
+.cu-form-group picker::after {
+ width: 60upx;
+ right: -20upx;
+.cu-form-group textarea[disabled],
+.cu-form-group textarea[disabled] .placeholder {
+ color: transparent;
+ 模态窗口
+.cu-modal {
+ z-index: 1110;
+ outline: 0;
+ -ms-transform: scale(1.185);
+ transform: scale(1.185);
+ backface-visibility: hidden;
+ perspective: 2000upx;
+.cu-modal::before {
+ content: "\200B";
+.cu-modal.show {
+ transition-duration: 0.3s;
+ -ms-transform: scale(1);
+ transform: scale(1);
+ overflow-x: hidden;
+ overflow-y: auto;
+ pointer-events: auto;
+.cu-dialog {
+ width: 680upx;
+.cu-modal.bottom-modal::before {
+ vertical-align: bottom;
+.cu-modal.bottom-modal .cu-dialog {
+.cu-modal.bottom-modal {
+ margin-bottom: -1000upx;
+.cu-modal.bottom-modal.show {
+ margin-bottom: 0;
+.cu-modal.drawer-modal {
+.cu-modal.drawer-modal .cu-dialog {
+ min-width: 200upx;
+ margin: initial;
+.cu-modal.drawer-modal.justify-start .cu-dialog {
+ transform: translateX(-100%);
+.cu-modal.drawer-modal.justify-end .cu-dialog {
+ transform: translateX(100%);
+.cu-modal.drawer-modal.show .cu-dialog {
+ transform: translateX(0%);
+.cu-modal .cu-dialog>.cu-bar:first-child .action{
+ min-width: 100rpx;
+ margin-right: 0;
+ min-height: 100rpx;
+ 轮播
+swiper .a-swiper-dot {
+ background: rgba(0, 0, 0, .3);
+swiper[class*="-dot"] .wx-swiper-dots,
+swiper[class*="-dot"] .a-swiper-dots,
+swiper[class*="-dot"] .uni-swiper-dots {
+swiper.square-dot .wx-swiper-dot,
+swiper.square-dot .a-swiper-dot,
+swiper.square-dot .uni-swiper-dot {
+ width: 10upx;
+ border-radius: 20upx;
+ margin: 0 8upx !important;
+swiper.square-dot .wx-swiper-dot.wx-swiper-dot-active,
+swiper.square-dot .a-swiper-dot.a-swiper-dot-active,
+swiper.square-dot .uni-swiper-dot.uni-swiper-dot-active {
+swiper.round-dot .wx-swiper-dot,
+swiper.round-dot .a-swiper-dot,
+swiper.round-dot .uni-swiper-dot {
+ margin: 4upx 8upx !important;
+swiper.round-dot .wx-swiper-dot.wx-swiper-dot-active::after,
+swiper.round-dot .a-swiper-dot.a-swiper-dot-active::after,
+swiper.round-dot .uni-swiper-dot.uni-swiper-dot-active::after {
+swiper.round-dot .wx-swiper-dot.wx-swiper-dot-active,
+swiper.round-dot .a-swiper-dot.a-swiper-dot-active,
+swiper.round-dot .uni-swiper-dot.uni-swiper-dot-active {
+ width: 18upx;
+ height: 18upx;
+.screen-swiper {
+ min-height: 375upx;
+.screen-swiper image,
+.screen-swiper video,
+.swiper-item image,
+.swiper-item video {
+.card-swiper {
+ height: 420upx !important;
+.card-swiper swiper-item {
+ width: 610upx !important;
+ left: 70upx;
+ padding: 40upx 0upx 70upx;
+.card-swiper swiper-item .swiper-item {
+ transform: scale(0.9);
+ transition: all 0.2s ease-in 0s;
+.card-swiper swiper-item.cur .swiper-item {
+ transform: none;
+.tower-swiper {
+ height: 420upx;
+ max-width: 750upx;
+.tower-swiper .tower-item {
+ width: 300upx;
+ height: 380upx;
+.tower-swiper .tower-item.none {
+.tower-swiper .tower-item .swiper-item {
+ 步骤条
+.cu-steps {
+scroll-view.cu-steps {
+scroll-view.cu-steps .cu-item {
+.cu-steps .cu-item {
+ min-width: 100upx;
+.cu-steps .cu-item:not([class*="text-"]) {
+.cu-steps .cu-item [class*="cuIcon-"],
+.cu-steps .cu-item .num {
+ line-height: 80upx;
+.cu-steps .cu-item::before,
+.cu-steps .cu-item::after,
+.cu-steps.steps-arrow .cu-item::before,
+.cu-steps.steps-arrow .cu-item::after {
+ height: 0px;
+ width: calc(100% - 80upx);
+ border-bottom: 1px solid #ccc;
+ left: calc(0px - (100% - 80upx) / 2);
+ top: 40upx;
+ font-family: 'cuIcon';
+ border-bottom-width: 0px;
+ line-height: 30upx;
+.cu-steps.steps-bottom .cu-item::before,
+.cu-steps.steps-bottom .cu-item::after {
+ bottom: 40upx;
+ top: initial;
+.cu-steps .cu-item::after {
+ border-bottom: 1px solid currentColor;
+ width: 0px;
+.cu-steps .cu-item[class*="text-"]::after {
+ color: currentColor;
+.cu-steps .cu-item:first-child::before,
+.cu-steps .cu-item:first-child::after {
+ width: 40upx;
+ height: 40upx;
+ border: 1px solid currentColor;
+.cu-steps .cu-item[class*="text-"] .num {
+ background-color: currentColor;
+.cu-steps .cu-item .num::before,
+.cu-steps .cu-item .num::after {
+ content: attr(data-index);
+ transform: translateY(0upx);
+.cu-steps .cu-item[class*="text-"] .num::before {
+ transform: translateY(-40upx);
+ transform: translateY(40upx);
+.cu-steps .cu-item[class*="text-"] .num::after {
+.cu-steps .cu-item[class*="text-"] .num.err::after {
+ 布局
+/* -- flex弹性布局 -- */
+.flex {
+.basis-xs {
+ flex-basis: 20%;
+.basis-sm {
+ flex-basis: 40%;
+.basis-df {
+ flex-basis: 50%;
+.basis-lg {
+ flex-basis: 60%;
+.basis-xl {
+ flex-basis: 80%;
+.flex-sub {
+.flex-twice {
+.flex-treble {
+ flex: 3;
+.flex-direction {
+.flex-wrap {
+ flex-wrap: wrap;
+.align-start {
+ align-items: flex-start;
+.align-end {
+ align-items: flex-end;
+.align-center {
+.align-stretch {
+ align-items: stretch;
+.self-start {
+ align-self: flex-start;
+.self-center {
+ align-self: flex-center;
+.self-end {
+ align-self: flex-end;
+.self-stretch {
+.justify-start {
+ justify-content: flex-start;
+.justify-end {
+.justify-center {
+.justify-between {
+.justify-around {
+/* grid布局 */
+.grid {
+.grid.grid-square {
+.grid.grid-square .cu-tag {
+ padding: 6upx 12upx;
+ background-color: rgba(0, 0, 0, 0.5);
+.grid.grid-square>view>text[class*="cuIcon-"] {
+ font-size: 52upx;
+.grid.grid-square>view {
+.grid.grid-square>view.bg-img image {
+.grid.col-1.grid-square>view {
+ padding-bottom: 100%;
+ height: 0;
+.grid.col-2.grid-square>view {
+ padding-bottom: calc((100% - 20upx)/2);
+ width: calc((100% - 20upx)/2);
+.grid.col-3.grid-square>view {
+ padding-bottom: calc((100% - 40upx)/3);
+ width: calc((100% - 40upx)/3);
+.grid.col-4.grid-square>view {
+ padding-bottom: calc((100% - 60upx)/4);
+ width: calc((100% - 60upx)/4);
+.grid.col-5.grid-square>view {
+ padding-bottom: calc((100% - 80upx)/5);
+ width: calc((100% - 80upx)/5);
+.grid.col-2.grid-square>view:nth-child(2n),
+.grid.col-3.grid-square>view:nth-child(3n),
+.grid.col-4.grid-square>view:nth-child(4n),
+.grid.col-5.grid-square>view:nth-child(5n) {
+.grid.col-1>view {
+.grid.col-2>view {
+.grid.col-3>view {
+ width: 33.33%;
+.grid.col-4>view {
+ width: 25%;
+.grid.col-5>view {
+ width: 20%;
+/* -- 内外边距 -- */
+.margin-0 {
+.margin-xs {
+ margin: 10upx;
+.margin-sm {
+ margin: 20upx;
+.margin {
+.margin-lg {
+ margin: 40upx;
+.margin-xl {
+ margin: 50upx;
+.margin-top-xs {
+.margin-top-sm {
+.margin-top {
+ margin-top: 30upx;
+.margin-top-lg {
+ margin-top: 40upx;
+.margin-top-xl {
+ margin-top: 50upx;
+.margin-right-xs {
+.margin-right-sm {
+.margin-right {
+.margin-right-lg {
+ margin-right: 40upx;
+.margin-right-xl {
+ margin-right: 50upx;
+.margin-bottom-xs {
+ margin-bottom: 10upx;
+.margin-bottom-sm {
+.margin-bottom {
+ margin-bottom: 30upx;
+.margin-bottom-lg {
+ margin-bottom: 40upx;
+.margin-bottom-xl {
+ margin-bottom: 50upx;
+.margin-left-xs {
+.margin-left-sm {
+.margin-left {
+.margin-left-lg {
+ margin-left: 40upx;
+.margin-left-xl {
+ margin-left: 50upx;
+.margin-lr-xs {
+.margin-lr-sm {
+.margin-lr {
+.margin-lr-lg {
+.margin-lr-xl {
+.margin-tb-xs {
+.margin-tb-sm {
+.margin-tb {
+.margin-tb-lg {
+.margin-tb-xl {
+.padding-0 {
+.padding-xs {
+ padding: 10upx;
+.padding-sm {
+.padding {
+.padding-lg {
+ padding: 40upx;
+.padding-xl {
+ padding: 50upx;
+.padding-top-xs {
+.padding-top-sm {
+ padding-top: 20upx;
+.padding-top {
+ padding-top: 30upx;
+.padding-top-lg {
+ padding-top: 40upx;
+.padding-top-xl {
+.padding-right-xs {
+.padding-right-sm {
+.padding-right {
+.padding-right-lg {
+.padding-right-xl {
+ padding-right: 50upx;
+.padding-bottom-xs {
+ padding-bottom: 10upx;
+.padding-bottom-sm {
+ padding-bottom: 20upx;
+.padding-bottom {
+.padding-bottom-lg {
+ padding-bottom: 40upx;
+.padding-bottom-xl {
+ padding-bottom: 50upx;
+.padding-left-xs {
+ padding-left: 10upx;
+.padding-left-sm {
+ padding-left: 20upx;
+.padding-left {
+ padding-left: 30upx;
+.padding-left-lg {
+ padding-left: 40upx;
+.padding-left-xl {
+ padding-left: 50upx;
+.padding-lr-xs {
+.padding-lr-sm {
+.padding-lr {
+.padding-lr-lg {
+.padding-lr-xl {
+.padding-tb-xs {
+.padding-tb-sm {
+.padding-tb {
+.padding-tb-lg {
+.padding-tb-xl {
+/* -- 浮动 -- */
+.cf::after,
+.cf::before {
+ display: table;
+.cf::after {
+ clear: both;
+.fl {
+ float: left;
+.fr {
+ float: right;
+ 背景
+.line-red::after,
+.lines-red::after {
+ border-color: #e54d42;
+.line-orange::after,
+.lines-orange::after {
+ border-color: #f37b1d;
+.line-yellow::after,
+.lines-yellow::after {
+ border-color: #fbbd08;
+.line-olive::after,
+.lines-olive::after {
+ border-color: #8dc63f;
+.line-green::after,
+.lines-green::after {
+ border-color: #39b54a;
+.line-cyan::after,
+.lines-cyan::after {
+ border-color: #1cbbb4;
+.line-blue::after,
+.lines-blue::after {
+ border-color: #0081ff;
+.line-purple::after,
+.lines-purple::after {
+ border-color: #6739b6;
+.line-mauve::after,
+.lines-mauve::after {
+ border-color: #9c26b0;
+.line-pink::after,
+.lines-pink::after {
+ border-color: #e03997;
+.line-brown::after,
+.lines-brown::after {
+ border-color: #a5673f;
+.line-grey::after,
+.lines-grey::after {
+ border-color: #8799a3;
+.line-gray::after,
+.lines-gray::after {
+ border-color: #aaaaaa;
+.line-black::after,
+.lines-black::after {
+ border-color: #333333;
+.line-white::after,
+.lines-white::after {
+ border-color: #ffffff;
+.bg-red {
+ background-color: #e54d42;
+.bg-orange {
+ background-color: #f37b1d;
+.bg-yellow {
+ background-color: #fbbd08;
+.bg-olive {
+ background-color: #8dc63f;
+.bg-green {
+ background-color: #39b54a;
+.bg-cyan {
+ background-color: #1cbbb4;
+.bg-blue {
+ background-color: #0081ff;
+.bg-purple {
+ background-color: #6739b6;
+.bg-mauve {
+ background-color: #9c26b0;
+.bg-pink {
+ background-color: #e03997;
+.bg-brown {
+ background-color: #a5673f;
+.bg-grey {
+ background-color: #8799a3;
+.bg-gray {
+.bg-black {
+.bg-white {
+ color: #666666;
+.bg-shadeTop {
+ background-image: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0.01));
+.bg-shadeBottom {
+ background-image: linear-gradient(rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 1));
+.bg-red.light {
+ color: #e54d42;
+ background-color: #fadbd9;
+.bg-orange.light {
+ color: #f37b1d;
+ background-color: #fde6d2;
+.bg-yellow.light {
+ color: #fbbd08;
+ background-color: #fef2ced2;
+.bg-olive.light {
+ color: #8dc63f;
+ background-color: #e8f4d9;
+.bg-green.light {
+ color: #39b54a;
+ background-color: #d7f0dbff;
+.bg-cyan.light {
+ color: #1cbbb4;
+ background-color: #d2f1f0;
+.bg-blue.light {
+ color: #0081ff;
+ background-color: #cce6ff;
+.bg-purple.light {
+ color: #6739b6;
+ background-color: #e1d7f0;
+.bg-mauve.light {
+ color: #9c26b0;
+ background-color: #ebd4ef;
+.bg-pink.light {
+ color: #e03997;
+ background-color: #f9d7ea;
+.bg-brown.light {
+ color: #a5673f;
+ background-color: #ede1d9;
+.bg-grey.light {
+ background-color: #e7ebed;
+.bg-gradual-red {
+ background-image: linear-gradient(45deg, #f43f3b, #ec008c);
+.bg-gradual-orange {
+ background-image: linear-gradient(45deg, #ff9700, #ed1c24);
+.bg-gradual-green {
+ background-image: linear-gradient(45deg, #39b54a, #8dc63f);
+.bg-gradual-purple {
+ background-image: linear-gradient(45deg, #9000ff, #5e00ff);
+.bg-gradual-pink {
+ background-image: linear-gradient(45deg, #ec008c, #6739b6);
+.bg-gradual-blue {
+ background-image: linear-gradient(45deg, #0081ff, #1cbbb4);
+.shadow[class*="-red"] {
+ box-shadow: 6upx 6upx 8upx rgba(204, 69, 59, 0.2);
+.shadow[class*="-orange"] {
+ box-shadow: 6upx 6upx 8upx rgba(217, 109, 26, 0.2);
+.shadow[class*="-yellow"] {
+ box-shadow: 6upx 6upx 8upx rgba(224, 170, 7, 0.2);
+.shadow[class*="-olive"] {
+ box-shadow: 6upx 6upx 8upx rgba(124, 173, 55, 0.2);
+.shadow[class*="-green"] {
+ box-shadow: 6upx 6upx 8upx rgba(48, 156, 63, 0.2);
+.shadow[class*="-cyan"] {
+ box-shadow: 6upx 6upx 8upx rgba(28, 187, 180, 0.2);
+.shadow[class*="-blue"] {
+ box-shadow: 6upx 6upx 8upx rgba(0, 102, 204, 0.2);
+.shadow[class*="-purple"] {
+ box-shadow: 6upx 6upx 8upx rgba(88, 48, 156, 0.2);
+.shadow[class*="-mauve"] {
+ box-shadow: 6upx 6upx 8upx rgba(133, 33, 150, 0.2);
+.shadow[class*="-pink"] {
+ box-shadow: 6upx 6upx 8upx rgba(199, 50, 134, 0.2);
+.shadow[class*="-brown"] {
+ box-shadow: 6upx 6upx 8upx rgba(140, 88, 53, 0.2);
+.shadow[class*="-grey"] {
+ box-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+.shadow[class*="-gray"] {
+.shadow[class*="-black"] {
+ box-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+.shadow[class*="-white"] {
+.text-shadow[class*="-red"] {
+ text-shadow: 6upx 6upx 8upx rgba(204, 69, 59, 0.2);
+.text-shadow[class*="-orange"] {
+ text-shadow: 6upx 6upx 8upx rgba(217, 109, 26, 0.2);
+.text-shadow[class*="-yellow"] {
+ text-shadow: 6upx 6upx 8upx rgba(224, 170, 7, 0.2);
+.text-shadow[class*="-olive"] {
+ text-shadow: 6upx 6upx 8upx rgba(124, 173, 55, 0.2);
+.text-shadow[class*="-green"] {
+ text-shadow: 6upx 6upx 8upx rgba(48, 156, 63, 0.2);
+.text-shadow[class*="-cyan"] {
+ text-shadow: 6upx 6upx 8upx rgba(28, 187, 180, 0.2);
+.text-shadow[class*="-blue"] {
+ text-shadow: 6upx 6upx 8upx rgba(0, 102, 204, 0.2);
+.text-shadow[class*="-purple"] {
+ text-shadow: 6upx 6upx 8upx rgba(88, 48, 156, 0.2);
+.text-shadow[class*="-mauve"] {
+ text-shadow: 6upx 6upx 8upx rgba(133, 33, 150, 0.2);
+.text-shadow[class*="-pink"] {
+ text-shadow: 6upx 6upx 8upx rgba(199, 50, 134, 0.2);
+.text-shadow[class*="-brown"] {
+ text-shadow: 6upx 6upx 8upx rgba(140, 88, 53, 0.2);
+.text-shadow[class*="-grey"] {
+ text-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+.text-shadow[class*="-gray"] {
+.text-shadow[class*="-black"] {
+ text-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+.bg-img {
+ background-repeat: no-repeat;
+.bg-mask {
+.bg-mask::after {
+ background-color: rgba(0, 0, 0, 0.4);
+.bg-mask view,
+.bg-mask cover-view {
+ z-index: 5;
+.bg-video {
+.bg-video video {
+ -o-object-fit: cover;
+ object-fit: cover;
+ 文本
+.text-xs {
+.text-sm {
+.text-df {
+.text-lg {
+.text-xl {
+.text-xxl {
+ font-size: 44upx;
+.text-sl {
+ font-size: 80upx;
+.text-xsl {
+ font-size: 120upx;
+.text-Abc {
+ text-transform: Capitalize;
+.text-ABC {
+ text-transform: Uppercase;
+.text-abc {
+ text-transform: Lowercase;
+.text-price::before {
+ content: "¥";
+ font-size: 80%;
+ margin-right: 4upx;
+.text-cut {
+.text-bold {
+.text-center {
+.text-content {
+.text-left {
+.text-right {
+.text-red,
+.line-red,
+.lines-red {
+.text-orange,
+.line-orange,
+.lines-orange {
+.text-yellow,
+.line-yellow,
+.lines-yellow {
+.text-olive,
+.line-olive,
+.lines-olive {
+.text-green,
+.line-green,
+.lines-green {
+.text-cyan,
+.line-cyan,
+.lines-cyan {
+.text-blue,
+.line-blue,
+.lines-blue {
+.text-purple,
+.line-purple,
+.lines-purple {
+.text-mauve,
+.line-mauve,
+.lines-mauve {
+.text-pink,
+.line-pink,
+.lines-pink {
+.text-brown,
+.line-brown,
+.lines-brown {
+.text-grey,
+.line-grey,
+.lines-grey {
+.text-gray,
+.line-gray,
+.lines-gray {
+ color: #aaaaaa;
+.text-black,
+.line-black,
+.lines-black {
+.text-white,
+.line-white,
+.lines-white {
+.font-13 {
+.font-12 {
+ font-size: 12px;
+.font-11 {
+ font-size: 11px;
+.text-grey1 {
+.text-grey2 {
+ color: #aaa;
+.list-cell-arrow::before {
+ content: ' ';
+ height: 10px;
+ width: 10px;
+ border-width: 2px 2px 0 0;
+ border-color: #c0c0c0;
+ border-style: solid;
+ -webkit-transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
+ transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
+ margin-top: -6px;
+ right: 30rpx;
+ .list-cell {
+ padding: 26rpx 30rpx;
+ .list-cell:first-child {
+ border-radius: 8rpx 8rpx 0 0;
+ .list-cell:last-child {
+ border-radius: 0 0 8rpx 8rpx;
+ .list-cell::after {
+ border-bottom: 1px solid #eaeef1;
+ -webkit-transform: scaleY(0.5) translateZ(0);
+ transform: scaleY(0.5) translateZ(0);
+ transform-origin: 0 100%;
+ .menu-list {
+ .menu-item-box {
+ .menu-icon {
+ color: #007AFF;
+ margin-right: 5px;
+ .text-right {
+ margin-right: 34rpx;
@@ -0,0 +1,6 @@
+// global
+@import "./global.scss";
+// color-ui
+@import "@/static/scss/colorui.css";
+// iconfont
+@import "@/static/font/iconfont.css";
@@ -0,0 +1,8 @@
+const getters = {
+ token: state => state.user.token,
+ avatar: state => state.user.avatar,
+ name: state => state.user.name,
+ roles: state => state.user.roles,
+ permissions: state => state.user.permissions
+export default getters
@@ -0,0 +1,15 @@
+import Vuex from 'vuex'
+import user from '@/store/modules/user'
+import getters from './getters'
+Vue.use(Vuex)
+const store = new Vuex.Store({
+ modules: {
+ user
+ getters
+export default store
@@ -0,0 +1,100 @@
+import config from '@/config'
+import storage from '@/utils/storage'
+import constant from '@/utils/constant'
+import { login, logout, getInfo } from '@/api/login'
+import { setToken, removeToken } from '@/utils/auth'
+const baseUrl = config.baseUrl
+const user = {
+ state: {
+ id: 0, // 用户编号
+ name: storage.get(constant.name),
+ avatar: storage.get(constant.avatar),
+ roles: storage.get(constant.roles),
+ permissions: storage.get(constant.permissions)
+ mutations: {
+ SET_ID: (state, id) => {
+ state.id = id
+ SET_NAME: (state, name) => {
+ state.name = name
+ storage.set(constant.name, name)
+ SET_AVATAR: (state, avatar) => {
+ state.avatar = avatar
+ storage.set(constant.avatar, avatar)
+ SET_ROLES: (state, roles) => {
+ state.roles = roles
+ storage.set(constant.roles, roles)
+ SET_PERMISSIONS: (state, permissions) => {
+ state.permissions = permissions
+ storage.set(constant.permissions, permissions)
+ actions: {
+ // 登录
+ Login({ commit }, userInfo) {
+ const username = userInfo.username.trim()
+ const password = userInfo.password
+ const code = userInfo.code
+ const uuid = userInfo.uuid
+ login(username, password, code, uuid).then(res => {
+ // 设置 token
+ setToken(res)
+ resolve()
+ }).catch(error => {
+ reject(error)
+ // 获取用户信息
+ GetInfo({ commit, state }) {
+ getInfo().then(res => {
+ res = res.data; // 读取 data 数据
+ const user = res.user
+ const avatar = (user == null || user.avatar === "" || user.avatar == null) ? require("@/static/images/profile.jpg") : user.avatar
+ const nickname = (user == null || user.nickname === "" || user.nickname == null) ? "" : user.nickname
+ if (res.roles && res.roles.length > 0) {
+ commit('SET_ROLES', res.roles)
+ commit('SET_PERMISSIONS', res.permissions)
+ commit('SET_ROLES', ['ROLE_DEFAULT'])
+ commit('SET_NAME', nickname)
+ commit('SET_AVATAR', avatar)
+ resolve(res)
+ // 退出系统
+ LogOut({ commit, state }) {
+ logout(state.token).then(() => {
+ commit('SET_TOKEN', '')
+ commit('SET_ROLES', [])
+ commit('SET_PERMISSIONS', [])
+ removeToken()
+ storage.clean()
+export default user
@@ -0,0 +1,64 @@
+/**
+ * uni-app内置的常用样式变量
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+/* 边框颜色 */
+$uni-border-color:#e5e5e5;
+/* 尺寸变量 */
+/* 文字尺寸 */
+$uni-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16px;
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;
@@ -0,0 +1,29 @@
+## 1.2.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge)
+## 1.1.7(2021-11-08)
+- 优化 升级ui
+- 修改 size 属性默认值调整为 small
+- 修改 type 属性,默认值调整为 error,info 替换 default
+## 1.1.6(2021-09-22)
+- 修复 在字节小程序上样式不生效的 bug
+## 1.1.5(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.4(2021-07-29)
+- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性
+## 1.1.3(2021-06-24)
+- 优化 示例项目
+## 1.1.1(2021-05-12)
+- 新增 组件示例地址
+## 1.1.0(2021-05-12)
+- 新增 uni-badge 的 absolute 属性,支持定位
+- 新增 uni-badge 的 offset 属性,支持定位偏移
+- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
+- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
+- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式
+## 1.0.7(2021-05-07)
+- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug
+- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug
+- 新增 uni-badge 属性 custom-style, 支持自定义样式
+## 1.0.6(2021-02-04)
+- 调整为uni_modules目录规范
@@ -0,0 +1,268 @@
+ <view class="uni-badge--x">
+ <text v-if="text" :class="classNames" :style="[badgeWidth, positionStyle, customStyle, dotStyle]"
+ class="uni-badge" @click="onClick()">{{displayValue}}</text>
+ * Badge 数字角标
+ * @description 数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=21
+ * @property {String} text 角标内容
+ * @property {String} size = [normal|small] 角标内容
+ * @property {String} type = [info|primary|success|warning|error] 颜色类型
+ * @value info 灰色
+ * @value primary 蓝色
+ * @value success 绿色
+ * @value warning 黄色
+ * @value error 红色
+ * @property {String} inverted = [true|false] 是否无需背景颜色
+ * @property {Number} maxNum 展示封顶的数字值,超过 99 显示 99+
+ * @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上
+ * @value rightTop 右上
+ * @value rightBottom 右下
+ * @value leftTop 左上
+ * @value leftBottom 左下
+ * @property {Array[number]} offset 距定位角中心点的偏移量,只有存在 absolute 属性时有效,例如:[-10, -10] 表示向外偏移 10px,[10, 10] 表示向 absolute 指定的内偏移 10px
+ * @property {String} isDot = [true|false] 是否显示为一个小点
+ * @event {Function} click 点击 Badge 触发事件
+ * @example <uni-badge text="1"></uni-badge>
+ name: 'UniBadge',
+ emits: ['click'],
+ default: 'error'
+ inverted: {
+ type: Boolean,
+ isDot: {
+ maxNum: {
+ type: Number,
+ default: 99
+ absolute: {
+ offset: {
+ type: Array,
+ default () {
+ return [0, 0]
+ text: {
+ type: [String, Number],
+ size: {
+ default: 'small'
+ customStyle: {
+ type: Object,
+ return {}
+ return {};
+ width() {
+ return String(this.text).length * 8 + 12
+ classNames() {
+ const {
+ inverted,
+ type,
+ size,
+ absolute
+ } = this
+ return [
+ inverted ? 'uni-badge--' + type + '-inverted' : '',
+ 'uni-badge--' + type,
+ 'uni-badge--' + size,
+ absolute ? 'uni-badge--absolute' : ''
+ ].join(' ')
+ positionStyle() {
+ if (!this.absolute) return {}
+ let w = this.width / 2,
+ h = 10
+ if (this.isDot) {
+ w = 5
+ h = 5
+ const x = `${- w + this.offset[0]}px`
+ const y = `${- h + this.offset[1]}px`
+ const whiteList = {
+ rightTop: {
+ right: x,
+ top: y
+ rightBottom: {
+ bottom: y
+ leftBottom: {
+ left: x,
+ leftTop: {
+ const match = whiteList[this.absolute]
+ return match ? match : whiteList['rightTop']
+ badgeWidth() {
+ width: `${this.width}px`
+ dotStyle() {
+ if (!this.isDot) return {}
+ width: '10px',
+ height: '10px',
+ borderRadius: '10px'
+ displayValue() {
+ isDot,
+ text,
+ maxNum
+ return isDot ? '' : (Number(text) > maxNum ? `${maxNum}+` : text)
+ this.$emit('click');
+ };
+ $uni-success: #4cd964 !default;
+ $uni-warning: #f0ad4e !default;
+ $uni-error: #dd524d !default;
+ $uni-info: #909399 !default;
+ $bage-size: 12px;
+ $bage-small: scale(0.8);
+ .uni-badge--x {
+ /* #ifdef APP-NVUE */
+ // align-self: flex-start;
+ .uni-badge--absolute {
+ .uni-badge--small {
+ transform: $bage-small;
+ transform-origin: center center;
+ .uni-badge {
+ height: 20px;
+ line-height: 18px;
+ border-radius: 100px;
+ background-color: $uni-info;
+ border: 1px solid #fff;
+ font-family: 'Helvetica Neue', Helvetica, sans-serif;
+ font-size: $bage-size;
+ /* #ifdef H5 */
+ cursor: pointer;
+ &--info {
+ &--primary {
+ &--success {
+ background-color: $uni-success;
+ &--warning {
+ background-color: $uni-warning;
+ &--error {
+ background-color: $uni-error;
+ &--inverted {
+ padding: 0 5px 0 0;
+ color: $uni-info;
+ &--info-inverted {
+ &--primary-inverted {
+ color: $uni-primary;
+ &--success-inverted {
+ color: $uni-success;
+ &--warning-inverted {
+ color: $uni-warning;
+ &--error-inverted {
+ color: $uni-error;
@@ -0,0 +1,88 @@
+ "id": "uni-badge",
+ "displayName": "uni-badge 数字角标",
+ "version": "1.2.0",
+ "description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
+ "keywords": [
+ "",
+ "badge",
+ "uni-ui",
+ "uniui",
+ "数字角标",
+ "徽章"
+],
+ "repository": "https://github.com/dcloudio/uni-ui",
+ "engines": {
+ "HBuilderX": ""
+ "directories": {
+ "example": "../../temps/example_temps"
+ "dcloudext": {
+ "category": [
+ "前端组件",
+ "通用组件"
+ ],
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ "sourcecode": {
+ "contact": {
+ "qq": ""
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+ "uni_modules": {
+ "dependencies": ["uni-scss"],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ "client": {
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y"
+ "快应用": {
+ "华为": "y",
+ "联盟": "y"
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
@@ -0,0 +1,10 @@
+## Badge 数字角标
+> **组件名:uni-badge**
+> 代码块: `uBadge`
+数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景,
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
+## 0.1.2(2022-06-08)
+- 修复 微信小程序 separator 不显示问题
+## 0.1.1(2022-06-02)
+- 新增 支持 uni.scss 修改颜色
+## 0.1.0(2022-04-21)
+- 初始化
@@ -0,0 +1,121 @@
+ <view class="uni-breadcrumb-item">
+ <view :class="{
+ 'uni-breadcrumb-item--slot': true,
+ 'uni-breadcrumb-item--slot-link': to && currentPage !== to
+ }" @click="navTo">
+ <i v-if="separatorClass" class="uni-breadcrumb-item--separator" :class="separatorClass" />
+ <text v-else class="uni-breadcrumb-item--separator">{{ separator }}</text>
+ * BreadcrumbItem 面包屑导航子组件
+ * @property {String/Object} to 路由跳转页面路径/对象
+ * @property {Boolean} replace 在使用 to 进行路由跳转时,启用 replace 将不会向 history 添加新记录(仅 h5 支持)
+ currentPage: ""
+ options: {
+ virtualHost: true
+ to: {
+ replace:{
+ inject: {
+ uniBreadcrumb: {
+ from: "uniBreadcrumb",
+ created(){
+ const pages = getCurrentPages()
+ const page = pages[pages.length-1]
+ if(page){
+ this.currentPage = `/${page.route}`
+ separator() {
+ return this.uniBreadcrumb.separator
+ separatorClass() {
+ return this.uniBreadcrumb.separatorClass
+ navTo() {
+ const { to } = this
+ if (!to || this.currentPage === to){
+ return
+ if(this.replace){
+ uni.redirectTo({
+ url:to
+ }else{
+ uni.navigateTo({
+ $uni-base-color: #6a6a6a !default;
+ $uni-main-color: #3a3a3a !default;
+ .uni-breadcrumb-item {
+ &--slot {
+ color: $uni-base-color;
+ padding: 0 10px;
+ &-link {
+ color: $uni-main-color;
+ &:hover {
+ &--separator {
+ &:first-child &--slot {
+ padding-left: 0;
+ &:last-child &--separator {
@@ -0,0 +1,41 @@
+ <view class="uni-breadcrumb">
+ * Breadcrumb 面包屑导航父组件
+ * @description 显示当前页面的路径,快速返回之前的任意页面
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
+ * @property {String} separator 分隔符,默认为斜杠'/'
+ * @property {String} separatorClass 图标分隔符 class
+ separator: {
+ default: '/'
+ separatorClass: {
+ provide() {
+ uniBreadcrumb: this
+ .uni-breadcrumb {
+ "id": "uni-breadcrumb",
+ "displayName": "uni-breadcrumb 面包屑",
+ "version": "0.1.2",
+ "description": "Breadcrumb 面包屑",
+ "uni-breadcrumb",
+ "breadcrumb",
+ "面包屑导航",
+ "面包屑"
+ "repository": "",
+ "HBuilderX": "^3.1.0"
+ "npmurl": ""
+ "dependencies": [],
+ "app-nvue": "n"
+ "阿里": "u",
+ "百度": "u",
+ "字节跳动": "u",
+ "QQ": "u",
+ "京东": "u"
+ "华为": "u",
+ "联盟": "u"
@@ -0,0 +1,66 @@
+## breadcrumb 面包屑导航
+> **组件名:uni-breadcrumb**
+> 代码块: `ubreadcrumb`
+显示当前页面的路径,快速返回之前的任意页面。
+### 安装方式
+本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
+如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
+### 基本用法
+在 ``template`` 中使用组件
+```html
+<uni-breadcrumb separator="/">
+ <uni-breadcrumb-item v-for="(route,index) in routes" :key="index" :to="route.to">{{route.name}}</uni-breadcrumb-item>
+</uni-breadcrumb>
+```
+```js
+ name: "uni-stat-breadcrumb",
+ routes: [{
+ to: '/A',
+ name: 'A页面'
+ to: '/B',
+ name: 'B页面'
+ to: '/C',
+ name: 'C页面'
+## API
+### Breadcrumb Props
+|属性名 |类型 |默认值 |说明 |
+|:-: |:-: |:-: |:-: |
+|separator |String |斜杠'/' |分隔符 |
+|separatorClass |String | |图标分隔符 class |
+### Breadcrumb Item Props
+|to |String | |路由跳转页面路径 |
+|replace|Boolean | |在使用 to 进行路由跳转时,启用 replace 将不会向 history 添加新记录(仅 h5 支持) |
+## 组件示例
+点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/breadcrumb/breadcrumb](https://hellouniapp.dcloud.net.cn/pages/extUI/breadcrumb/breadcrumb)
+## 1.4.5(2022-02-25)
+- 修复 条件编译 nvue 不支持的 css 样式
+## 1.4.4(2022-02-25)
+## 1.4.3(2021-09-22)
+- 修复 startDate、 endDate 属性失效的 bug
+## 1.4.2(2021-08-24)
+- 新增 支持国际化
+## 1.4.1(2021-08-05)
+- 修复 弹出层被 tabbar 遮盖 bug
+## 1.4.0(2021-07-30)
+## 1.3.16(2021-05-12)
+## 1.3.15(2021-02-04)
@@ -0,0 +1,546 @@
+* @1900-2100区间内的公历、农历互转
+* @charset UTF-8
+* @github https://github.com/jjonline/calendar.js
+* @Author Jea杨(JJonline@JJonline.Cn)
+* @Time 2014-7-21
+* @Time 2016-8-13 Fixed 2033hex、Attribution Annals
+* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
+* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
+* @Version 1.0.3
+* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
+* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
+/* eslint-disable */
+var calendar = {
+ * 农历1900-2100的润大小信息表
+ * @Array Of Property
+ * @return Hex
+ lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
+ 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
+ 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
+ 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
+ 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
+ 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
+ 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
+ 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
+ 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
+ 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
+ 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
+ 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
+ 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
+ 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
+ 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
+ /** Add By JJonline@JJonline.Cn**/
+ 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
+ 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
+ 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
+ 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
+ 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
+ 0x0d520], // 2100
+ * 公历每个月份的天数普通表
+ * @return Number
+ solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+ * 天干地支之天干速查表
+ * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
+ * @return Cn string
+ Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
+ * 天干地支之地支速查表
+ * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
+ Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
+ * 天干地支之地支速查表<=>生肖
+ * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
+ Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
+ * 24节气速查表
+ * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
+ 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'],
+ * 1900-2100各年的24节气日期速查表
+ * @return 0x string For splice
+ sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
+ '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
+ 'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
+ '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
+ '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
+ '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
+ '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
+ '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
+ '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+ '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+ '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
+ '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+ '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+ '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+ '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+ '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
+ '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
+ '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+ '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+ '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
+ '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
+ '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
+ '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
+ '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+ '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
+ '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
+ '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+ '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
+ '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
+ '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
+ '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
+ '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
+ * 数字转中文速查表
+ * @trans ['日','一','二','三','四','五','六','七','八','九','十']
+ nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
+ * 日期转农历称呼速查表
+ * @trans ['初','十','廿','卅']
+ nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
+ * 月份转农历称呼速查表
+ * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
+ nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
+ * 返回农历y年一整年的总天数
+ * @param lunar Year
+ * @eg:var count = calendar.lYearDays(1987) ;//count=387
+ lYearDays: function (y) {
+ var i; var sum = 348
+ for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
+ return (sum + this.leapDays(y))
+ * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
+ * @return Number (0-12)
+ * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
+ leapMonth: function (y) { // 闰字编码 \u95f0
+ return (this.lunarInfo[y - 1900] & 0xf)
+ * 返回农历y年闰月的天数 若该年没有闰月则返回0
+ * @return Number (0、29、30)
+ * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
+ leapDays: function (y) {
+ if (this.leapMonth(y)) {
+ return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
+ return (0)
+ * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
+ * @return Number (-1、29、30)
+ * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
+ monthDays: function (y, m) {
+ if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
+ return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
+ * 返回公历(!)y年m月的天数
+ * @param solar Year
+ * @return Number (-1、28、29、30、31)
+ * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
+ solarDays: function (y, m) {
+ if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+ var ms = m - 1
+ if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
+ return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
+ return (this.solarMonth[ms])
+ * 农历年份转换为干支纪年
+ * @param lYear 农历年的年份数
+ toGanZhiYear: function (lYear) {
+ var ganKey = (lYear - 3) % 10
+ var zhiKey = (lYear - 3) % 12
+ if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
+ if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
+ return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
+ * 公历月、日判断所属星座
+ * @param cMonth [description]
+ * @param cDay [description]
+ toAstro: function (cMonth, cDay) {
+ 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'
+ var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
+ return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
+ * 传入offset偏移量返回干支
+ * @param offset 相对甲子的偏移量
+ toGanZhi: function (offset) {
+ return this.Gan[offset % 10] + this.Zhi[offset % 12]
+ * 传入公历(!)y年获得该年第n个节气的公历日期
+ * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
+ * @return day Number
+ * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
+ getTerm: function (y, n) {
+ if (y < 1900 || y > 2100) { return -1 }
+ if (n < 1 || n > 24) { return -1 }
+ var _table = this.sTermInfo[y - 1900]
+ var _info = [
+ parseInt('0x' + _table.substr(0, 5)).toString(),
+ parseInt('0x' + _table.substr(5, 5)).toString(),
+ parseInt('0x' + _table.substr(10, 5)).toString(),
+ parseInt('0x' + _table.substr(15, 5)).toString(),
+ parseInt('0x' + _table.substr(20, 5)).toString(),
+ parseInt('0x' + _table.substr(25, 5)).toString()
+ var _calday = [
+ _info[0].substr(0, 1),
+ _info[0].substr(1, 2),
+ _info[0].substr(3, 1),
+ _info[0].substr(4, 2),
+ _info[1].substr(0, 1),
+ _info[1].substr(1, 2),
+ _info[1].substr(3, 1),
+ _info[1].substr(4, 2),
+ _info[2].substr(0, 1),
+ _info[2].substr(1, 2),
+ _info[2].substr(3, 1),
+ _info[2].substr(4, 2),
+ _info[3].substr(0, 1),
+ _info[3].substr(1, 2),
+ _info[3].substr(3, 1),
+ _info[3].substr(4, 2),
+ _info[4].substr(0, 1),
+ _info[4].substr(1, 2),
+ _info[4].substr(3, 1),
+ _info[4].substr(4, 2),
+ _info[5].substr(0, 1),
+ _info[5].substr(1, 2),
+ _info[5].substr(3, 1),
+ _info[5].substr(4, 2)
+ return parseInt(_calday[n - 1])
+ * 传入农历数字月份返回汉语通俗表示法
+ * @param lunar month
+ * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
+ toChinaMonth: function (m) { // 月 => \u6708
+ var s = this.nStr3[m - 1]
+ s += '\u6708'// 加上月字
+ return s
+ * 传入农历日期数字返回汉字表示法
+ * @param lunar day
+ * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
+ toChinaDay: function (d) { // 日 => \u65e5
+ var s
+ switch (d) {
+ case 10:
+ s = '\u521d\u5341'; break
+ case 20:
+ s = '\u4e8c\u5341'; break
+ case 30:
+ s = '\u4e09\u5341'; break
+ default :
+ s = this.nStr2[Math.floor(d / 10)]
+ s += this.nStr1[d % 10]
+ return (s)
+ * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
+ * @param y year
+ * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
+ getAnimal: function (y) {
+ return this.Animals[(y - 4) % 12]
+ * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
+ * @param y solar year
+ * @param m solar month
+ * @param d solar day
+ * @return JSON object
+ * @eg:console.log(calendar.solar2lunar(1987,11,01));
+ solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
+ // 年份限定、上限
+ if (y < 1900 || y > 2100) {
+ return -1// undefined转换为数字变为NaN
+ // 公历传参最下限
+ if (y == 1900 && m == 1 && d < 31) {
+ return -1
+ // 未传参 获得当天
+ if (!y) {
+ var objDate = new Date()
+ var objDate = new Date(y, parseInt(m) - 1, d)
+ var i; var leap = 0; var temp = 0
+ // 修正ymd参数
+ var y = objDate.getFullYear()
+ var m = objDate.getMonth() + 1
+ var d = objDate.getDate()
+ var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
+ for (i = 1900; i < 2101 && offset > 0; i++) {
+ temp = this.lYearDays(i)
+ offset -= temp
+ if (offset < 0) {
+ offset += temp; i--
+ // 是否今天
+ var isTodayObj = new Date()
+ var isToday = false
+ if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
+ isToday = true
+ // 星期几
+ var nWeek = objDate.getDay()
+ var cWeek = this.nStr1[nWeek]
+ // 数字表示周几顺应天朝周一开始的惯例
+ if (nWeek == 0) {
+ nWeek = 7
+ // 农历年
+ var year = i
+ var leap = this.leapMonth(i) // 闰哪个月
+ var isLeap = false
+ // 效验闰月
+ for (i = 1; i < 13 && offset > 0; i++) {
+ // 闰月
+ if (leap > 0 && i == (leap + 1) && isLeap == false) {
+ --i
+ isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
+ temp = this.monthDays(year, i)// 计算农历普通月天数
+ // 解除闰月
+ if (isLeap == true && i == (leap + 1)) { isLeap = false }
+ // 闰月导致数组下标重叠取反
+ if (offset == 0 && leap > 0 && i == leap + 1) {
+ if (isLeap) {
+ isLeap = false
+ isLeap = true; --i
+ offset += temp; --i
+ // 农历月
+ var month = i
+ // 农历日
+ var day = offset + 1
+ // 天干地支处理
+ var sm = m - 1
+ var gzY = this.toGanZhiYear(year)
+ // 当月的两个节气
+ // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
+ var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
+ var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
+ // 依据12节气修正干支月
+ var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
+ if (d >= firstNode) {
+ gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
+ // 传入的日期的节气与否
+ var isTerm = false
+ var Term = null
+ if (firstNode == d) {
+ isTerm = true
+ Term = this.solarTerm[m * 2 - 2]
+ if (secondNode == d) {
+ Term = this.solarTerm[m * 2 - 1]
+ // 日柱 当月一日与 1900/1/1 相差天数
+ var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
+ var gzD = this.toGanZhi(dayCyclical + d - 1)
+ // 该日期所属的星座
+ var astro = this.toAstro(m, d)
+ 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 }
+ * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
+ * @param y lunar year
+ * @param m lunar month
+ * @param d lunar day
+ * @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
+ * @eg:console.log(calendar.lunar2solar(1987,9,10));
+ lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
+ var isLeapMonth = !!isLeapMonth
+ var leapOffset = 0
+ var leapMonth = this.leapMonth(y)
+ var leapDay = this.leapDays(y)
+ if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
+ if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
+ var day = this.monthDays(y, m)
+ var _day = day
+ // bugFix 2016-9-25
+ // if month is leap, _day use leapDays method
+ if (isLeapMonth) {
+ _day = this.leapDays(y, m)
+ if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
+ // 计算农历的时间差
+ var offset = 0
+ for (var i = 1900; i < y; i++) {
+ offset += this.lYearDays(i)
+ var leap = 0; var isAdd = false
+ for (var i = 1; i < m; i++) {
+ leap = this.leapMonth(y)
+ if (!isAdd) { // 处理闰月
+ if (leap <= i && leap > 0) {
+ offset += this.leapDays(y); isAdd = true
+ offset += this.monthDays(y, i)
+ // 转换闰月农历 需补充该年闰月的前一个月的时差
+ if (isLeapMonth) { offset += day }
+ // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
+ var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
+ var calObj = new Date((offset + d - 31) * 86400000 + stmap)
+ var cY = calObj.getUTCFullYear()
+ var cM = calObj.getUTCMonth() + 1
+ var cD = calObj.getUTCDate()
+ return this.solar2lunar(cY, cM, cD)
+export default calendar
@@ -0,0 +1,12 @@
+ "uni-calender.ok": "ok",
+ "uni-calender.cancel": "cancel",
+ "uni-calender.today": "today",
+ "uni-calender.MON": "MON",
+ "uni-calender.TUE": "TUE",
+ "uni-calender.WED": "WED",
+ "uni-calender.THU": "THU",
+ "uni-calender.FRI": "FRI",
+ "uni-calender.SAT": "SAT",
+ "uni-calender.SUN": "SUN"
+import en from './en.json'
+import zhHans from './zh-Hans.json'
+import zhHant from './zh-Hant.json'
+ en,
+ 'zh-Hans': zhHans,
+ 'zh-Hant': zhHant
+ "uni-calender.ok": "确定",
+ "uni-calender.cancel": "取消",
+ "uni-calender.today": "今日",
+ "uni-calender.SUN": "日",
+ "uni-calender.MON": "一",
+ "uni-calender.TUE": "二",
+ "uni-calender.WED": "三",
+ "uni-calender.THU": "四",
+ "uni-calender.FRI": "五",
+ "uni-calender.SAT": "六"
+ "uni-calender.ok": "確定",
@@ -0,0 +1,188 @@
+ <view class="uni-calendar-item__weeks-box" :class="{
+ 'uni-calendar-item--disable':weeks.disable,
+ 'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+ 'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
+ 'uni-calendar-item--before-checked':weeks.beforeMultiple,
+ 'uni-calendar-item--multiple': weeks.multiple,
+ 'uni-calendar-item--after-checked':weeks.afterMultiple,
+ }"
+ @click="choiceDate(weeks)">
+ <view class="uni-calendar-item__weeks-box-item">
+ <text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
+ <text class="uni-calendar-item__weeks-box-text" :class="{
+ 'uni-calendar-item--isDay-text': weeks.isDay,
+ 'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+ }">{{weeks.date}}</text>
+ <text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
+ 'uni-calendar-item--isDay-text':weeks.isDay,
+ }">{{todayText}}</text>
+ <text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
+ }">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
+ <text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
+ 'uni-calendar-item--extra':weeks.extraInfo.info,
+ }">{{weeks.extraInfo.info}}</text>
+ import {
+ initVueI18n
+ } from '@dcloudio/uni-i18n'
+ import messages from './i18n/index.js'
+ const { t } = initVueI18n(messages)
+ emits:['change'],
+ weeks: {
+ calendar: {
+ default: () => {
+ selected: {
+ return []
+ lunar: {
+ todayText() {
+ return t("uni-calender.today")
+ choiceDate(weeks) {
+ this.$emit('change', weeks)
+ $uni-font-size-base:14px;
+ $uni-text-color:#333;
+ $uni-font-size-sm:12px;
+ $uni-color-error: #e43d33;
+ $uni-opacity-disabled: 0.3;
+ $uni-text-color-disable:#c0c0c0;
+ $uni-color-primary: #2979ff;
+ .uni-calendar-item__weeks-box {
+ .uni-calendar-item__weeks-box-text {
+ font-size: $uni-font-size-base;
+ color: $uni-text-color;
+ .uni-calendar-item__weeks-lunar-text {
+ font-size: $uni-font-size-sm;
+ .uni-calendar-item__weeks-box-item {
+ width: 100rpx;
+ height: 100rpx;
+ .uni-calendar-item__weeks-box-circle {
+ top: 5px;
+ background-color: $uni-color-error;
+ .uni-calendar-item--disable {
+ background-color: rgba(249, 249, 249, $uni-opacity-disabled);
+ color: $uni-text-color-disable;
+ .uni-calendar-item--isDay-text {
+ color: $uni-color-primary;
+ .uni-calendar-item--isDay {
+ background-color: $uni-color-primary;
+ opacity: 0.8;
+ .uni-calendar-item--extra {
+ color: $uni-color-error;
+ .uni-calendar-item--checked {
+ .uni-calendar-item--multiple {
+ .uni-calendar-item--before-checked {
+ background-color: #ff5a5f;
+ .uni-calendar-item--after-checked {
@@ -0,0 +1,562 @@
+ <view class="uni-calendar">
+ <view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}" @click="clean"></view>
+ <view v-if="insert || show" class="uni-calendar__content" :class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow}">
+ <view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
+ <view class="uni-calendar__header-btn-box" @click="close">
+ <text class="uni-calendar__header-text uni-calendar--fixed-width">{{cancelText}}</text>
+ <view class="uni-calendar__header-btn-box" @click="confirm">
+ <text class="uni-calendar__header-text uni-calendar--fixed-width">{{okText}}</text>
+ <view class="uni-calendar__header">
+ <view class="uni-calendar__header-btn-box" @click.stop="pre">
+ <view class="uni-calendar__header-btn uni-calendar--left"></view>
+ <picker mode="date" :value="date" fields="month" @change="bindDateChange">
+ <text class="uni-calendar__header-text">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>
+ </picker>
+ <view class="uni-calendar__header-btn-box" @click.stop="next">
+ <view class="uni-calendar__header-btn uni-calendar--right"></view>
+ <text class="uni-calendar__backtoday" @click="backtoday">{{todayText}}</text>
+ <view class="uni-calendar__box">
+ <view v-if="showMonth" class="uni-calendar__box-bg">
+ <text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
+ <view class="uni-calendar__weeks">
+ <view class="uni-calendar__weeks-day">
+ <text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
+ <text class="uni-calendar__weeks-day-text">{{monText}}</text>
+ <text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
+ <text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
+ <text class="uni-calendar__weeks-day-text">{{THUText}}</text>
+ <text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
+ <text class="uni-calendar__weeks-day-text">{{SATText}}</text>
+ <view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
+ <view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
+ <calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item>
+ import Calendar from './util.js';
+ import calendarItem from './uni-calendar-item.vue'
+ * Calendar 日历
+ * @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=56
+ * @property {String} date 自定义当前时间,默认为今天
+ * @property {Boolean} lunar 显示农历
+ * @property {String} startDate 日期选择范围-开始日期
+ * @property {String} endDate 日期选择范围-结束日期
+ * @property {Boolean} range 范围选择
+ * @property {Boolean} insert = [true|false] 插入模式,默认为false
+ * @value true 弹窗模式
+ * @value false 插入模式
+ * @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
+ * @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
+ * @property {Boolean} showMonth 是否选择月份为背景
+ * @event {Function} change 日期改变,`insert :ture` 时生效
+ * @event {Function} confirm 确认选择`insert :false` 时生效
+ * @event {Function} monthSwitch 切换月份时触发
+ * @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
+ components: {
+ calendarItem
+ emits:['close','confirm','change','monthSwitch'],
+ date: {
+ startDate: {
+ endDate: {
+ range: {
+ insert: {
+ default: true
+ showMonth: {
+ clearDate: {
+ show: false,
+ weeks: [],
+ calendar: {},
+ nowDate: '',
+ aniMaskShow: false
+ * for i18n
+ okText() {
+ return t("uni-calender.ok")
+ cancelText() {
+ return t("uni-calender.cancel")
+ monText() {
+ return t("uni-calender.MON")
+ TUEText() {
+ return t("uni-calender.TUE")
+ WEDText() {
+ return t("uni-calender.WED")
+ THUText() {
+ return t("uni-calender.THU")
+ FRIText() {
+ return t("uni-calender.FRI")
+ SATText() {
+ return t("uni-calender.SAT")
+ SUNText() {
+ return t("uni-calender.SUN")
+ date(newVal) {
+ // this.cale.setDate(newVal)
+ this.init(newVal)
+ startDate(val){
+ this.cale.resetSatrtDate(val)
+ this.cale.setDate(this.nowDate.fullDate)
+ this.weeks = this.cale.weeks
+ endDate(val){
+ this.cale.resetEndDate(val)
+ selected(newVal) {
+ this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
+ // 获取日历方法实例
+ this.cale = new Calendar({
+ // date: new Date(),
+ selected: this.selected,
+ startDate: this.startDate,
+ endDate: this.endDate,
+ range: this.range,
+ // 选中某一天
+ // this.cale.setDate(this.date)
+ this.init(this.date)
+ // this.setDay
+ // 取消穿透
+ clean() {},
+ bindDateChange(e) {
+ const value = e.detail.value + '-1'
+ console.log(this.cale.getDate(value));
+ this.init(value)
+ * 初始化日期显示
+ * @param {Object} date
+ init(date) {
+ this.cale.setDate(date)
+ this.nowDate = this.calendar = this.cale.getInfo(date)
+ * 打开日历弹窗
+ open() {
+ // 弹窗模式并且清理数据
+ if (this.clearDate && !this.insert) {
+ this.cale.cleanMultipleStatus()
+ this.show = true
+ this.$nextTick(() => {
+ setTimeout(() => {
+ this.aniMaskShow = true
+ }, 50)
+ * 关闭日历弹窗
+ close() {
+ this.aniMaskShow = false
+ this.show = false
+ this.$emit('close')
+ }, 300)
+ * 确认按钮
+ confirm() {
+ this.setEmit('confirm')
+ this.close()
+ * 变化触发
+ change() {
+ if (!this.insert) return
+ this.setEmit('change')
+ * 选择月份触发
+ monthSwitch() {
+ let {
+ year,
+ month
+ } = this.nowDate
+ this.$emit('monthSwitch', {
+ month: Number(month)
+ * 派发事件
+ * @param {Object} name
+ setEmit(name) {
+ month,
+ date,
+ fullDate,
+ lunar,
+ extraInfo
+ } = this.calendar
+ this.$emit(name, {
+ range: this.cale.multipleStatus,
+ fulldate: fullDate,
+ extraInfo: extraInfo || {}
+ * 选择天触发
+ * @param {Object} weeks
+ if (weeks.disable) return
+ this.calendar = weeks
+ // 设置多选
+ this.cale.setMultiple(this.calendar.fullDate)
+ this.change()
+ * 回到今天
+ backtoday() {
+ console.log(this.cale.getDate(new Date()).fullDate);
+ let date = this.cale.getDate(new Date()).fullDate
+ // this.cale.setDate(date)
+ this.init(date)
+ * 上个月
+ pre() {
+ const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
+ this.setDate(preDate)
+ this.monthSwitch()
+ * 下个月
+ next() {
+ const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
+ this.setDate(nextDate)
+ * 设置日期
+ setDate(date) {
+ this.nowDate = this.cale.getInfo(date)
+ $uni-bg-color-mask: rgba($color: #000000, $alpha: 0.4);
+ $uni-border-color: #EDEDED;
+ $uni-text-color: #333;
+ $uni-bg-color-hover:#f1f1f1;
+ $uni-text-color-placeholder: #808080;
+ $uni-color-subtitle: #555555;
+ $uni-text-color-grey:#999;
+ .uni-calendar {
+ .uni-calendar__mask {
+ background-color: $uni-bg-color-mask;
+ transition-property: opacity;
+ z-index: 99;
+ .uni-calendar--mask-show {
+ opacity: 1
+ .uni-calendar--fixed {
+ transition-property: transform;
+ transform: translateY(460px);
+ bottom: calc(var(--window-bottom));
+ .uni-calendar--ani-show {
+ transform: translateY(0);
+ .uni-calendar__content {
+ .uni-calendar__header {
+ height: 50px;
+ border-bottom-color: $uni-border-color;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ .uni-calendar--fixed-top {
+ border-top-color: $uni-border-color;
+ border-top-style: solid;
+ border-top-width: 1px;
+ .uni-calendar--fixed-width {
+ width: 50px;
+ // padding: 0 15px;
+ .uni-calendar__backtoday {
+ top: 25rpx;
+ padding: 0 5px;
+ padding-left: 10px;
+ height: 25px;
+ line-height: 25px;
+ border-top-left-radius: 25px;
+ border-bottom-left-radius: 25px;
+ background-color: $uni-bg-color-hover;
+ .uni-calendar__header-text {
+ width: 100px;
+ .uni-calendar__header-btn-box {
+ .uni-calendar__header-btn {
+ border-left-color: $uni-text-color-placeholder;
+ border-left-style: solid;
+ border-left-width: 2px;
+ border-top-color: $uni-color-subtitle;
+ border-top-width: 2px;
+ .uni-calendar--left {
+ transform: rotate(-45deg);
+ .uni-calendar--right {
+ transform: rotate(135deg);
+ .uni-calendar__weeks {
+ .uni-calendar__weeks-item {
+ .uni-calendar__weeks-day {
+ border-bottom-color: #F5F5F5;
+ .uni-calendar__weeks-day-text {
+ .uni-calendar__box {
+ .uni-calendar__box-bg {
+ .uni-calendar__box-bg-text {
+ font-size: 200px;
+ color: $uni-text-color-grey;
@@ -0,0 +1,350 @@
+import CALENDAR from './calendar.js'
+class Calendar {
+ constructor({
+ selected,
+ startDate,
+ endDate,
+ range
+ } = {}) {
+ // 当前日期
+ this.date = this.getDate(new Date()) // 当前初入日期
+ // 打点信息
+ this.selected = selected || [];
+ // 范围开始
+ this.startDate = startDate
+ // 范围结束
+ this.endDate = endDate
+ this.range = range
+ // 多选状态
+ this.cleanMultipleStatus()
+ // 每周日期
+ this.weeks = {}
+ // this._getWeek(this.date.fullDate)
+ this.selectDate = this.getDate(date)
+ this._getWeek(this.selectDate.fullDate)
+ * 清理多选状态
+ cleanMultipleStatus() {
+ this.multipleStatus = {
+ before: '',
+ after: '',
+ data: []
+ * 重置开始日期
+ resetSatrtDate(startDate) {
+ * 重置结束日期
+ resetEndDate(endDate) {
+ * 获取任意时间
+ getDate(date, AddDayCount = 0, str = 'day') {
+ if (!date) {
+ date = new Date()
+ if (typeof date !== 'object') {
+ date = date.replace(/-/g, '/')
+ const dd = new Date(date)
+ switch (str) {
+ case 'day':
+ dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
+ case 'month':
+ if (dd.getDate() === 31) {
+ dd.setDate(dd.getDate() + AddDayCount)
+ dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
+ case 'year':
+ dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
+ const y = dd.getFullYear()
+ const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
+ const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
+ fullDate: y + '-' + m + '-' + d,
+ year: y,
+ month: m,
+ date: d,
+ day: dd.getDay()
+ * 获取上月剩余天数
+ _getLastMonthDays(firstDay, full) {
+ let dateArr = []
+ for (let i = firstDay; i > 0; i--) {
+ const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
+ dateArr.push({
+ date: beforeDate,
+ month: full.month - 1,
+ lunar: this.getlunar(full.year, full.month - 1, beforeDate),
+ disable: true
+ return dateArr
+ * 获取本月天数
+ _currentMonthDys(dateData, full) {
+ let fullDate = this.date.fullDate
+ for (let i = 1; i <= dateData; i++) {
+ let nowDate = full.year + '-' + (full.month < 10 ?
+ full.month : full.month) + '-' + (i < 10 ?
+ '0' + i : i)
+ let isDay = fullDate === nowDate
+ // 获取打点信息
+ let info = this.selected && this.selected.find((item) => {
+ if (this.dateEqual(nowDate, item.date)) {
+ return item
+ // 日期禁用
+ let disableBefore = true
+ let disableAfter = true
+ if (this.startDate) {
+ // let dateCompBefore = this.dateCompare(this.startDate, fullDate)
+ // disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
+ disableBefore = this.dateCompare(this.startDate, nowDate)
+ if (this.endDate) {
+ // let dateCompAfter = this.dateCompare(fullDate, this.endDate)
+ // disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
+ disableAfter = this.dateCompare(nowDate, this.endDate)
+ let multiples = this.multipleStatus.data
+ let checked = false
+ let multiplesStatus = -1
+ if (this.range) {
+ if (multiples) {
+ multiplesStatus = multiples.findIndex((item) => {
+ return this.dateEqual(item, nowDate)
+ if (multiplesStatus !== -1) {
+ checked = true
+ let data = {
+ fullDate: nowDate,
+ year: full.year,
+ date: i,
+ multiple: this.range ? checked : false,
+ beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
+ afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
+ month: full.month,
+ lunar: this.getlunar(full.year, full.month, i),
+ disable: !(disableBefore && disableAfter),
+ isDay
+ if (info) {
+ data.extraInfo = info
+ dateArr.push(data)
+ * 获取下月天数
+ _getNextMonthDays(surplus, full) {
+ for (let i = 1; i < surplus + 1; i++) {
+ month: Number(full.month) + 1,
+ lunar: this.getlunar(full.year, Number(full.month) + 1, i),
+ * 获取当前日期详情
+ getInfo(date) {
+ const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
+ return dateInfo
+ * 比较时间大小
+ dateCompare(startDate, endDate) {
+ // 计算截止时间
+ startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
+ // 计算详细项的截止时间
+ endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
+ if (startDate <= endDate) {
+ * 比较时间是否相等
+ dateEqual(before, after) {
+ before = new Date(before.replace('-', '/').replace('-', '/'))
+ after = new Date(after.replace('-', '/').replace('-', '/'))
+ if (before.getTime() - after.getTime() === 0) {
+ * 获取日期范围内所有日期
+ * @param {Object} begin
+ * @param {Object} end
+ geDateAll(begin, end) {
+ var arr = []
+ var ab = begin.split('-')
+ var ae = end.split('-')
+ var db = new Date()
+ db.setFullYear(ab[0], ab[1] - 1, ab[2])
+ var de = new Date()
+ de.setFullYear(ae[0], ae[1] - 1, ae[2])
+ var unixDb = db.getTime() - 24 * 60 * 60 * 1000
+ var unixDe = de.getTime() - 24 * 60 * 60 * 1000
+ for (var k = unixDb; k <= unixDe;) {
+ k = k + 24 * 60 * 60 * 1000
+ arr.push(this.getDate(new Date(parseInt(k))).fullDate)
+ return arr
+ * 计算阴历日期显示
+ getlunar(year, month, date) {
+ return CALENDAR.solar2lunar(year, month, date)
+ * 设置打点
+ setSelectInfo(data, value) {
+ this.selected = value
+ this._getWeek(data)
+ * 获取多选状态
+ setMultiple(fullDate) {
+ before,
+ after
+ } = this.multipleStatus
+ if (!this.range) return
+ if (before && after) {
+ this.multipleStatus.before = ''
+ this.multipleStatus.after = ''
+ this.multipleStatus.data = []
+ if (!before) {
+ this.multipleStatus.before = fullDate
+ this.multipleStatus.after = fullDate
+ if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
+ this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
+ this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
+ this._getWeek(fullDate)
+ * 获取每周数据
+ * @param {Object} dateData
+ _getWeek(dateData) {
+ } = this.getDate(dateData)
+ let firstDay = new Date(year, month - 1, 1).getDay()
+ let currentDay = new Date(year, month, 0).getDate()
+ let dates = {
+ lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
+ currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
+ nextMonthDays: [], // 下个月开始几天
+ weeks: []
+ let canlender = []
+ const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
+ dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
+ canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
+ let weeks = {}
+ // 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
+ for (let i = 0; i < canlender.length; i++) {
+ if (i % 7 === 0) {
+ weeks[parseInt(i / 7)] = new Array(7)
+ weeks[parseInt(i / 7)][i % 7] = canlender[i]
+ this.canlender = canlender
+ this.weeks = weeks
+ //静态方法
+ // static init(date) {
+ // if (!this.instance) {
+ // this.instance = new Calendar(date);
+ // }
+ // return this.instance;
+export default Calendar
+ "id": "uni-calendar",
+ "displayName": "uni-calendar 日历",
+ "version": "1.4.5",
+ "description": "日历组件",
+ "日历",
+ "打卡",
+ "日历选择"
@@ -0,0 +1,103 @@
+## Calendar 日历
+> **组件名:uni-calendar**
+> 代码块: `uCalendar`
+日历组件
+> **注意事项**
+> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
+> - 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js)
+> - 仅支持自定义组件模式
+> - `date`属性传入的应该是一个 String ,如: 2019-06-27 ,而不是 new Date()
+> - 通过 `insert` 属性来确定当前的事件是 @change 还是 @confirm 。理应合并为一个事件,但是为了区分模式,现使用两个事件,这里需要注意
+> - 弹窗模式下无法阻止后面的元素滚动,如有需要阻止,请在弹窗弹出后,手动设置滚动元素为不可滚动
+<view>
+ <uni-calendar
+ :insert="true"
+ :lunar="true"
+ :start-date="'2019-3-2'"
+ :end-date="'2019-5-20'"
+ @change="change"
+ />
+</view>
+### 通过方法打开日历
+需要设置 `insert` 为 `false`
+ ref="calendar"
+ :insert="false"
+ @confirm="confirm"
+ <button @click="open">打开日历</button>
+```javascript
+ open(){
+ this.$refs.calendar.open();
+ confirm(e) {
+ console.log(e);
+};
+### Calendar Props
+| 属性名 | 类型 | 默认值| 说明 |
+| | |
+| date | String |- | 自定义当前时间,默认为今天 |
+| lunar | Boolean | false | 显示农历 |
+| startDate | String |- | 日期选择范围-开始日期 |
+| endDate | String |- | 日期选择范围-结束日期 |
+| range | Boolean | false | 范围选择 |
+| insert | Boolean | false | 插入模式,可选值,ture:插入模式;false:弹窗模式;默认为插入模式 |
+|clearDate |Boolean |true |弹窗模式是否清空上次选择内容 |
+| selected | Array |- | 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] |
+|showMonth | Boolean | true | 是否显示月份为背景 |
+### Calendar Events
+| 事件名 | 说明 |返回值|
+| | | |
+| open | 弹出日历组件,`insert :false` 时生效|- |
+点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)
+## 1.3.1(2021-12-20)
+- 修复 在vue页面下略缩图显示不正常的bug
+## 1.3.0(2021-11-19)
+- 重构插槽的用法 ,header 替换为 title
+- 新增 actions 插槽
+- 新增 cover 封面图属性和插槽
+- 新增 padding 内容默认内边距离
+- 新增 margin 卡片默认外边距离
+- 新增 spacing 卡片默认内边距
+- 新增 shadow 卡片阴影属性
+- 取消 mode 属性,可使用组合插槽代替
+- 取消 note 属性 ,使用actions插槽代替
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)
+## 1.2.1(2021-07-30)
+- 优化 vue3下事件警告的问题
+## 1.2.0(2021-07-13)
+- 组件兼容 vue3,如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.8(2021-07-01)
+- 优化 图文卡片无图片加载时,提供占位图标
+- 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持)
+- 修复 thumbnail 不存在仍然占位的 bug
+## 1.1.7(2021-05-12)
+## 1.1.6(2021-02-04)
@@ -0,0 +1,270 @@
+ <view class="uni-card" :class="{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':border}"
+ :style="{'margin':isFull?0:margin,'padding':spacing,'box-shadow':isShadow?shadow:''}">
+ <!-- 封面 -->
+ <slot name="cover">
+ <view v-if="cover" class="uni-card__cover">
+ <image class="uni-card__cover-image" mode="widthFix" @click="onClick('cover')" :src="cover"></image>
+ </slot>
+ <slot name="title">
+ <view v-if="title || extra" class="uni-card__header">
+ <!-- 卡片标题 -->
+ <view class="uni-card__header-box" @click="onClick('title')">
+ <view v-if="thumbnail" class="uni-card__header-avatar">
+ <image class="uni-card__header-avatar-image" :src="thumbnail" mode="aspectFit" />
+ <view class="uni-card__header-content">
+ <text class="uni-card__header-content-title uni-ellipsis">{{ title }}</text>
+ <text v-if="title&&subTitle"
+ class="uni-card__header-content-subtitle uni-ellipsis">{{ subTitle }}</text>
+ <view class="uni-card__header-extra" @click="onClick('extra')">
+ <text class="uni-card__header-extra-text">{{ extra }}</text>
+ <!-- 卡片内容 -->
+ <view class="uni-card__content" :style="{padding:padding}" @click="onClick('content')">
+ <slot></slot>
+ <view class="uni-card__actions" @click="onClick('actions')">
+ <slot name="actions"></slot>
+ * Card 卡片
+ * @description 卡片视图组件
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=22
+ * @property {String} title 标题文字
+ * @property {Number} padding 内容内边距
+ * @property {Number} margin 卡片外边距
+ * @property {Number} spacing 卡片内边距
+ * @property {String} extra 标题额外信息
+ * @property {String} cover 封面图(本地路径需要引入)
+ * @property {String} thumbnail 标题左侧缩略图
+ * @property {Boolean} is-full = [true | false] 卡片内容是否通栏,为 true 时将去除padding值
+ * @property {Boolean} is-shadow = [true | false] 卡片内容是否开启阴影
+ * @property {String} shadow 卡片阴影
+ * @property {Boolean} border 卡片边框
+ * @event {Function} click 点击 Card 触发事件
+ name: 'UniCard',
+ default: '10px'
+ margin: {
+ default: '15px'
+ spacing: {
+ default: '0 10px'
+ extra: {
+ cover: {
+ thumbnail: {
+ isFull: {
+ // 内容区域是否通栏
+ isShadow: {
+ // 是否开启阴影
+ shadow: {
+ default: '0px 0px 3px 1px rgba(0, 0, 0, 0.08)'
+ border: {
+ onClick(type) {
+ this.$emit('click', type)
+ $uni-border-3: #EBEEF5 !default;
+ $uni-shadow-base:0 0px 6px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
+ $uni-secondary-color: #909399 !default;
+ $uni-spacing-sm: 8px !default;
+ $uni-border-color:$uni-border-3;
+ $uni-shadow: $uni-shadow-base;
+ $uni-card-title: 15px;
+ $uni-cart-title-color:$uni-main-color;
+ $uni-card-subtitle: 12px;
+ $uni-cart-subtitle-color:$uni-secondary-color;
+ $uni-card-spacing: 10px;
+ $uni-card-content-color: $uni-base-color;
+ .uni-card {
+ margin: $uni-card-spacing;
+ padding: 0 $uni-spacing-sm;
+ font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
+ .uni-card__cover {
+ margin-top: $uni-card-spacing;
+ .uni-card__cover-image {
+ // width: 100%;
+ /* #ifndef APP-PLUS */
+ .uni-card__header {
+ border-bottom: 1px $uni-border-color solid;
+ padding: $uni-card-spacing;
+ .uni-card__header-box {
+ .uni-card__header-avatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 5px;
+ margin-right: $uni-card-spacing;
+ .uni-card__header-avatar-image {
+ .uni-card__header-content {
+ // height: 40px;
+ .uni-card__header-content-title {
+ font-size: $uni-card-title;
+ color: $uni-cart-title-color;
+ // line-height: 22px;
+ .uni-card__header-content-subtitle {
+ font-size: $uni-card-subtitle;
+ margin-top: 5px;
+ color: $uni-cart-subtitle-color;
+ .uni-card__header-extra {
+ line-height: 12px;
+ .uni-card__header-extra-text {
+ .uni-card__content {
+ color: $uni-card-content-color;
+ line-height: 22px;
+ .uni-card__actions {
+ .uni-card--border {
+ border: 1px solid $uni-border-color;
+ .uni-card--shadow {
+ box-shadow: $uni-shadow;
+ .uni-card--full {
+ border-left-width: 0;
+ .uni-card--full:after {
+ .uni-ellipsis {
+ lines: 1;
+ "id": "uni-card",
+ "displayName": "uni-card 卡片",
+ "version": "1.3.1",
+ "description": "Card 组件,提供常见的卡片样式。",
+ "card",
+ "卡片"
+ "dependencies": [
+ "uni-icons",
+ "uni-scss"
+## Card 卡片
+> **组件名:uni-card**
+> 代码块: `uCard`
+卡片视图组件。
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)
@@ -0,0 +1,36 @@
+## 1.4.3(2022-01-25)
+- 修复 初始化的时候 ,open 属性失效的bug
+## 1.4.2(2022-01-21)
+- 修复 微信小程序resize后组件收起的bug
+## 1.4.1(2021-11-22)
+- 修复 vue3中个别scss变量无法找到的问题
+## 1.4.0(2021-11-19)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-collapse](https://uniapp.dcloud.io/component/uniui/uni-collapse)
+## 1.3.3(2021-08-17)
+- 优化 show-arrow 属性默认为true
+## 1.3.2(2021-08-17)
+- 新增 show-arrow 属性,控制是否显示右侧箭头
+## 1.3.1(2021-07-30)
+- 优化 vue3下小程序事件警告的问题
+## 1.3.0(2021-07-30)
+## 1.2.2(2021-07-21)
+- 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug
+## 1.2.1(2021-07-21)
+- 优化 组件示例
+## 1.2.0(2021-07-21)
+- 新增 组件折叠动画
+- 新增 value\v-model 属性 ,动态修改面板折叠状态
+- 新增 title 插槽 ,可定义面板标题
+- 新增 border 属性 ,显示隐藏面板内容分隔线
+- 新增 title-border 属性 ,显示隐藏面板标题分隔线
+- 修复 resize 方法失效的Bug
+- 修复 change 事件返回参数不正确的Bug
+- 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法
+## 1.1.6(2021-02-05)
+- 优化 组件引用关系,通过uni_modules引用组件
+## 1.1.5(2021-02-05)
@@ -0,0 +1,402 @@
+ <view class="uni-collapse-item">
+ <!-- onClick(!isOpen) -->
+ <view @click="onClick(!isOpen)" class="uni-collapse-item__title"
+ :class="{'is-open':isOpen &&titleBorder === 'auto' ,'uni-collapse-item-border':titleBorder !== 'none'}">
+ <view class="uni-collapse-item__title-wrap">
+ <view class="uni-collapse-item__title-box" :class="{'is-disabled':disabled}">
+ <image v-if="thumb" :src="thumb" class="uni-collapse-item__title-img" />
+ <text class="uni-collapse-item__title-text">{{ title }}</text>
+ <view v-if="showArrow"
+ :class="{ 'uni-collapse-item__title-arrow-active': isOpen, 'uni-collapse-item--animation': showAnimation === true }"
+ class="uni-collapse-item__title-arrow">
+ <uni-icons :color="disabled?'#ddd':'#bbb'" size="14" type="bottom" />
+ <view class="uni-collapse-item__wrap" :class="{'is--transition':showAnimation}"
+ :style="{height: (isOpen?height:0) +'px'}">
+ <view :id="elId" ref="collapse--hook" class="uni-collapse-item__wrap-content"
+ :class="{open:isheight,'uni-collapse-item--border':border&&isOpen}">
+ // #ifdef APP-NVUE
+ const dom = weex.requireModule('dom')
+ // #endif
+ * CollapseItem 折叠面板子组件
+ * @description 折叠面板子组件
+ * @property {String} thumb 标题左侧缩略图
+ * @property {String} name 唯一标志符
+ * @property {Boolean} open = [true|false] 是否展开组件
+ * @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
+ * @property {Boolean} border = [true|false] 是否显示分隔线
+ * @property {Boolean} disabled = [true|false] 是否展开面板
+ * @property {Boolean} showAnimation = [true|false] 开启动画
+ * @property {Boolean} showArrow = [true|false] 是否显示右侧箭头
+ name: 'uniCollapseItem',
+ // 列表标题
+ name: {
+ type: [Number, String],
+ // 是否禁用
+ disabled: {
+ // #ifdef APP-PLUS
+ // 是否显示动画,app 端默认不开启动画,卡顿严重
+ showAnimation: {
+ // #ifndef APP-PLUS
+ // 是否显示动画
+ // 是否展开
+ open: {
+ // 缩略图
+ thumb: {
+ // 标题分隔线显示类型
+ titleBorder: {
+ default: 'auto'
+ showArrow: {
+ // TODO 随机生生元素ID,解决百度小程序获取同一个元素位置信息的bug
+ const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
+ isOpen: false,
+ isheight: null,
+ height: 0,
+ elId,
+ nameSync: 0
+ open(val) {
+ this.isOpen = val
+ this.onClick(val, 'init')
+ updated(e) {
+ this.init(true)
+ this.collapse = this.getCollapse()
+ this.oldHeight = 0
+ this.onClick(this.open, 'init')
+ // #ifndef VUE3
+ // TODO vue2
+ destroyed() {
+ if (this.__isUnmounted) return
+ this.uninstall()
+ // #ifdef VUE3
+ // TODO vue3
+ unmounted() {
+ this.__isUnmounted = true
+ mounted() {
+ if (!this.collapse) return
+ if (this.name !== '') {
+ this.nameSync = this.name
+ this.nameSync = this.collapse.childrens.length + ''
+ if (this.collapse.names.indexOf(this.nameSync) === -1) {
+ this.collapse.names.push(this.nameSync)
+ console.warn(`name 值 ${this.nameSync} 重复`);
+ if (this.collapse.childrens.indexOf(this) === -1) {
+ this.collapse.childrens.push(this)
+ this.init()
+ init(type) {
+ // #ifndef APP-NVUE
+ this.getCollapseHeight(type)
+ this.getNvueHwight(type)
+ uninstall() {
+ if (this.collapse) {
+ this.collapse.childrens.forEach((item, index) => {
+ if (item === this) {
+ this.collapse.childrens.splice(index, 1)
+ this.collapse.names.forEach((item, index) => {
+ if (item === this.nameSync) {
+ this.collapse.names.splice(index, 1)
+ onClick(isOpen, type) {
+ if (this.disabled) return
+ this.isOpen = isOpen
+ if (this.isOpen && this.collapse) {
+ this.collapse.setAccordion(this)
+ if (type !== 'init') {
+ this.collapse.onChange(isOpen, this)
+ getCollapseHeight(type, index = 0) {
+ const views = uni.createSelectorQuery().in(this)
+ views
+ .select(`#${this.elId}`)
+ .fields({
+ size: true
+ }, data => {
+ // TODO 百度中可能获取不到节点信息 ,需要循环获取
+ if (index >= 10) return
+ if (!data) {
+ index++
+ this.getCollapseHeight(false, index)
+ this.height = data.height + 1
+ this.height = data.height
+ this.isheight = true
+ if (type) return
+ this.onClick(this.isOpen, 'init')
+ .exec()
+ getNvueHwight(type) {
+ const result = dom.getComponentRect(this.$refs['collapse--hook'], option => {
+ if (option && option.result && option.size) {
+ this.height = option.size.height + 1
+ this.height = option.size.height
+ * 获取父元素实例
+ getCollapse(name = 'uniCollapse') {
+ let parent = this.$parent;
+ let parentName = parent.$options.name;
+ while (parentName !== name) {
+ parent = parent.$parent;
+ if (!parent) return false;
+ parentName = parent.$options.name;
+ return parent;
+ .uni-collapse-item {
+ &__title {
+ transition: border-bottom-color .3s;
+ // transition-property: border-bottom-color;
+ // transition-duration: 5s;
+ &-wrap {
+ &-box {
+ padding: 0 15px;
+ height: 48px;
+ line-height: 48px;
+ font-weight: 500;
+ outline: none;
+ &.is-disabled {
+ .uni-collapse-item__title-text {
+ &.uni-collapse-item-border {
+ border-bottom: 1px solid #ebeef5;
+ &.is-open {
+ border-bottom-color: transparent;
+ &-img {
+ height: 22px;
+ width: 22px;
+ margin-right: 10px;
+ &-text {
+ color: inherit;
+ &-arrow {
+ width: 20px;
+ transform: rotate(0deg);
+ &-active {
+ transform: rotate(-180deg);
+ &__wrap {
+ will-change: height;
+ &.is--transition {
+ // transition: all 0.3s;
+ transition-property: height, border-bottom-width;
+ &-content {
+ // transition: height 0.3s;
+ border-bottom-width: 0;
+ &.uni-collapse-item--border {
+ border-bottom-color: red;
+ border-bottom-color: #ebeef5;
+ &.open {
+ &--animation {
+ transition-timing-function: ease;
@@ -0,0 +1,147 @@
+ <view class="uni-collapse">
+ * Collapse 折叠面板
+ * @description 展示可以折叠 / 展开的内容区域
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=23
+ * @property {String|Array} value 当前激活面板改变时触发(如果是手风琴模式,参数类型为string,否则为array)
+ * @property {Boolean} accordion = [true|false] 是否开启手风琴效果是否开启手风琴效果
+ * @event {Function} change 切换面板时触发,如果是手风琴模式,返回类型为string,否则为array
+ name: 'uniCollapse',
+ emits:['change','activeItem','input','update:modelValue'],
+ value: {
+ type: [String, Array],
+ modelValue: {
+ accordion: {
+ // 是否开启手风琴效果
+ // TODO 兼容 vue2 和 vue3
+ dataValue() {
+ let value = (typeof this.value === 'string' && this.value === '') ||
+ (Array.isArray(this.value) && this.value.length === 0)
+ let modelValue = (typeof this.modelValue === 'string' && this.modelValue === '') ||
+ (Array.isArray(this.modelValue) && this.modelValue.length === 0)
+ if (value) {
+ return this.modelValue
+ if (modelValue) {
+ return this.value
+ dataValue(val) {
+ this.setOpen(val)
+ this.childrens = []
+ this.names = []
+ this.$nextTick(()=>{
+ this.setOpen(this.dataValue)
+ setOpen(val) {
+ let str = typeof val === 'string'
+ let arr = Array.isArray(val)
+ this.childrens.forEach((vm, index) => {
+ if (str) {
+ if (val === vm.nameSync) {
+ if (!this.accordion) {
+ console.warn('accordion 属性为 false ,v-model 类型应该为 array')
+ vm.isOpen = true
+ if (arr) {
+ val.forEach(v => {
+ if (v === vm.nameSync) {
+ if (this.accordion) {
+ console.warn('accordion 属性为 true ,v-model 类型应该为 string')
+ this.emit(val)
+ setAccordion(self) {
+ if (!this.accordion) return
+ if (self !== vm) {
+ vm.isOpen = false
+ resize() {
+ vm.getCollapseHeight()
+ vm.getNvueHwight()
+ onChange(isOpen, self) {
+ let activeItem = []
+ activeItem = isOpen ? self.nameSync : ''
+ if (vm.isOpen) {
+ activeItem.push(vm.nameSync)
+ this.$emit('change', activeItem)
+ this.emit(activeItem)
+ emit(val){
+ this.$emit('input', val)
+ this.$emit('update:modelValue', val)
+ .uni-collapse {
@@ -0,0 +1,89 @@
+ "id": "uni-collapse",
+ "displayName": "uni-collapse 折叠面板",
+ "version": "1.4.3",
+ "description": "Collapse 组件,可以折叠 / 展开的内容区域。",
+ "折叠",
+ "折叠面板",
+ "手风琴"
+ "uni-scss",
+ "uni-icons"
+## Collapse 折叠面板
+> **组件名:uni-collapse**
+> 代码块: `uCollapse`
+> 关联组件:`uni-collapse-item`、`uni-icons`。
+折叠面板用来折叠/显示过长的内容或者是列表。通常是在多内容分类项使用,折叠不重要的内容,显示重要内容。点击可以展开折叠部分。
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-collapse)
+## 1.0.1(2021-11-23)
+- 优化 label、label-width 属性
+## 1.0.0(2021-11-19)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-combox](https://uniapp.dcloud.io/component/uniui/uni-combox)
+## 0.1.0(2021-07-30)
+## 0.0.6(2021-05-12)
+## 0.0.5(2021-04-21)
+- 优化 添加依赖 uni-icons, 导入后自动下载依赖
+## 0.0.4(2021-02-05)
+## 0.0.3(2021-02-04)
@@ -0,0 +1,275 @@
+ <view class="uni-combox" :class="border ? '' : 'uni-combox__no-border'">
+ <view v-if="label" class="uni-combox__label" :style="labelStyle">
+ <text>{{label}}</text>
+ <view class="uni-combox__input-box">
+ <input class="uni-combox__input" type="text" :placeholder="placeholder"
+ placeholder-class="uni-combox__input-plac" v-model="inputVal" @input="onInput" @focus="onFocus"
+@blur="onBlur" />
+ <uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" @click="toggleSelector">
+ </uni-icons>
+ <view class="uni-combox__selector" v-if="showSelector">
+ <view class="uni-popper__arrow"></view>
+ <scroll-view scroll-y="true" class="uni-combox__selector-scroll">
+ <view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
+ <text>{{emptyTips}}</text>
+ <view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index"
+ @click="onSelectorClick(index)">
+ <text>{{item}}</text>
+ </scroll-view>
+ * Combox 组合输入框
+ * @description 组合输入框一般用于既可以输入也可以选择的场景
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=1261
+ * @property {String} label 左侧文字
+ * @property {String} labelWidth 左侧内容宽度
+ * @property {String} placeholder 输入框占位符
+ * @property {Array} candidates 候选项列表
+ * @property {String} emptyTips 筛选结果为空时显示的文字
+ * @property {String} value 组合框的值
+ name: 'uniCombox',
+ emits: ['input', 'update:modelValue'],
+ label: {
+ labelWidth: {
+ placeholder: {
+ candidates: {
+ emptyTips: {
+ default: '无匹配项'
+ showSelector: false,
+ inputVal: ''
+ labelStyle() {
+ if (this.labelWidth === 'auto') {
+ return ""
+ return `width: ${this.labelWidth}`
+ filterCandidates() {
+ return this.candidates.filter((item) => {
+ return item.toString().indexOf(this.inputVal) > -1
+ filterCandidatesLength() {
+ return this.filterCandidates.length
+ handler(newVal) {
+ this.inputVal = newVal
+ immediate: true
+ toggleSelector() {
+ this.showSelector = !this.showSelector
+ onFocus() {
+ this.showSelector = true
+ onBlur() {
+ this.showSelector = false
+ }, 153)
+ onSelectorClick(index) {
+ this.inputVal = this.filterCandidates[index]
+ this.$emit('input', this.inputVal)
+ this.$emit('update:modelValue', this.inputVal)
+ onInput() {
+ .uni-combox {
+ border: 1px solid #DCDFE6;
+ padding: 6px 10px;
+ // border-bottom: solid 1px #DDDDDD;
+ .uni-combox__label {
+ padding-right: 10px;
+ color: #999999;
+ .uni-combox__input-box {
+ .uni-combox__input {
+ .uni-combox__input-plac {
+ .uni-combox__selector {
+ top: calc(100% + 12px);
+ border: 1px solid #EBEEF5;
+ border-radius: 6px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ padding: 4px 0;
+ .uni-combox__selector-scroll {
+ max-height: 200px;
+ .uni-combox__selector-empty,
+ .uni-combox__selector-item {
+ line-height: 36px;
+ padding: 0px 10px;
+ .uni-combox__selector-item:hover {
+ background-color: #f9f9f9;
+ .uni-combox__selector-empty:last-child,
+ .uni-combox__selector-item:last-child {
+ border-bottom: none;
+ // picker 弹出层通用的指示小三角
+ .uni-popper__arrow,
+ .uni-popper__arrow::after {
+ border-color: transparent;
+ border-width: 6px;
+ .uni-popper__arrow {
+ filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
+ top: -6px;
+ left: 10%;
+ margin-right: 3px;
+ border-top-width: 0;
+ border-bottom-color: #EBEEF5;
+ top: 1px;
+ margin-left: -6px;
+ border-bottom-color: #fff;
+ .uni-combox__no-border {
+ "id": "uni-combox",
+ "displayName": "uni-combox 组合框",
+ "version": "1.0.1",
+ "description": "可以选择也可以输入的表单项 ",
+ "combox",
+ "组合框",
+ "select"
@@ -0,0 +1,11 @@
+## Combox 组合框
+> **组件名:uni-combox**
+> 代码块: `uCombox`
+组合框组件。
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-combox)
@@ -0,0 +1,24 @@
+## 1.2.2(2022-01-19)
+- 修复 在微信小程序中样式不生效的bug
+## 1.2.1(2022-01-18)
+- 新增 update 方法 ,在动态更新时间后,刷新组件
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-countdown](https://uniapp.dcloud.io/component/uniui/uni-countdown)
+## 1.1.3(2021-10-18)
+- 重构
+- 新增 font-size 支持自定义字体大小
+## 1.1.2(2021-08-24)
+## 1.1.1(2021-07-30)
+## 1.1.0(2021-07-30)
+## 1.0.5(2021-06-18)
+- 修复 uni-countdown 重复赋值跳两秒的 bug
+## 1.0.4(2021-05-12)
+## 1.0.3(2021-05-08)
+- 修复 uni-countdown 不能控制倒计时的 bug
+## 1.0.2(2021-02-04)
+ "uni-countdown.day": "day",
+ "uni-countdown.h": "h",
+ "uni-countdown.m": "m",
+ "uni-countdown.s": "s"
+ "uni-countdown.day": "天",
+ "uni-countdown.h": "时",
+ "uni-countdown.m": "分",
+ "uni-countdown.s": "秒"
+ "uni-countdown.h": "時",
@@ -0,0 +1,271 @@
+ <view class="uni-countdown">
+ <text v-if="showDay" :style="[timeStyle]" class="uni-countdown__number">{{ d }}</text>
+ <text v-if="showDay" :style="[splitorStyle]" class="uni-countdown__splitor">{{dayText}}</text>
+ <text :style="[timeStyle]" class="uni-countdown__number">{{ h }}</text>
+ <text :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : hourText }}</text>
+ <text :style="[timeStyle]" class="uni-countdown__number">{{ i }}</text>
+ <text :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : minuteText }}</text>
+ <text :style="[timeStyle]" class="uni-countdown__number">{{ s }}</text>
+ <text v-if="!showColon" :style="[splitorStyle]" class="uni-countdown__splitor">{{secondText}}</text>
+ t
+ } = initVueI18n(messages)
+ * Countdown 倒计时
+ * @description 倒计时组件
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=25
+ * @property {String} backgroundColor 背景色
+ * @property {String} color 文字颜色
+ * @property {Number} day 天数
+ * @property {Number} hour 小时
+ * @property {Number} minute 分钟
+ * @property {Number} second 秒
+ * @property {Number} timestamp 时间戳
+ * @property {Boolean} showDay = [true|false] 是否显示天数
+ * @property {Boolean} show-colon = [true|false] 是否以冒号为分隔符
+ * @property {String} splitorColor 分割符号颜色
+ * @event {Function} timeup 倒计时时间到触发事件
+ * @example <uni-countdown :day="1" :hour="1" :minute="12" :second="40"></uni-countdown>
+ name: 'UniCountdown',
+ emits: ['timeup'],
+ showDay: {
+ showColon: {
+ start: {
+ backgroundColor: {
+ color: {
+ fontSize: {
+ default: 14
+ splitorColor: {
+ day: {
+ default: 0
+ hour: {
+ minute: {
+ second: {
+ timestamp: {
+ timer: null,
+ syncFlag: false,
+ d: '00',
+ h: '00',
+ i: '00',
+ s: '00',
+ leftTime: 0,
+ seconds: 0
+ dayText() {
+ return t("uni-countdown.day")
+ hourText(val) {
+ return t("uni-countdown.h")
+ minuteText(val) {
+ return t("uni-countdown.m")
+ secondText(val) {
+ return t("uni-countdown.s")
+ timeStyle() {
+ color,
+ backgroundColor,
+ fontSize
+ fontSize: `${fontSize}px`,
+ width: `${fontSize * 22 / 14}px`, // 按字体大小为 14px 时的比例缩放
+ lineHeight: `${fontSize * 20 / 14}px`,
+ borderRadius: `${fontSize * 3 / 14}px`,
+ splitorStyle() {
+ const { splitorColor, fontSize, backgroundColor } = this
+ color: splitorColor,
+ fontSize: `${fontSize * 12 / 14}px`,
+ margin: backgroundColor ? `${fontSize * 4 / 14}px` : ''
+ day(val) {
+ this.changeFlag()
+ hour(val) {
+ minute(val) {
+ second(val) {
+ immediate: true,
+ handler(newVal, oldVal) {
+ if (newVal) {
+ this.startData();
+ if (!oldVal) return
+ clearInterval(this.timer)
+ created: function(e) {
+ this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
+ this.countDown()
+ toSeconds(timestamp, day, hours, minutes, seconds) {
+ if (timestamp) {
+ return timestamp - parseInt(new Date().getTime() / 1000, 10)
+ return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
+ timeUp() {
+ this.$emit('timeup')
+ countDown() {
+ let seconds = this.seconds
+ let [day, hour, minute, second] = [0, 0, 0, 0]
+ if (seconds > 0) {
+ day = Math.floor(seconds / (60 * 60 * 24))
+ hour = Math.floor(seconds / (60 * 60)) - (day * 24)
+ minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
+ second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
+ this.timeUp()
+ if (day < 10) {
+ day = '0' + day
+ if (hour < 10) {
+ hour = '0' + hour
+ if (minute < 10) {
+ minute = '0' + minute
+ if (second < 10) {
+ second = '0' + second
+ this.d = day
+ this.h = hour
+ this.i = minute
+ this.s = second
+ startData() {
+ if (this.seconds <= 0) {
+ this.seconds = this.toSeconds(0, 0, 0, 0, 0)
+ this.timer = setInterval(() => {
+ this.seconds--
+ if (this.seconds < 0) {
+ }, 1000)
+ update(){
+ changeFlag() {
+ if (!this.syncFlag) {
+ this.syncFlag = true;
+ $font-size: 14px;
+ .uni-countdown {
+ &__splitor {
+ margin: 0 2px;
+ font-size: $font-size;
+ &__number {
+ border-radius: 3px;
@@ -0,0 +1,86 @@
+ "id": "uni-countdown",
+ "displayName": "uni-countdown 倒计时",
+ "version": "1.2.2",
+ "description": "CountDown 倒计时组件",
+ "countdown",
+ "倒计时"
+## CountDown 倒计时
+> **组件名:uni-countdown**
+> 代码块: `uCountDown`
+倒计时组件。
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-countdown)
+## 1.0.2(2022-06-30)
+- 优化 在 uni-forms 中的依赖注入方式
+## 1.0.1(2022-02-07)
+- 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-checkbox](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
+## 0.2.5(2021-08-23)
+- 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题
+## 0.2.4(2021-08-17)
+- 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题
+## 0.2.3(2021-08-11)
+- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
+## 0.2.2(2021-07-30)
+- 优化 在uni-forms组件,与label不对齐的问题
+## 0.2.1(2021-07-27)
+- 修复 单选默认值为0不能选中的Bug
+## 0.2.0(2021-07-13)
+## 0.1.11(2021-07-06)
+- 优化 删除无用日志
+## 0.1.10(2021-07-05)
+- 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题
+## 0.1.9(2021-07-05)
+- 修复 nvue 黑框样式问题
+## 0.1.8(2021-06-28)
+- 修复 selectedTextColor 属性不生效的Bug
+## 0.1.7(2021-06-02)
+- 新增 map 属性,可以方便映射text/value属性
+## 0.1.6(2021-05-26)
+- 修复 不关联服务空间的情况下组件报错的Bug
+## 0.1.5(2021-05-12)
+## 0.1.4(2021-04-09)
+- 修复 nvue 下无法选中的问题
+## 0.1.3(2021-03-22)
+- 新增 disabled属性
+## 0.1.2(2021-02-24)
+- 优化 默认颜色显示
+## 0.1.1(2021-02-24)
+- 新增 支持nvue
+## 0.1.0(2021-02-18)
+- “暂无数据”显示居中
@@ -0,0 +1,817 @@
+ <view class="uni-data-checklist" :style="{'margin-top':isTop+'px'}">
+ <template v-if="!isLocal">
+ <view class="uni-data-loading">
+ <uni-load-more v-if="!mixinDatacomErrorMessage" status="loading" iconType="snow" :iconSize="18" :content-text="contentText"></uni-load-more>
+ <text v-else>{{mixinDatacomErrorMessage}}</text>
+ </template>
+ <template v-else>
+ <checkbox-group v-if="multiple" class="checklist-group" :class="{'is-list':mode==='list' || wrap}" @change="chagne">
+ <label class="checklist-box" :class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
+ :style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
+ <checkbox class="hidden" hidden :disabled="disabled || !!item.disabled" :value="item[map.value]+''" :checked="item.selected" />
+ <view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="checkbox__inner" :style="item.styleIcon">
+ <view class="checkbox__inner-icon"></view>
+ <view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
+ <text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text>
+ <view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :style="item.styleBackgroud"></view>
+ </label>
+ </checkbox-group>
+ <radio-group v-else class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="chagne">
+ <!-- -->
+ <radio class="hidden" hidden :disabled="disabled || item.disabled" :value="item[map.value]+''" :checked="item.selected" />
+ <view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="radio__inner"
+ :style="item.styleBackgroud">
+ <view class="radio__inner-icon" :style="item.styleIcon"></view>
+ <view v-if="mode === 'list' && icon === 'right'" :style="item.styleRightIcon" class="checkobx__list"></view>
+ </radio-group>
+ * DataChecklist 数据选择器
+ * @description 通过数据渲染 checkbox 和 radio
+ * @property {String} mode = [default| list | button | tag] 显示模式
+ * @value default 默认横排模式
+ * @value list 列表模式
+ * @value button 按钮模式
+ * @value tag 标签模式
+ * @property {Boolean} multiple = [true|false] 是否多选
+ * @property {Array|String|Number} value 默认值
+ * @property {Array} localdata 本地数据 ,格式 [{text:'',value:''}]
+ * @property {Number|String} min 最小选择个数 ,multiple为true时生效
+ * @property {Number|String} max 最大选择个数 ,multiple为true时生效
+ * @property {Boolean} wrap 是否换行显示
+ * @property {String} icon = [left|right] list 列表模式下icon显示位置
+ * @property {Boolean} selectedColor 选中颜色
+ * @property {Boolean} emptyText 没有数据时显示的文字 ,本地数据无效
+ * @property {Boolean} selectedTextColor 选中文本颜色,如不填写则自动显示
+ * @property {Object} map 字段映射, 默认 map={text:'text',value:'value'}
+ * @value left 左侧显示
+ * @value right 右侧显示
+ * @event {Function} change 选中发生变化触发
+ name: 'uniDataChecklist',
+ mixins: [uniCloud.mixinDatacom || {}],
+ emits:['input','update:modelValue','change'],
+ mode: {
+ default: 'default'
+ multiple: {
+ type: [Array, String, Number],
+ return ''
+ default() {
+ return '';
+ localdata: {
+ min: {
+ max: {
+ wrap: {
+ icon: {
+ default: 'left'
+ selectedColor: {
+ selectedTextColor: {
+ emptyText:{
+ default: '暂无数据'
+ disabled:{
+ map:{
+ default(){
+ text:'text',
+ value:'value'
+ this.range = newVal
+ this.dataList = this.getDataList(this.getSelectedValue(newVal))
+ deep: true
+ mixinDatacomResData(newVal) {
+ value(newVal) {
+ this.dataList = this.getDataList(newVal)
+ // fix by mehaotian is_reset 在 uni-forms 中定义
+ // if(!this.is_reset){
+ // this.is_reset = false
+ // this.formItem && this.formItem.setValue(newVal)
+ modelValue(newVal) {
+ this.dataList = this.getDataList(newVal);
+ dataList: [],
+ range: [],
+ contentText: {
+ contentdown: '查看更多',
+ contentrefresh: '加载中',
+ contentnomore: '没有更多'
+ isLocal:true,
+ styles: {
+ selectedColor: '#2979ff',
+ selectedTextColor: '#666',
+ isTop:0
+ dataValue(){
+ if(this.value === '')return this.modelValue
+ if(this.modelValue === '') return this.value
+ // this.form = this.getForm('uniForms')
+ // this.formItem = this.getForm('uniFormsItem')
+ // this.formItem && this.formItem.setValue(this.value)
+ // if (this.formItem) {
+ // this.isTop = 6
+ // if (this.formItem.name) {
+ // // 如果存在name添加默认值,否则formData 中不存在这个字段不校验
+ // this.formItem.setValue(this.dataValue)
+ // this.rename = this.formItem.name
+ // this.form.inputChildrens.push(this)
+ if (this.localdata && this.localdata.length !== 0) {
+ this.isLocal = true
+ this.range = this.localdata
+ this.dataList = this.getDataList(this.getSelectedValue(this.range))
+ if (this.collection) {
+ this.isLocal = false
+ this.loadData()
+ loadData() {
+ this.mixinDatacomGet().then(res=>{
+ this.mixinDatacomResData = res.result.data
+ if(this.mixinDatacomResData.length === 0){
+ this.mixinDatacomErrorMessage = this.emptyText
+ }).catch(err=>{
+ this.mixinDatacomErrorMessage = err.message
+ getForm(name = 'uniForms') {
+ if (!parent) return false
+ chagne(e) {
+ const values = e.detail.value
+ let detail = {
+ value: [],
+ if (this.multiple) {
+ this.range.forEach(item => {
+ if (values.includes(item[this.map.value] + '')) {
+ detail.value.push(item[this.map.value])
+ detail.data.push(item)
+ const range = this.range.find(item => (item[this.map.value] + '') === values)
+ if (range) {
+ detail = {
+ value: range[this.map.value],
+ data: range
+ // this.formItem && this.formItem.setValue(detail.value)
+ // TODO 兼容 vue2
+ this.$emit('input', detail.value);
+ // // TOTO 兼容 vue3
+ this.$emit('update:modelValue', detail.value);
+ this.$emit('change', {
+ detail
+ // 如果 v-model 没有绑定 ,则走内部逻辑
+ // if (this.value.length === 0) {
+ this.dataList = this.getDataList(detail.value, true)
+ this.dataList = this.getDataList(detail.value)
+ * 获取渲染的新数组
+ * @param {Object} value 选中内容
+ getDataList(value) {
+ // 解除引用关系,破坏原引用关系,避免污染源数据
+ let dataList = JSON.parse(JSON.stringify(this.range))
+ let list = []
+ if (!Array.isArray(value)) {
+ value = []
+ dataList.forEach((item, index) => {
+ item.disabled = item.disable || item.disabled || false
+ if (value.length > 0) {
+ let have = value.find(val => val === item[this.map.value])
+ item.selected = have !== undefined
+ item.selected = false
+ item.selected = value === item[this.map.value]
+ list.push(item)
+ return this.setRange(list)
+ * 处理最大最小值
+ * @param {Object} list
+ setRange(list) {
+ let selectList = list.filter(item => item.selected)
+ let min = Number(this.min) || 0
+ let max = Number(this.max) || ''
+ list.forEach((item, index) => {
+ if (selectList.length <= min) {
+ let have = selectList.find(val => val[this.map.value] === item[this.map.value])
+ if (have !== undefined) {
+ item.disabled = true
+ if (selectList.length >= max && max !== '') {
+ if (have === undefined) {
+ this.setStyles(item, index)
+ list[index] = item
+ return list
+ * 设置 class
+ * @param {Object} item
+ * @param {Object} index
+ setStyles(item, index) {
+ // 设置自定义样式
+ item.styleBackgroud = this.setStyleBackgroud(item)
+ item.styleIcon = this.setStyleIcon(item)
+ item.styleIconText = this.setStyleIconText(item)
+ item.styleRightIcon = this.setStyleRightIcon(item)
+ * 获取选中值
+ * @param {Object} range
+ getSelectedValue(range) {
+ if (!this.multiple) return this.dataValue
+ let selectedArr = []
+ range.forEach((item) => {
+ if (item.selected) {
+ selectedArr.push(item[this.map.value])
+ return this.dataValue.length > 0 ? this.dataValue : selectedArr
+ * 设置背景样式
+ setStyleBackgroud(item) {
+ let styles = {}
+ let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
+ if (this.mode !== 'list') {
+ styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
+ if (this.mode === 'tag') {
+ styles['background-color'] = item.selected? selectedColor:'#f5f5f5'
+ let classles = ''
+ for (let i in styles) {
+ classles += `${i}:${styles[i]};`
+ return classles
+ setStyleIcon(item) {
+ styles['background-color'] = item.selected?selectedColor:'#fff'
+ if(!item.selected && item.disabled){
+ styles['background-color'] = '#F2F6FC'
+ setStyleIconText(item) {
+ styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:'#fff'):'#666'
+ styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:selectedColor):'#666'
+ styles.color = '#999'
+ setStyleRightIcon(item) {
+ if (this.mode === 'list') {
+ styles['border-color'] = item.selected?this.styles.selectedColor:'#DCDFE6'
+ $checked-color: #2979ff;
+ $border-color: #DCDFE6;
+ $disable:0.4;
+ @mixin flex {
+ .uni-data-loading {
+ @include flex;
+ height: 36px;
+ .uni-data-checklist {
+ // 多选样式
+ .checklist-group {
+ &.is-list {
+ .checklist-box {
+ margin: 5px 0;
+ margin-right: 25px;
+ .hidden {
+ // 文字样式
+ .checklist-content {
+ .checklist-text {
+ color: #666;
+ margin-left: 5px;
+ line-height: 14px;
+ .checkobx__list {
+ border-right-width: 1px;
+ border-right-color: #007aff;
+ border-right-style: solid;
+ border-bottom-width:1px;
+ border-bottom-color: #007aff;
+ width: 6px;
+ left: -5px;
+ transform-origin: center;
+ .checkbox__inner {
+ flex-shrink: 0;
+ width: 16px;
+ height: 16px;
+ border: 1px solid $border-color;
+ .checkbox__inner-icon {
+ top: 2px;
+ left: 5px;
+ border-right-color: #fff;
+ border-bottom-width:1px ;
+ transform: rotate(40deg);
+ // 单选样式
+ .radio__inner {
+ border-radius: 16px;
+ .radio__inner-icon {
+ // 默认样式
+ &.is--default {
+ // 禁用
+ &.is-disable {
+ cursor: not-allowed;
+ background-color: #F2F6FC;
+ border-color: $border-color;
+ // 选中
+ &.is-checked {
+ border-color: $checked-color;
+ background-color: $checked-color;
+ color: $checked-color;
+ // 选中禁用
+ opacity: $disable;
+ // 按钮样式
+ &.is--button {
+ padding: 5px 10px;
+ border: 1px $border-color solid;
+ transition: border-color 0.2s;
+ border: 1px #eee solid;
+ // 标签样式
+ &.is--tag {
+ // 列表样式
+ &.is--list {
+ padding: 10px 15px;
+ &.is-list-border {
+ border-top: 1px #eee solid;
@@ -0,0 +1,87 @@
+ "id": "uni-data-checkbox",
+ "displayName": "uni-data-checkbox 数据选择器",
+ "version": "1.0.2",
+ "description": "通过数据驱动的单选框和复选框",
+ "checkbox",
+ "单选",
+ "多选",
+ "单选多选"
+ "HBuilderX": "^3.1.1"
+ "dependencies": ["uni-load-more","uni-scss"],
@@ -0,0 +1,18 @@
+## DataCheckbox 数据驱动的单选复选框
+> **组件名:uni-data-checkbox**
+> 代码块: `uDataCheckbox`
+本组件是基于uni-app基础组件checkbox的封装。本组件要解决问题包括:
+1. 数据绑定型组件:给本组件绑定一个data,会自动渲染一组候选内容。再以往,开发者需要编写不少代码实现类似功能
+2. 自动的表单校验:组件绑定了data,且符合[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)组件的表单校验规范,搭配使用会自动实现表单校验
+3. 本组件合并了单选多选
+4. 本组件有若干风格选择,如普通的单选多选框、并列button风格、tag风格。开发者可以快速选择需要的风格。但作为一个封装组件,样式代码虽然不用自己写了,却会牺牲一定的样式自定义性
+在uniCloud开发中,`DB Schema`中配置了enum枚举等类型后,在web控制台的[自动生成表单](https://uniapp.dcloud.io/uniCloud/schema?id=autocode)功能中,会自动生成``uni-data-checkbox``组件并绑定好data
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
+## 1.0.7(2022-07-06)
+- 优化 pc端图标位置不正确的问题
+## 1.0.6(2022-07-05)
+- 优化 显示样式
+## 1.0.5(2022-07-04)
+- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
+## 1.0.4(2022-04-19)
+- 修复 字节小程序 本地数据无法选择下一级的Bug
+## 1.0.3(2022-02-25)
+- 修复 nvue 不支持的 v-show 的 bug
+## 1.0.2(2022-02-25)
+- 修复 由上个版本引发的map、v-model等属性不生效的bug
+- 优化 组件 UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-picker](https://uniapp.dcloud.io/component/uniui/uni-data-picker)
+## 0.4.9(2021-10-28)
+- 修复 VUE2 v-model 概率无效的 bug
+## 0.4.8(2021-10-27)
+- 修复 v-model 概率无效的 bug
+## 0.4.7(2021-10-25)
+- 新增 属性 spaceInfo 服务空间配置 HBuilderX 3.2.11+
+- 修复 树型 uniCloud 数据类型为 int 时报错的 bug
+## 0.4.6(2021-10-19)
+- 修复 非 VUE3 v-model 为 0 时无法选中的 bug
+## 0.4.5(2021-09-26)
+- 新增 清除已选项的功能(通过 clearIcon 属性配置是否显示按钮),同时提供 clear 方法以供调用,二者等效
+- 修复 readonly 为 true 时报错的 bug
+## 0.4.4(2021-09-26)
+- 修复 上一版本造成的 map 属性失效的 bug
+- 新增 ellipsis 属性,支持配置 tab 选项长度过长时是否自动省略
+## 0.4.3(2021-09-24)
+- 修复 某些情况下级联未触发的 bug
+## 0.4.2(2021-09-23)
+- 新增 提供 show 和 hide 方法,开发者可以通过 ref 调用
+- 新增 选项内容过长自动添加省略号
+## 0.4.1(2021-09-15)
+- 新增 map 属性 字段映射,将 text/value 映射到数据中的其他字段
+## 0.4.0(2021-07-13)
+- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 0.3.5(2021-06-04)
+- 修复 无法加载云端数据的问题
+## 0.3.4(2021-05-28)
+- 修复 v-model 无效问题
+- 修复 loaddata 为空数据组时加载时间过长问题
+- 修复 上个版本引出的本地数据无法选择带有 children 的 2 级节点
+## 0.3.3(2021-05-12)
+## 0.3.2(2021-04-22)
+- 修复 非树形数据有 where 属性查询报错的问题
+## 0.3.1(2021-04-15)
+- 修复 本地数据概率无法回显时问题
+## 0.3.0(2021-04-07)
+- 新增 支持云端非树形表结构数据
+- 修复 根节点 parent_field 字段等于 null 时选择界面错乱问题
+## 0.2.0(2021-03-15)
+- 修复 nodeclick、popupopened、popupclosed 事件无法触发的问题
+## 0.1.9(2021-03-09)
+- 修复 微信小程序某些情况下无法选择的问题
+## 0.1.8(2021-02-05)
+- 优化 部分样式在 nvue 上的兼容表现
+## 0.1.7(2021-02-05)
+- 调整为 uni_modules 目录规范
@@ -0,0 +1,45 @@
+// #ifdef H5
+ name: 'Keypress',
+ disable: {
+ mounted () {
+ const keyNames = {
+ esc: ['Esc', 'Escape'],
+ tab: 'Tab',
+ enter: 'Enter',
+ space: [' ', 'Spacebar'],
+ up: ['Up', 'ArrowUp'],
+ left: ['Left', 'ArrowLeft'],
+ right: ['Right', 'ArrowRight'],
+ down: ['Down', 'ArrowDown'],
+ delete: ['Backspace', 'Delete', 'Del']
+ const listener = ($event) => {
+ if (this.disable) {
+ const keyName = Object.keys(keyNames).find(key => {
+ const keyName = $event.key
+ const value = keyNames[key]
+ return value === keyName || (Array.isArray(value) && value.includes(keyName))
+ if (keyName) {
+ // 避免和其他按键事件冲突
+ this.$emit(keyName, {})
+ }, 0)
+ document.addEventListener('keyup', listener)
+ this.$once('hook:beforeDestroy', () => {
+ document.removeEventListener('keyup', listener)
+ render: () => {}
+// #endif