@@ -0,0 +1,11 @@
+[*]
+#缩进风格:空格
+indent_style = tab
+#缩进大小2
+indent_size = 4
+#换行符lf
+end_of_line = lf
+#字符集utf-8
+charset = utf-8
+
@@ -0,0 +1,4 @@
+unpackage
+node_modules
+coverage
+static/common/*
@@ -0,0 +1,27 @@
+module.exports = {
+ extends: ['airbnb'],
+ rules: {
+ // 关闭严格模式的提示
+ strict: 0,
+ // 强制4个空格缩进
+ indent: ['error', 4],
+ // 不允许出现分号
+ semi: ['error', 'always'],
+ // 是否检查连续等号赋值
+ 'no-multi-assign': 0,
+ // 要求 require() 出现在顶层模块作用域中,禁止它
+ 'global-require': 0,
+ // 允许console,覆盖来自airbnb的规则
+ 'no-console': 'off',
+ 'no-underscore-dangle': 'off',
+ 'prefer-rest-params': 'off',
+ // 禁用对象最后一个属性的逗号
+ 'comma-dangle': ['error', 'never'],
+ // 可以使用未定义的变量,因为会全局引用uni对象
+ 'no-undef': 'off',
+ // 允许lf和crlf文件行尾
+ 'linebreak-style': 'off',
+ // 关闭function的jsdoc校验
+ 'jsdoc/check-tag-names': 'off'
+ }
+}
@@ -0,0 +1,25 @@
+## bug所属区域(vue页面,nvue页面,文档)
+## 简单描述
+## 问题截图
+## 代码示例
+## uView版本号
+## 测试平台(h5,android,ios,微信小程序,其他小程序)
+## 备注
+或者使用下面的链接创建 issue 以帮助我们更快的排查问题,不规范的 issue 会被关闭,感谢配合。
+https://new-issue.uviewui.com/
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: 创建一个新issue
+ url: https://new-issue.uviewui.com/
+ about: 请使用接下来的链接创建新issue。
+ - name: Create new issue
+ url: https://new-issue.uviewui.com/?lang=en
+ about: Please use the following link to create a new issue.
@@ -0,0 +1,10 @@
+/unpackage/dist/build/*
+/unpackage/dist/dev/*
+/unpackage/cache/*
+/unpackage/release/*
+/node_modules/*
+/.idea/*
+deploy.sh
+/.hbuilderx/
+/.vscode/
+deploy
@@ -0,0 +1,20 @@
+<script>
+ export default {
+ onLaunch: function() {
+ },
+ onShow: function() {
+ onHide: function() {
+</script>
+<style lang="scss">
+ /*每个页面公共css */
+ @import '@/uni_modules/windi-css-uniapp/index.scss';
+ @import "@/uni_modules/uview-ui/index.scss";
+ @import "common/demo.scss";
+</style>
@@ -0,0 +1,21 @@
+MIT License
+Copyright (c) 2023 www.uviewui.com
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
@@ -0,0 +1,7 @@
+const { http } = uni.$u
+// 获取菜单
+export const wxLogin = (params, config = {}) => http.get('api/show/wx/login', {params}, config)
+export const userInfo = (params, config = {}) => http.get('api/wx/userInfo', {params}, config)
+export const logout = (params, config = {}) => http.get('api/wx/logout', {params}, config)
+export const updateWxUser = (params, config = {}) => http.post('api/wx/updateWxUser', params, config)
+export const fetchMenu = (params, config = {}) => http.post('/ebapi/public_api/index', params, config)
@@ -0,0 +1,3 @@
+ baseUrl: 'https://sit-nanhuacs.4dage.com/'
@@ -0,0 +1,50 @@
+.u-block{
+ padding: 14px;
+ &__section{
+ margin-bottom:10px;
+ &__title {
+ margin-top:10px;
+ font-size: 15px;
+ color: $u-content-color;
+ &__flex{
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+// 使用了cell组件的icon图片样式
+.u-cell-icon {
+ width: 36rpx;
+ height: 36rpx;
+ margin-right: 8rpx;
+.mypopup {
+.w-full {
+ width: 100%;
+.u-page {
+ padding: 15px 15px 40px 15px;
+.u-demo-block {
+ flex: 1;
+ margin-bottom: 23px;
+ &__content {
+ @include flex(column);
+ font-size: 14px;
+ color: rgb(143, 156, 162);
+ margin-bottom: 8px;
+ @include flex;
+export default {
+ data() {
+ return {
@@ -0,0 +1,2 @@
+uni.$u.props.gap.bgColor = '#f3f4f6'
+uni.$u.props.gap.height = '10'
@@ -0,0 +1,112 @@
+<template>
+ <view style="height: 750rpx">
+ <l-echart ref="chart"></l-echart>
+ </view>
+</template>
+ import * as echarts from "./echarts.min.js";
+ mounted() {
+ this.$refs.chart.init(echarts,async chart => {
+ chart.showLoading()
+ const data = await this.getData()
+ chart.hideLoading()
+ echarts.registerMap('HK', data);
+ const option = {
+ title: {
+ text: 'Population Density of Hong Kong (2011)',
+ sublink:
+ 'http://zh.wikipedia.org/wiki/%E9%A6%99%E6%B8%AF%E8%A1%8C%E6%94%BF%E5%8D%80%E5%8A%83#cite_note-12'
+ tooltip: {
+ trigger: 'item',
+ formatter: '{b}<br/>{c} (p / km2)'
+ visualMap: {
+ min: 800,
+ max: 50000,
+ text: ['High', 'Low'],
+ realtime: false,
+ calculable: true,
+ inRange: {
+ color: ['lightskyblue', 'yellow', 'orangered']
+ series: [
+ {
+ name: '香港18区人口密度',
+ type: 'map',
+ map: 'HK',
+ label: {
+ show: true
+ data: [
+ { name: '中西区', value: 20057.34 },
+ { name: '湾仔', value: 15477.48 },
+ { name: '东区', value: 31686.1 },
+ { name: '南区', value: 6992.6 },
+ { name: '油尖旺', value: 44045.49 },
+ { name: '深水埗', value: 40689.64 },
+ { name: '九龙城', value: 37659.78 },
+ { name: '黄大仙', value: 45180.97 },
+ { name: '观塘', value: 55204.26 },
+ { name: '葵青', value: 21900.9 },
+ { name: '荃湾', value: 4918.26 },
+ { name: '屯门', value: 5881.84 },
+ { name: '元朗', value: 4178.01 },
+ { name: '北区', value: 2227.92 },
+ { name: '大埔', value: 2180.98 },
+ { name: '沙田', value: 9172.94 },
+ { name: '西贡', value: 3368 },
+ { name: '离岛', value: 806.98 }
+ ],
+ // 自定义名称映射
+ nameMap: {
+ 'Central and Western': '中西区',
+ Eastern: '东区',
+ Islands: '离岛',
+ 'Kowloon City': '九龙城',
+ 'Kwai Tsing': '葵青',
+ 'Kwun Tong': '观塘',
+ North: '北区',
+ 'Sai Kung': '西贡',
+ 'Sha Tin': '沙田',
+ 'Sham Shui Po': '深水埗',
+ Southern: '南区',
+ 'Tai Po': '大埔',
+ 'Tsuen Wan': '荃湾',
+ 'Tuen Mun': '屯门',
+ 'Wan Chai': '湾仔',
+ 'Wong Tai Sin': '黄大仙',
+ 'Yau Tsim Mong': '油尖旺',
+ 'Yuen Long': '元朗'
+ ]
+ chart.setOption(option);
+ })
+ methods: {
+ getData() {
+ return new Promise(resolve => {
+ uni.request({
+ url: 'https://fastly.jsdelivr.net/gh/apache/echarts-website@asf-site/examples/data/asset/geo/HK.json',
+ success(res) {
+ setTimeout(() => {
+ resolve(res.data);
+ }, 2000)
+ });
@@ -0,0 +1,228 @@
+ <view
+ style="
+ width: 500px;
+ height: 310px;
+ position: relative;
+ top: 170px;
+ left: -30px;
+ "
+ class="map"
+ >
+ <!-- -->
+ <l-echart style="width: 100%; height: 100%" ref="chart"></l-echart>
+import * as echarts from "./echarts.min.js";
+import china from "./china.json"; // 引入中国地图数据
+ mapData: [
+ name: "新疆维吾尔自治区",
+ value: 0,
+ name: "西藏自治区",
+ name: "内蒙古自治区",
+ name: "青海省",
+ name: "四川省",
+ name: "黑龙江省",
+ name: "甘肃省",
+ name: "云南省",
+ value: 4,
+ name: "广西壮族自治区",
+ value: 40,
+ name: "湖南省",
+ value: 89,
+ name: "陕西省",
+ name: "广东省",
+ value: 54,
+ name: "吉林省",
+ value: 15,
+ name: "河北省",
+ value: 81,
+ name: "湖北省",
+ value: 82,
+ name: "贵州省",
+ value: 32,
+ name: "山东省",
+ value: 11,
+ name: "江西省",
+ value: 64,
+ name: "河南省",
+ value: 38,
+ name: "辽宁省",
+ name: "山西省",
+ value: 50,
+ name: "安徽省",
+ value: 45,
+ name: "福建省",
+ value: 76,
+ name: "浙江省",
+ value: 30,
+ name: "江苏省",
+ value: 9,
+ name: "重庆市",
+ value: 91,
+ name: "宁夏回族自治区",
+ value: 17,
+ name: "海南省",
+ value: 27,
+ name: "台湾省",
+ value: 79,
+ name: "北京市",
+ name: "天津市",
+ value: 33,
+ name: "上海市",
+ value: 69,
+ name: "香港特别行政区",
+ value: 23,
+ name: "澳门特别行政区",
+ value: 37,
+ };
+ this.$refs.chart.init(echarts, async (chart) => {
+ chart.showLoading();
+ // const data = await this.getData();
+ chart.hideLoading();
+ console.log("china", china);
+ echarts.registerMap("China", china); // 注册中国地图
+ // echarts.registerMap('HK', data);
+ const option = this.getChartOption();
+ getChartOption() {
+ text: "",
+ trigger: "item",
+ // formatter: "{b}<br/>{c} (p / km2)",
+ min: 0,
+ left: 100,
+ max: 300,
+ show: false,
+ text: ["High", "Low"],
+ pieces: [
+ gt: 20,
+ label: "疑似",
+ color: "yellow",
+ lt: 20,
+ color: "transparent",
+ color: ["transparent", "yellow", "yellow"],
+ name: "祈愿人数分布",
+ type: "map",
+ map: "China",
+ // selectedMode: "multiple",
+ data: this.mapData,
+ itemStyle: {
+ normal: {
+ borderColor: "transparent",
+ console.log("option", option);
+ return option;
+};
@@ -0,0 +1,113 @@
+ <view class="nav-wrap">
+ <view class="nav-title">
+ <u--image :showLoading="true" src="https://cdn.uviewui.com/uview/common/logo.png" width="70px"
+ height="70px" />
+ <view class="nav-info">
+ <view class="nav-info__title" @tap="jumpToWx">
+ <text class="nav-info__title__text">uView {{version}}</text>
+ <!-- #ifdef MP-WEIXIN -->
+ <!-- uni-app不支持text内部的text组件的tap事件,所以放到外部响应tap -->
+ <text class="nav-info__title__jump">查看1.x演示</text>
+ <!-- #endif -->
+ <text class="nav-slogan">多平台快速开发的UI框架</text>
+ <text class="nav-desc">{{desc}}</text>
+ props: {
+ desc: String,
+ title: String,
+ version: uni.$u.config.v
+ jumpToWx() {
+ // #ifdef MP-WEIXIN
+ uni.navigateToMiniProgram({
+ appId: 'wxc6bdb80f216e918c'
+ // #endif
+<style lang="scss" scoped>
+ .nav-wrap {
+ padding: 15px;
+ .lang {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ .nav-title {
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+ .nav-info {
+ margin-left: 15px;
+ &__text {
+ color: $u-main-color;
+ font-size: 25px;
+ font-weight: bold;
+ text-align: left;
+ &__jump {
+ font-size: 12px;
+ color: $u-primary;
+ font-weight: normal;
+ margin-left: 20px;
+ .logo {
+ width: 70px;
+ height: 70px;
+ height: auto;
+ .nav-slogan {
+ color: $u-tips-color;
+ .nav-desc {
+ margin-top: 10px;
+ line-height: 20px;
@@ -0,0 +1,146 @@
+ <canvas @click="onClick()" :style="{ width: `${size}px`, height: `${size}px` }" :canvas-id="id" :id="id" type="2d" :hidpi="false"></canvas>
+/**
+ * Qrcode 显示二维码
+ * @description 二维码生成的组件
+ * @property {String} id 多个时需设置不同id
+ * @property {String} text 二维码内容
+ * @property {Number} size 二维码大小
+ * @property {String} background 背景颜色
+ * @property {String} foreground 二维码颜色
+ * @property {String} logo 中间logo资源
+ * @property {String} logoSize logo大小
+ * @property {Boolean} logoRound logo圆角
+ *
+ * @event {Function} click 点击 Qrcode 触发事件
+ * @example <QiyanQrcode text="321"></QiyanQrcode>
+ */
+import drawQrcode from "./utils.js";
+let canvasObj = {};
+ name: 'QiyanQrcode',
+ emits: ['click'],
+ id: {
+ type: String,
+ default: "qrCanvas",
+ text: {
+ default: "",
+ size: {
+ type: Number,
+ default: 200,
+ background: {
+ default: "#ffffff",
+ foreground: {
+ default: "#000000",
+ logo: {
+ logoSize: {
+ default: 60,
+ logoRound: {
+ type: Boolean,
+ default: false,
+ watch: {
+ text(nVal) {
+ if (nVal) {
+ this.draw();
+ } else {
+ console.error('qrcode text is null');
+ this.$nextTick(() => {
+ let _this = this;
+ // #ifdef H5
+ const canvas = document.getElementById(this.id).childNodes[0]
+ canvasObj[this.id] = canvas;
+ _this.draw();
+ // #ifndef H5
+ const query = uni.createSelectorQuery().in(this);
+ query.select("#" + this.id).fields({
+ node: true,
+ size: true,
+ (data) => {
+ canvasObj[this.id] = data.node;
+ )
+ .exec();
+ beforeUnmount() {
+ delete canvasObj[this.id];
+ draw() {
+ if (this.text && canvasObj[this.id]) {
+ let canvas = canvasObj[this.id];
+ const drawText = (imageResource) => {
+ let image = {};
+ if (imageResource) {
+ image = {
+ imageResource,
+ width: this.logoSize,
+ height: this.logoSize,
+ round: this.logoRound
+ drawQrcode({
+ canvas,
+ canvasId: this.id,
+ width: this.size,
+ height: this.size,
+ padding: 0,
+ background: this.background,
+ foreground: this.foreground,
+ text: this.text,
+ image
+ if (this.logo) {
+ let img;
+ img = new Image();
+ img = canvas.createImage();
+ img.src = this.logo;
+ img.onload = () => {
+ drawText(img);
+ drawText();
+ }, 100);
+ onClick() {
+ this.$emit('click');
@@ -0,0 +1,145 @@
+// import extend from 'extend'
+// import { extend } from "@/utils/lodash";
+import {
+ QRCode,
+ QRErrorCorrectLevel
+} from './qrcode.js'
+// support Chinese
+function utf16to8(str) {
+ var out, i, len, c
+ out = ''
+ len = str.length
+ for (i = 0; i < len; i++) {
+ c = str.charCodeAt(i)
+ if ((c >= 0x0001) && (c <= 0x007F)) {
+ out += str.charAt(i)
+ } else if (c > 0x07FF) {
+ out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F))
+ out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F))
+ out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F))
+ out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F))
+ return out
+function drawQrcode(options, debug) {
+ options = options || {}
+ options = Object.assign(true, {
+ canvasId: 'myQrcode',
+ // canvas: canvas,
+ text: '爱一个人就要勇敢说出来',
+ width: 260,
+ height: 260,
+ padding: 20,
+ // paddingColor: '#ffffff', // 默认与background一致
+ typeNumber: -1,
+ correctLevel: QRErrorCorrectLevel.H,
+ background: '#ffffff',
+ foreground: '#000000',
+ image: {
+ imageResource: '',
+ width: 80,
+ height: 80,
+ round: true
+ }, options)
+ if (!options.canvasId && !options.canvas) {
+ console.warn('please set canvasId or canvas!')
+ return
+ if (!options.paddingColor) options.paddingColor = options.background
+ if (debug) {
+ var qrcode = new QRCode(options.typeNumber, options.correctLevel)
+ qrcode.addData(utf16to8(options.text))
+ qrcode.make()
+ return new Promise(function (resolve, reject) {
+ resolve(qrcode);
+ return resolve(createCanvas());
+ function createCanvas() {
+ // create the qrcode itself
+ var dpr = 1
+ dpr = wx.getSystemInfoSync().pixelRatio
+ var canvas = options.canvas
+ const ctx = canvas.getContext('2d')
+ canvas.width = options.width * dpr
+ canvas.height = options.width * dpr
+ const width = canvas.width
+ // 背景色
+ ctx.fillStyle = options.paddingColor
+ ctx.fillRect(0, 0, width + options.padding * 2, width + options.padding * 2);
+ var tileW = (width - options.padding * 2) / qrcode.getModuleCount()
+ var tileH = (width - options.padding * 2) / qrcode.getModuleCount()
+ // 开始画二维码
+ for (var row = 0; row < qrcode.getModuleCount(); row++) {
+ for (var col = 0; col < qrcode.getModuleCount(); col++) {
+ ctx.fillStyle = qrcode.isDark(row, col) ? options.foreground : options.background
+ var w = (Math.ceil((col + 1) * tileW) - Math.floor(col * tileW))
+ var h = (Math.ceil((row + 1) * tileW) - Math.floor(row * tileW))
+ ctx.fillRect(Math.round(col * tileW) + options.padding, Math.round(row * tileH) + options.padding, w, h);
+ if (options.image.imageResource) {
+ const imgW = options.image.width * dpr
+ const imgH = options.image.height * dpr
+ const dx = (width - imgW) / 2
+ const dy = (width - imgH) / 2
+ if (options.image.round) {
+ // Logo边框
+ const imgW2 = options.image.width * dpr + 5
+ const dx2 = (width - imgW2) / 2
+ const r2 = imgW2 / 2
+ const cx2 = dx2 + r2;
+ ctx.beginPath();
+ ctx.arc(cx2, cx2, r2, 0, 2 * Math.PI);
+ ctx.fillStyle = '#ffffff'
+ ctx.fill();
+ ctx.restore();
+ // 画Logo
+ const r = imgW / 2
+ const cx = dx + r;
+ const cy = dy + r;
+ ctx.arc(cx, cy, r, 0, 2 * Math.PI);
+ ctx.clip();
+ ctx.drawImage(options.image.imageResource, dx, dy, imgW, imgW);
+ ctx.drawImage(options.image.imageResource, dx, dy, imgW, imgH)
+ return ctx
+export default drawQrcode
@@ -0,0 +1,373 @@
+ <div>
+ <view class="tabbar">
+ <div class="tabbarbg">
+ <u--image
+ src="/static/img/menu_light@2x.png"
+ width="100vw"
+ height="81px"
+ ></u--image>
+ </div>
+ class="tabbarItem"
+ @click="handleItem(item, index)"
+ :class="{ activeMenu: active == index }"
+ v-for="(item, index) in list"
+ :key="index"
+ <div :class="{ aotoImg: index == 2 }">
+ :src="active == index ? item.active : item.light"
+ :width="index == 2 ? '50px' : '48px'"
+ :height="index == 2 ? '50px' : '48px'"
+ <div class="hightImg" v-show="active == index">
+ src="https://4dscene.4dage.com/new4dkk/deng/static/img/icon_active@2x.png"
+ width="68px"
+ <view class="tabbarItemText" :class="{ active: active == index }">{{
+ item.name
+ }}</view>
+ <u-popup
+ closeIconPos="bottom-right"
+ :show="show"
+ mode="center"
+ round="10"
+ bgColor="transparent"
+ @close="close"
+ @open="open"
+ <view class="codeData" :style="{backgroundImage: `url(${codeImg})`}">
+ <div class="close" @click="close">
+ width="42px"
+ height="42px"
+ src="https://4dscene.4dage.com/new4dkk/deng/static/img/icon_cancel@2x.png"
+ <!-- <view class="text">
+ width="100%"
+ height="185px"
+ src="https://4dscene.4dage.com/new4dkk/v2/lang/images/solutions/government/survey1.png"
+ <div class="text-center pt-20 pb-8 text-2xl">一花五叶</div>
+ height="10px"
+ src="https://4dscene.4dage.com/new4dkk/deng/static/img/line@2x.png"
+ <div class="codeDataText">
+ 此灯组以禅宗“一花五叶”为主题,传承禅宗经典。您可欣赏花开五叶的意象,感受南宗禅衍化出临济、曹洞、法眼、沩仰和云门五家,发展壮大的辉煌历史,领悟慧能大师法脉的深邃智慧。
+ <div class="codeImg">
+ <QiyanQrcode
+ @click="title1 = '1.现在时间戳:' + Date.now()"
+ :text="title1"
+ ></QiyanQrcode>
+ <up-qrcode
+ :size="72"
+ val="uview-plus"
+ icon="https://uview-plus.jiangruyi.com/uview-plus/common/logo.png"
+ ></up-qrcode>
+ </view> -->
+ <view class="codebutList">
+ <!-- <div class="downText" v-if="false">
+ <div class="img flex justify-center items-center">
+ <div style="width: 36px; height: 36px">
+ width="36px"
+ height="36px"
+ src="https://4dscene.4dage.com/new4dkk/deng/static/img/icon_download@2x.png"
+ <div class="pl-10">下载图片</div>
+ <div class="tips">完成6处打卡,即可点灯祈愿</div>
+ </div> -->
+ <div class="downButtom flex justify-center">
+ <div class="downButtomItem">下载图片</div>
+ <div class="downButtomItem" @click="handleqiyan">去祈愿</div>
+ </u-popup>
+import { mapState } from "vuex";
+import QiyanQrcode from "@/components/qiyan-qrcode/qiyan-qrcode.vue";
+// import uButton from "uview-ui/components/u-button/u-button.vue";
+ components: {
+ QiyanQrcode,
+ type: {
+ title1: "1.现在时间戳:" + Date.now(),
+ codeImg: 'https://4dscene.4dage.com/new4dkk/deng/static/img/code/01.png',
+ codeList:{
+ code1: 'https://4dscene.4dage.com/new4dkk/deng/static/img/code/01.png',
+ code2: 'https://4dscene.4dage.com/new4dkk/deng/static/img/code/01.png',
+ code3: 'https://4dscene.4dage.com/new4dkk/deng/static/img/code/01.png',
+ code4: 'https://4dscene.4dage.com/new4dkk/deng/static/img/code/01.png',
+ code5: 'https://4dscene.4dage.com/new4dkk/deng/static/img/code/01.png',
+ code6: 'https://4dscene.4dage.com/new4dkk/deng/static/img/code/01.png',
+ code7: 'https://4dscene.4dage.com/new4dkk/deng/static/img/code/02.png',
+ code8: 'https://4dscene.4dage.com/new4dkk/deng/static/img/code/01.png',
+ code9: 'https://4dscene.4dage.com/new4dkk/deng/static/img/code/12.png',
+ list: [
+ active: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_scan_active@2x.png",
+ dark: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_scan_dark@2x.png",
+ light: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_scan_light@2x.png",
+ name: "扫码打卡",
+ active: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_lotus_active@2x.png",
+ dark: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_lotus_dark@2x.png",
+ light: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_lotus_light@2x.png",
+ name: "点亮",
+ url: "https://sit-nanhuacs.4dage.com/web/index.html#/prayers/list",
+ active: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_map_active@2x.png",
+ dark: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_map_dark@2x.png",
+ light: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_map_light@2x.png",
+ name: "地图",
+ path: "/pages/home/map",
+ url: "https://sit-nanhuacs.4dage.com/web/index.html#/map",
+ active: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_ai_active@2x.png",
+ dark: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_ai_dark@2x.png",
+ light: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_ai_light@2x.png",
+ name: "妙笔生花",
+ url: "https://sit-nanhuacs.4dage.com/web/index.html#/composite",
+ active: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_user_active@2x.png",
+ dark: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_user_dark@2x.png",
+ light: "https://4dscene.4dage.com/new4dkk/deng/static/img/icon_user_light@2x.png",
+ name: "我的",
+ title: "Hello",
+ computed: {
+ ...mapState(["active"]),
+ onLoad() {},
+ handleItem(item, index) {
+ let obj = {
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=1':this.codeList.code1,
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=2':this.codeList.code2,
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=3':this.codeList.code3,
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=4':this.codeList.code4,
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=5':this.codeList.code5,
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=6':this.codeList.code6,
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=7':this.codeList.code7,
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=8':this.codeList.code8,
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=9':this.codeList.code9,
+ let objclockIn = {
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=1':'SG-PHP2F5mD35e',
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=2':'SG-PHP2F5mD35e',
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=3':'SG-PHP2F5mD35e',
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=4':'SG-PHP2F5mD35e',
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=5':'SG-PHP2F5mD35e',
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=6':'SG-PHP2F5mD35e',
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=7':'SG-PHP2F5mD35e',
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=8':'SG-PHP2F5mD35e',
+ 'https://houseoss.4dkankan.com/project/nanHua/index.html#/code?m=9':'SG-PHP2F5mD35e',
+ let that = this;
+ if (item.name == "扫码打卡") {
+ // 只允许通过相机扫码
+ uni.scanCode({
+ onlyFromCamera: true,
+ success: function (res) {
+ console.log("条码类型:" + res.scanType);
+ console.log("条码内容:" + res.result);
+ that.show = true;
+ that.codeImg = obj[res.result];
+ that.$store.commit("changeActive", index);
+ that.getRequest('/api/wx/clockIn', {code: objclockIn[res.result]})
+ fail: function (e) {
+ console.log("扫码失败", e);
+ that.$store.commit("changeActive", 4);
+ return;
+ } else if (item.name == "我的") {
+ this.$store.commit("changeActive", index);
+ if (item.url){
+ uni.$u.route("/pages/home/webview", {
+ url: item.url,
+ index: index,
+ handleHome() {
+ console.log("开启云上观灯", uni);
+ uni.$u.route("/pages/home/home");
+ handleqiyan() {
+ url: 'https://sit-nanhuacs.4dage.com/web/index.html#/prayers/list',
+ close() {
+ this.show = false;
+ // console.log('close');
+ open() {},
+<style lang="less" scoped>
+.tabbar {
+ justify-content: space-around;
+ position: fixed;
+ bottom: 0;
+ width: 100vw;
+ z-index: 1000;
+ height: 81px;
+ left: 0;
+ padding: -2px 0 16px 0;
+ .tabbarbg {
+ right: 0;
+ .tabbarItem {
+ text-align: center;
+ width: 50px;
+ font-weight: 400;
+ font-size: 11px;
+ color: #303030;
+ line-height: 13px;
+ z-index: 1;
+ .hightImg {
+ z-index: 2;
+ top: 0;
+ left: -10px;
+ .aotoImg {
+ top: -5px;
+.codeData {
+ // background: url(https://4dscene.4dage.com/new4dkk/deng/static/img/pop2@2x.png) no-repeat cover;
+ background: url(https://4dscene.4dage.com/new4dkk/deng/static/img/code1.png)
+ 100% 100% no-repeat;
+ background-size: 100% auto;
+ width: calc(100vw - 108px);
+ height: 490px;
+ padding: 48px 52px 30px 36px;
+ .close {
+ right: -0px;
+ top: -0px;
+ width: 42px;
+ height: 42px;
+ .text {
+ font-size: 24px;
+ color: #d86332;
+ line-height: 29px;
+ padding-bottom: 72px;
+ .codeDataText {
+ padding: 8px 0 16px 0;
+ color: #b1967b;
+ line-height: 18px;
+ .codeImg {
+ text-align: right;
+ width: 72px;
+ height: 72px;
+ float: right;
+ .codebutList {
+ bottom: -42px;
+ margin: 0 auto;
+ .downText {
+ .img {
+ margin-right: 10px;
+ font-size: 16px;
+ color: #ffffff;
+ line-height: 19px;
+ .tips {
+ padding-top: 10px;
+ color: #a49d94;
+ .downButtom {
+ .downButtomItem {
+ width: 140px;
+ background: url(https://4dscene.4dage.com/new4dkk/deng/static/img/btn01@2x.png)
+ background-size: cover;
+ font-style: normal;
+ margin: 0 5px;
+ line-height: 42px;
@@ -0,0 +1,37 @@
+import Vue from 'vue'
+import App from './App'
+// vuex
+import store from './store'
+// 引入全局uView
+import uView from '@/uni_modules/uview-ui'
+import mixin from './common/mixin'
+Vue.prototype.$store = store
+Vue.prototype.$root = 'https://4dscene.4dage.com/new4dkk/'
+Vue.config.productionTip = false
+App.mpType = 'app'
+Vue.use(uView)
+// #ifdef MP
+// 引入uView对小程序分享的mixin封装
+const mpShare = require('@/uni_modules/uview-ui/libs/mixin/mpShare.js')
+Vue.mixin(mpShare)
+// #endif
+Vue.mixin(mixin)
+const app = new Vue({
+ store,
+ ...App
+})
+// 引入请求封装
+require('./util/request/index')(app)
+app.$mount()
@@ -0,0 +1,152 @@
+{
+ "name" : "南华灯会",
+ "appid" : "__UNI__A145573",
+ "description" : "多平台快速开发的UI框架",
+ "versionName" : "2.0.37",
+ "versionCode" : 1,
+ "transformPx" : false,
+ "app-plus" : {
+ // APP-VUE分包,可提升APP启动速度,2.7.12开始支持,兼容微信小程序分包方案,默认关闭
+ "optimization" : {
+ "subPackages" : true
+ "safearea" : {
+ "bottom" : {
+ "offset" : "none"
+ "splashscreen" : {
+ "alwaysShowBeforeRender" : true,
+ "waiting" : true,
+ "autoclose" : true,
+ "delay" : 0
+ "usingComponents" : true,
+ "nvueCompiler" : "uni-app",
+ "compilerVersion" : 3,
+ "modules" : {
+ "Webview-x5" : {}
+ "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.READ_CONTACTS\"/>",
+ "<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.WRITE_CONTACTS\"/>",
+ "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+ "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+ "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+ "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+ "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+ "<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.CALL_PHONE\"/>",
+ "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+ "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+ "<uses-feature android:name=\"android.hardware.camera\"/>",
+ "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+ "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+ "abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
+ "ios" : {
+ "idfa" : false
+ "sdkConfigs" : {
+ "ad" : {}
+ "icons" : {
+ "hdpi" : "unpackage/res/icons/72x72.png",
+ "xhdpi" : "unpackage/res/icons/96x96.png",
+ "xxhdpi" : "unpackage/res/icons/144x144.png",
+ "xxxhdpi" : "unpackage/res/icons/192x192.png"
+ "appstore" : "unpackage/res/icons/1024x1024.png",
+ "ipad" : {
+ "app" : "unpackage/res/icons/76x76.png",
+ "app@2x" : "unpackage/res/icons/152x152.png",
+ "notification" : "unpackage/res/icons/20x20.png",
+ "notification@2x" : "unpackage/res/icons/40x40.png",
+ "proapp@2x" : "unpackage/res/icons/167x167.png",
+ "settings" : "unpackage/res/icons/29x29.png",
+ "settings@2x" : "unpackage/res/icons/58x58.png",
+ "spotlight" : "unpackage/res/icons/40x40.png",
+ "spotlight@2x" : "unpackage/res/icons/80x80.png"
+ "iphone" : {
+ "app@2x" : "unpackage/res/icons/120x120.png",
+ "app@3x" : "unpackage/res/icons/180x180.png",
+ "notification@3x" : "unpackage/res/icons/60x60.png",
+ "settings@3x" : "unpackage/res/icons/87x87.png",
+ "spotlight@2x" : "unpackage/res/icons/80x80.png",
+ "spotlight@3x" : "unpackage/res/icons/120x120.png"
+ "quickapp" : {},
+ "mp-weixin" : {
+ "appid" : "wxc6bdb80f216e918c",
+ "setting" : {
+ "urlCheck" : false,
+ "es6" : false,
+ "minified" : false,
+ "postcss" : false
+ "mergeVirtualHostAttributes" : true
+ "mp-alipay" : {
+ "component2" : true
+ "mp-qq" : {
+ "appid" : "15646153"
+ "mp-baidu" : {
+ "appid" : "17597421"
+ "mp-toutiao" : {
+ "appid" : "tt2bc55d78b4ff50bf"
+ "h5" : {
+ "template" : "template.h5.html",
+ "router" : {
+ "mode" : "history",
+ "base" : ""
+ "treeShaking" : {
+ "enable" : false
+ "title" : "uView UI",
+ "maps" : {
+ "qqmap" : {
+ "key" : ""
+ "domain" : ""
+ "id": "qiyan-qrcode",
+ "name": "qrcode",
+ "displayName": "极简的qrcode(二维码)生成",
+ "version": "1.1.0",
+ "description": "qrcode(二维码)显示生成,体积小,不依赖任何库",
+ "keywords": [
+ "qrcode",
+ "二维码"
+ "dcloudext": {
+ "category": [
+ "前端组件",
+ "通用组件"
+ "dependencies": {
+ "compression-webpack-plugin": "^11.1.0",
+ "uniapp-qrcode": "^1.0.2"
+ // "condition": { //模式配置,仅开发期间生效
+ // "current": 0, //当前激活的模式(list 的索引项)
+ // "list": [{
+ // "name": "test", //模式名称
+ // "path": "pages/componentsA/test/test", //启动页面,必选
+ // "query": "" //启动参数,在页面的onLoad函数里面得到
+ // }]
+ // },
+ "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+ "path": "pages/home/index",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": "南华灯会"
+ },{
+ "path": "pages/home/webview",
+ "path": "pages/my/index",
+ "globalStyle": {
+ "navigationBarTextStyle": "black",
+ "navigationBarTitleText": "uView",
+ "navigationBarBackgroundColor": "#FFFFFF",
+ "backgroundColor": "#FFFFFF"
@@ -0,0 +1,57 @@
+ <view class="content">
+ <u--image :showLoading="true" src="/static/img/img_cover@2x.jpg" width="100vw" height="100vh"></u--image>
+ <view class="fixed bottom-48 w-full text-center">
+ <view class="absolute" style="left: calc(50% - 112px);bottom: 100px">
+ <u--image src="/static/img/btn_enter@2x.png" width="224px" height="55px"></u--image>
+ <view class="butText text-center" @click="handleHome">开始云游</view>
+ // uButton
+ title: 'Hello'
+ onLoad() {
+ console.log('开启云上观灯', uni)
+ uni.$u.route('/pages/home/home')
+<style>
+ .content {
+ flex-direction: column;
+ justify-content: center;
+ min-height: 100vh;
+ .butText{
+ color: #FFFFFF;
+ line-height: 36px;
+ text-shadow: 0px 0px 8px #FFF9B1, 0px 0px 27px #FFF9B1, 0px 0px 4px #D86332;
+ text-transform: none;
+ top: -110px;
+ z-index: 10;
@@ -0,0 +1,366 @@
+ <view class="home-img">
+ <div class="homeBg">
+ <scroll-view :scroll-x="true" class="scrollview-box">
+ <div class="img">
+ height="calc(100vh - 71px)"
+ width="700px"
+ mode="heightFix"
+ src="/static/img/homeBg.jpg"
+ <div class="list">
+ class="itme"
+ :class="{ active: item.id == active }"
+ v-for="item in list"
+ :key="item.id"
+ @click.stop="handleItem(item)"
+ <img
+ class="bg"
+ v-if="item.id == active"
+ src="/static/img/tab_active@2x.png"
+ alt=""
+ />
+ v-else
+ src="/static/img/tab_normal@2x.png"
+ <div class="itmeText">{{ item.title }}</div>
+ <div class="mypopup">
+ <div class="popupImg">
+ <div class="but">可打卡</div>
+ width="209px"
+ height="131px"
+ radius="10"
+ src="https://4dkk.4dage.com/head/15915816041/head_1722399456050.png"
+ <div class="title">{{ item.title }}</div>
+ <div
+ class="flex justify-evenly butList content-center"
+ style="line-height: 24px; color: #fff"
+ <div class="flex justify-around content-center" @click="handleRoam">
+ <img src="/static/img/icon_dollhouse_normal@2x.png" />
+ 漫游
+ <div class="border"></div>
+ <div class="flex justify-around content-center">
+ 导航
+ </scroll-view>
+ <!-- <div >
+ <!-- <view class="fixed bottom-40 flex w-full justify-around" style="">
+ <view class="text-center">
+ 点亮祈福
+ <u-icon
+ class="text-center flex-col-reverse"
+ name="scan"
+ color="#2979ff"
+ size="28"
+ ></u-icon>
+ 扫码打卡
+ <tabbar></tabbar>
+ :closeable="true"
+ <view>
+ <view class="text">
+ <text mt-10>出淤泥而不染,濯清涟而不妖</text>
+ <view class="butList flex justify-around my-10">
+ <u-button
+ @click="handleRoam"
+ style="width: 60px"
+ type="primary"
+ plain
+ icon="car"
+ size="small"
+ shape="circle"
+ text="漫游"
+ ></u-button>
+ @click="handleMap"
+ icon="map"
+ text="祈愿地图"
+ text="导航"
+import tabbar from "components/tabbar/index.vue";
+ tabbar,
+ active: null,
+ id: 1,
+ title: "大雄宝殿",
+ style: {},
+ handleMap() {
+ uni.$u.route("/pages/home/map");
+ handleRoam() {
+ uni.$u.route("/pages/home/roam");
+ handleItem(item) {
+ this.active = item.id;
+.content {
+ .right {
+ left: 20px;
+ top: 20px;
+.logo {
+ height: 200rpx;
+ width: 200rpx;
+ margin-top: 200rpx;
+ margin-left: auto;
+ margin-right: auto;
+ margin-bottom: 50rpx;
+.text-area {
+ padding-bottom: 50%;
+.title {
+ font-size: 36rpx;
+ color: #8f8f94;
+.home-img {
+ height: 100%;
+ .homeBg {
+ .scrollview-box {
+ white-space: nowrap; // 滚动必须加的属性
+ .list {
+ .itme {
+ top: 50%;
+ left: 50%;
+ border-radius: 50%;
+ line-height: 30px;
+ width: 27px;
+ height: 108px;
+ color: #fff;
+ // color: #5B472E;
+ line-height: 17px;
+ // letter-spacing: 10px;
+ // vertical-align: middle;
+ .bg {
+ z-index: -1;
+ .itmeText {
+ writing-mode: vertical-rl;
+ left: 18px;
+ top: 22px;
+ // letter-spacing: normal;
+ // display: table-cell;
+ .mypopup {
+ display: none;
+ width: 230px;
+ height: 162px;
+ bottom: 108px;
+ background: linear-gradient(
+ 158deg,
+ rgba(193, 149, 97, 0.85) 0%,
+ #5b472e 100%
+ );
+ border: 1px solid;
+ left: -95px;
+ border-radius: 15px;
+ .popupImg {
+ letter-spacing: 0;
+ width: 209px;
+ height: 131px;
+ padding: 0 9px;
+ top: -10px;
+ border-radius: 10px;
+ overflow: hidden;
+ .title {
+ line-height: 16px;
+ bottom: 10px;
+ .but {
+ width: 63px;
+ height: 25px;
+ right: 10px;
+ top: 10px;
+ border-radius: 20px 20px 20px 20px;
+ border: 1px solid #fff9b6;
+ color: #fff9b6;
+ line-height: 25px;
+ .butList {
+ .border {
+ width: 1px;
+ height: 26px;
+ border-radius: 0px 0px 0px 0px;
+ border-left: 1px solid;
+ border-image: linear-gradient(
+ 180deg,
+ rgba(255, 255, 255, 0),
+ rgba(255, 255, 255, 1),
+ rgba(255, 255, 255, 0)
+ 1 1;
+ img {
+ width: 24px;
+ height: 24px;
+ .active {
+ color: #5b472e;
+ display: block;
+ height: calc(100vh - 81px);
+ overflow-x: auto;
+ width: 700px;
+.u-popup__content__close {
+ right: calc(50% - 4.5px) !important;
+ bottom: -40px !important;
+ border: 1px solid #fff;
+ border-radius: 20px;
+ padding: 5px;
+ .u-icon__icon {
+ color: #fff !important;
@@ -0,0 +1,71 @@
+ <u--image :showLoading="true" src="https://4dscene.4dage.com/new4dkk/deng/static/img/img_cover@2x.jpg" width="100vw" height="100vh"></u--image>
+ <u--image src="https://4dscene.4dage.com/new4dkk/deng/static/img/btn_enter@2x.png" width="224px" height="55px"></u--image>
+import { wxLogin } from "@/common/api.js";
+ this.$store.commit("wxLogininfo", wxLogin);
+ // uni.login({
+ // provider: 'weixin', //使用微信登录
+ // success: function ({code}) {
+ // console.log('loginRes', code);
+ // wxLogin({
+ // code: code
+ // }).then(res => {
+ // console.log('wxLoginRes', res, code);
+ // uni.$u.toast("登录成功");
+ // uni.setStorageSync('token', res.token);
+ // })
+ // }
+ // });
@@ -0,0 +1,153 @@
+ <div class="homeMap">
+ <view class="map">
+ <div class="chinaMap" v-if="show">
+ <echarsMap></echarsMap>
+ <div class="allMap" v-else>
+ <div class="goChina" @click="show = !show"></div>
+ <div class="people">
+ height="30px"
+ width="30px"
+ src="/static/img/icon_lotus_dark@2x.png"
+ <text class="peopleNum">祈愿人数</text>
+ <text class="peopleNum">{{ people }}</text>
+ <div class="bottom flex justify-between items-end">
+ <div class="listItem" v-for="item in 6" :key="item">
+ 用户名
+ <text style="color: #7EE3DE">【河北】</text>
+ 打卡!
+ <div class="qiyuan">
+ height="70px"
+ width="70px"
+ src="/static/img/btn_wish@2x.png"
+import echarsMap from "components/echars/maps.vue";
+ echarsMap,
+ show: true,
+ people: 15124,
+ onReady() {},
+.homeMap {
+ height: 100vh;
+ background: url(https://4dscene.4dage.com/new4dkk/deng/static/img/bg03@2x.png)
+ .bottom {
+ width: calc(100% - 47px);
+ bottom: 105px;
+ height: 25vh;
+ padding: 0 27px 0 20px;
+ .list{
+ .listItem{
+ padding: 6px 10px;
+ background: rgba(0,0,0,0.3);
+ border-radius: 50px;
+ line-height: 14px;
+ .people {
+ height: 35px;
+ padding: 0 5px;
+ background: rgba(0,0,0,0.5);
+ top: 125px;
+ border-radius: 20px 0 0 20px;
+ .peopleNum {
+ font-size: 24rpx;
+ margin-right: 8px;
+ .map {
+ .chinaMap {
+ background: url(https://4dscene.4dage.com/new4dkk/deng/static/img/map@2x.png)
+ // background-position: -1281px -397px;
+ background-position: -1262px -383px;
+ background-size: 1950px 1334px;
+ .allMap {
+ width: 384px;
+ height: calc(100vh - 358px);
+ 100% 55% no-repeat;
+ background-position-y: 47px;
+ background-position-x: -231px;
+ background-size: 100vh 1097px;
+ // background-position: -1758px -600px;
+ .goChina {
+ width: 100px;
+ height: 100px;
+ left: 240px;
+ top: 236px;
@@ -0,0 +1,206 @@
+ <!-- <web-view class="relative z-0" :src="weburl" ></web-view> -->
+ <u-popup :show="show" mode="center" round="10" @close="close" @open="open">
+ <view class="mySharepopup">
+ <u--image width="42px" height="42px" src="/static/img/icon_cancel@2x.png"></u--image>
+ <u--image width="295px" height="450px" src="/static/img/img_cover@2x.jpg"></u--image>
+ <div class="flex justify-between shareImg">
+ <div class="code">
+ <u--image width="66px" height="66px" src="/static/img/icon_scan_active@2x.png"></u--image>
+ <div class="zz">
+ <u--image width="122px" height="113px" src="/static/img/insignia@2x.png"></u--image>
+ <view class="bottom">
+ <view class="text" style="width: 100%">
+ <view mt-10>第<text >5462</text>位游客完成打卡</view>
+ <!-- <div class="share">
+ <u-button :customStyle="{opacity: 0,width: '50px', height: '50px',}" type="primary" icon="share" plain text=" " open-type="share"></u-button>
+ <u-icon name="share" color="#2979ff" size="28"></u-icon>
+ <u-icon name="download" color="#2979ff" size="28"></u-icon>
+ weburl: "https://test.4dkankan.com/spg.html?m=KK-t-fs8TbMny2Zb&lang=zh"
+ this.show = false
+ open() {
+ handleShare() {
+ uni.share({
+ provider: "weixin",
+ scene: "WXSceneTimeline",
+ type: 1,
+ summary: "我正在使用HBuilderX开发uni-app,赶紧跟我一起来体验!",
+ console.log("success:" + JSON.stringify(res));
+ fail: function (err) {
+ console.log("fail:" + JSON.stringify(err));
+.mySharepopup{
+ // width: 330px;
+ // height: 537px;
+ padding: 19px 17px 0px 17px;
+ .close{
+ right: -20px;
+ top: -20px;
+ .butList{
+ bottom: -80px;
+ left: calc(50% - 14px);
+ .share{
+ .u-icon{
+ top: calc(50% - 14px);
+ .text{
+ .shareImg{
+ bottom: -30px;
+ .bottom{
+ height: 50px;
+ color: #000000;
+ text {
+ font-size: 20px;
+ color: #B1967B;
+ .urlContent{
+ .right{
+.home-img{
+ width: 30px;
+ height: 30px;
+ border: 1px solid #2979ff;
+.u-popup__content__close{
@@ -0,0 +1,52 @@
+ <view class="webview-box">
+ <web-view
+ v-if="url"
+ ref="webview"
+ class="webview"
+ :src="url"
+ @onPostMessage="PostMessage"
+ @message="PostMessage"
+ ></web-view>
+ url: "https://sit-nanhuacs.4dage.com/web/index.html#/home",
+ token: uni.getStorageSync('token')
+ onLoad(data) {
+ console.log("postMessage1: ", data);
+ this.url = data.url + "?token=" + this.token;
+ console.log("postMessage1: ", this.url);
+ // 接收h5页面发来的键值判断需要执行的操作
+ PostMessage(evt) {
+ console.log("2222postMessage1: ", evt);
+ // 获取到对应webview(关键)通过evalJs(注意大小写,如果不知道evalJ是什么,可自行百度) 执行网页的函数,可对其进行传参,完成与网页的通讯
+ handlePostMessage(res) {
+ console.log("212", res);
+.webview-box {
+ left: 0px;
+ right: 0px;
+ top: 0px;
+ bottom: 0px;
+.webview {
+ height: 300rpx;
@@ -0,0 +1,225 @@
+ <div class="content">
+ <u-navbar
+ title="南华禅寺灯会云游"
+ @rightClick="rightClick"
+ :autoBack="true" >
+ </u-navbar>
+ <div class="avatarUrl">
+ <button class="but" type="balanced" open-type="chooseAvatar" @chooseavatar="onChooseavatar">
+ <img :src="userInfo.avatarUrl" class="refreshIcon"></img>
+ </button>
+ <div class="userName">
+ <text>昵称:</text>
+ <input style=" text-align: left;width: 100px" :clearable="false" type="nickname" class="weui-input" :value="userInfo.nickName" @blur="bindblur"
+ placeholder="请输入昵称" @input="bindinput" />
+ </div> <view class="fixed bottom-48 w-full text-center">
+ <view class="butText text-center" @click="handleHome">退出登录</view>
+ <tabbar ref="tabbar" />
+import { userInfo, updateWxUser, logout } from "@/common/api.js";
+// import uNavbar from "uview-ui/components/u-navbar/u-navbar.vue";
+ // uNavbar
+ title: "Hello World",
+ avatarUrl: "",
+ userName: "",
+ ...mapState(["userInfo"]),
+ onLoad(e) {
+ console.log(e, "onChooseAvatar");
+ this.$store.commit("changeUseinf", userInfo);
+ userInfo({}).then((res) => {
+ // state.token = res.token;
+ if (e.scanCode) {
+ //进入扫码
+ that.$refs.tabbar.handleItem({name: "扫码打卡"}, 4)
+ // uni.scanCode({
+ // onlyFromCamera: true,
+ // success: function (res) {
+ // console.log("条码类型:" + res.scanType);
+ // console.log("条码内容:" + res.result, that.$refs.tabbar);
+ // that.$refs.tabbar.show = true;
+ // // that.$store.commit("changeActive", index);
+ // fail: function (e) {
+ // console.log("扫码失败", e, that.$refs.tabbar.show);
+ // // this.$store.commit("changeActive", 4);
+ // // 在小程序的JS文件中定义接口
+ // wx.miniProgram.onMessageToH5 = function (event) {
+ // console.log('收到来自H5页面的消息:', event.data);
+ // // 在这里处理接收到的消息
+ onChooseavatar(e) {
+ this.avatarUrl = e.detail.avatarUrl;
+ this.upload_file(e.detail.avatarUrl);
+ upload_file(e) {
+ wx.showLoading({
+ title: "上传中",
+ let self = this;
+ wx.uploadFile({
+ url: "https://sit-nanhuacs.4dage.com/api/wx/upload",
+ filePath: e, //图片路径
+ name: "file",
+ header: {
+ "Content-Type": "multipart/form-data",
+ token: uni.getStorageSync("token"),
+ formData: {
+ type: "img",
+ success: function (a) {
+ let res = a.data;
+ res = JSON.parse(res);
+ console.log(res, "data");
+ if (res.code == 0) {
+ const { data } = res;
+ let avatarUrl = 'https://sit-nanhuacs.4dage.com' + data.filePath;
+ self.avatar = 'https://sit-nanhuacs.4dage.com' + data.filePath;
+ self.cdnUrl = 'https://sit-nanhuacs.4dage.com' + data.filePath;
+ updateWxUser({ ...self.userInfo, avatarUrl: avatarUrl }).then((res) => {
+ console.log(res);
+ self.$store.commit("changeUseinf", userInfo);
+ // if (res.code == 0) {
+ // uni.$u.toast("修改成功");
+ // } else {
+ // uni.$u.toast("修改失败");
+ wx.hideLoading();
+ wx.showToast({
+ title: "上传成功",
+ icon: "success",
+ duration: 3000,
+ fail: function (a) {
+ title: "上传失败",
+ icon: "none",
+ bindblur(e) {
+ console.log(e);
+ let userName = e.detail.value;
+ updateWxUser({ ...this.userInfo,nickName: userName}).then((res) => {
+ bindinput(e) {
+ savaInfo() {
+ updateWxUser({ ...this.userInfo,nickname: this.userName, avatarUrl: this.avatar }).then((res) => {
+ logout().then((res) => {
+ uni.$u.route("/pages/home/index");
+ // this.show = false;
+ .userName {
+ .avatarUrl {
+ width: 126px;
+ height: 126px;
+ box-shadow: 0px 4px 9px 0px rgba(0, 0, 0, 0.25);
+ border: 5px solid #ffffff;
+ margin: 146px auto 40px auto;
+ padding: 0;
+ .refreshIcon {
+ .butText {
+ text-shadow: 0px 0px 8px #fff9b1, 0px 0px 27px #fff9b1, 0px 0px 4px #d86332;
@@ -0,0 +1,92 @@
+ * @param {*} number 第几位
+ * @param {*} src 名片头像
+ * @param {*} name 名片名字
+ * @param {*} qrCodeUrl 小程序codeURL图片
+const wxml = (name, pic, c1, c2) => `
+<view class="container">
+ <text class="name">wodecesss</text>
+ <text class="content">asdsadad</text>
+ <text class="content">cccccc</text>
+ <text class="msg">扫码一起加入学习吧</text>
+</view>
+`
+ * @param {*} screenWidth 屏幕宽度
+ * @param {*} canvasWidth 画布宽度
+ * @param {*} canvasHeight 画布高度
+ * @param {*} numberWidth 数字宽度,动态设置
+ * @return {*}
+const style = (screenWidth, canvasWidth, canvasHeight) => {
+ "container": {
+ width: canvasWidth,
+ height: canvasHeight,
+ position: 'relative',
+ overflow: 'hidden',
+ backgroundColor: '#ffffff',
+ "name": {
+ fontSize: 20,
+ color: '#333',
+ marginLeft: canvasWidth * 0.08,
+ width: canvasWidth * 0.84,
+ height: screenWidth * 0.18,
+ textAlign: 'center',
+ "content": {
+ fontSize: 14,
+ height: screenWidth * 0.15,
+ "pic": {
+ width: canvasWidth * 0.3,
+ height: screenWidth * 0.28,
+ marginTop: canvasWidth * 0.1,
+ marginLeft: canvasWidth * 0.35,
+ marginBottom: canvasWidth * 0.05,
+ borderRadius: screenWidth * 0.14,
+ "bottom": {
+ height: screenWidth * 0.2,
+ flexDirection: 'row',
+ justifyContent: 'self-start',
+ alignItems: 'center',
+ backgroundColor: '#fafafa',
+ position: 'absolute',
+ bottom: 0,
+ left: 0,
+ "qr": {
+ width: canvasWidth * 0.14,
+ height: screenWidth * 0.14,
+ marginLeft: canvasWidth * 0.04,
+ marginRight: canvasWidth * 0.04,
+ "msg": {
+ color: '#a1a1a1',
+ width: canvasWidth * 0.74,
+ height: 14,
+ textAlign: 'left'
+ wxml,
+ style
@@ -0,0 +1,393 @@
+ <view class="prayers-list" @click="handleclick">
+ title="南华云灯会"
+ leftIconColor="#fff"
+ :titleStyle="{ color: '#fff' }"
+ :autoBack="true"
+ <!-- <u-navbar title="剑未配妥,出门已是江湖" @click-left="onClickBack" @click-right="onClickRight"></u-navbar> -->
+ <div class="allList" v-if="false">
+ <swiper
+ :display-multiple-items="3.3"
+ :rebound="false"
+ slidesPerView="auto"
+ style="height: 555px"
+ :height="555"
+ class="swiper"
+ :autoplay="false"
+ @click="handleclick"
+ <swiper-item
+ :height="680"
+ <div class="listitem">
+ class="item"
+ :class="{ active: active == element }"
+ v-for="(element, s) in item.list"
+ :key="s"
+ {{ element }}
+ </swiper-item>
+ </swiper>
+ <div class="tips">请选择祈愿类型</div>
+ <div class="img_signal" v-if="!show"></div>
+ <div class="activeIndex">
+ <div class="indexHome">学业祈愿</div>
+ <div class="textListContent">
+ class="textList"
+ v-for="(item, index) in textList"
+ :key="item"
+ :class="'text' + index"
+ {{ item }}
+ <div class="textTips">请选择祈愿语</div>
+ <div class="selcet">
+ <div>您来自哪个省份?</div>
+ <div class="selcetBut" @click="provinceShow = true">
+ {{province}}
+ class="mytest"
+ name="arrow-down-fill"
+ color="#fff"
+ size="10"
+ <u-picker :show="provinceShow" :columns="[provinceList]"
+ @cancel="provinceShow = false"
+ @confirm="confirm"
+ @close="provinceShow = false" closeOnClickOverlay></u-picker>
+ active: "婚姻祈愿1",
+ province: "请选择",
+ provinceShow: false,
+ provinceList: [
+ "北京",
+ "天津",
+ "河北省",
+ "山西省",
+ "内蒙古自治区",
+ "辽宁省",
+ "吉林省",
+ "黑龙江省",
+ "上海",
+ "江苏省",
+ "浙江省",
+ "安徽省",
+ "福建省",
+ "江西省",
+ "山东省",
+ "河南省",
+ "湖北省",
+ "湖南省",
+ "广东省",
+ "广西壮族自治区",
+ "海南省",
+ "重庆",
+ "四川省",
+ "贵州省",
+ "云南省",
+ "西藏自治区",
+ "陕西省",
+ "甘肃省",
+ "青海省",
+ "宁夏回族自治区",
+ "新疆维吾尔自治区",
+ "台湾",
+ "香港特别行政区",
+ "澳门特别行政区",
+ "海外",
+ list: ["婚姻祈愿1", "婚姻祈愿2"],
+ list: ["婚姻祈愿2", "婚姻祈愿2"],
+ list: ["婚姻祈愿3", "婚姻祈愿2"],
+ list: ["婚姻祈愿4", "婚姻祈愿2"],
+ list: ["婚姻祈愿5", "婚姻祈愿2"],
+ list: ["婚姻祈愿6", "婚姻祈愿2"],
+ list: ["婚姻祈愿7", "婚姻祈愿2"],
+ textList: [
+ "100%纯手工制作",
+ "知识的殿堂中闪耀属于你的光芒。",
+ "愿你学有所",
+ // list: [{
+ // name: '婚姻祈愿',
+ // textList: ['100%纯手工制作', '100%愿你学业精进,成绩辉煌,知识的殿堂中闪耀属于你的光芒。', '愿你学有所成,智慧增进,每一次努力都为未来铺就光明之路。']
+ // },{
+ handleclick() {
+ this.show = true;
+ confirm(val){
+ console.log("confirm", val);
+ this.province = val.value[0];
+ this.provinceShow = false;
+.prayers-list {
+ background: rgba(32, 21, 10, 0.7);
+ padding-top: 100px;
+ .img_signal {
+ width: 76px;
+ height: 106px;
+ background: url(https://4dscene.4dage.com/new4dkk/deng/static/img/img_signal@2x.png)
+ right: 50px;
+ bottom: 30px;
+ animation: handleAni 1s linear infinite;
+ transition: all 1s;
+ margin-top: 27px;
+ line-height: 21px;
+ height: 555px;
+ .swiper {
+ padding: 0 10px;
+ .listitem {
+ padding: 0 20px;
+ .item {
+ margin-top: 20px;
+ // margin: 18px 20px 0 20px;
+ width: 18px;
+ height: 267px;
+ background: url(https://4dscene.4dage.com/new4dkk/deng/static/img/label@2x.png)
+ writing-mode: tb-rl;
+ padding: 0 25px;
+ &:after {
+ content: "";
+ box-shadow: 0px 2px 18px 11px #fff86c,
+ inset 0px 1px 1px 0px rgba(177, 156, 125, 0.7);
+ height: 71%;
+ width: 80%;
+ top: 32px;
+ left: 8px;
+ border-radius: 25px;
+ .activeIndex {
+ .indexHome {
+ height: 500px;
+ width: 28px;
+ background-size: contain;
+ padding: 0 50px;
+ font-size: 30px;
+ color: #8c4042;
+ line-height: 45px;
+ writing-mode: vertical-lr;
+ letter-spacing: 18px;
+ left: calc(50% - 64px);
+ .qiyuan{
+ bottom: 162px;
+ right: 20px;
+ .selcet {
+ bottom: 20%;
+ .selcetBut {
+ border: 1px solid #d9d9d9;
+ line-height: 48px;
+ width: 120px;
+ padding: 0 15px;
+ height: 48px;
+ .mytest {
+ right: 25px;
+ top: calc(50% - 5px);
+ .textTips {
+ top: calc(50% - 150px);
+ left: calc(50% + 68px);
+ .textListContent {
+ .textList {
+ height: fit-content;
+ padding: 20px 6px;
+ background: rgba(255, 255, 255, 0.3);
+ top: 50px;
+ .text0 {
+ left: 30px;
+ .text1 {
+ left: 80px;
+ top: 276px;
+ .text2 {
+ right: 30px;
+@keyframes handleAni {
+ from {
+ to {
@@ -0,0 +1,381 @@
+ <view class="canvas-box">
+ <!-- 导航栏 -->
+ <view class="nav-box">
+ <view class="title-top" :style="'padding-top:' + statusBarHeight + 'rpx'">
+ <u-icon class="title-icon" name="arrow-left" color="#ffffff" size="36" @click="getBack"></u-icon>
+ <text>海报分享</text>
+ <!-- 开发完成之前,取消 fixed;opacity: 0;-->
+ <canvas style="width: 352px;height: 800px;position: fixed;" class="canvas" canvas-id="canvasID"></canvas>
+ <!-- 完成海报制作后,需要把canvas移到看不见的地方,或者隐藏,把image显示出来 -->
+ <!-- <image :src="imgUrl" mode=""></image> -->
+ <view class="footer">
+ <view class="download" @click="saveImage">
+ <!-- 小于符号图标 -->
+ <u-icon name="download" color="#ffffff" size="34"></u-icon>
+ <text>保存到相册</text>
+import wxcode from 'uniapp-qrcode';
+ imgUrl: '',
+ statusBarHeight: 0,
+ bgimg: '',
+ textX: 10, //文字x轴坐标
+ avatar: 'https://4dscene.4dage.com/new4dkk/deng/static/img/flower_colorful@2x.png', //头像地址
+ hello: 'https://4dscene.4dage.com/new4dkk/deng/static/img/img_label@2x.png', // hello图标
+ mony: 'https://4dscene.4dage.com/new4dkk/deng/static/img/flower_pink@2x.png' //圆的钱图标
+ async created() {
+ wxcode.qrcode('qrcode', '1234567890123456789', 420, 420);
+ // this.$tool.getSystemInfo().then(res => {
+ // this.statusBarHeight = res
+ async mounted() {
+ let list = ["知识的殿堂中闪耀属于你的光芒。阿斯顿撒大大大大大大啊啊啊啊", "愿你学业精进,成绩辉煌,"]
+ let imgData = await uni.getImageInfo({src: this.hello})
+ this.bgimg = imgData[1].path
+ let ctx = uni.createCanvasContext('canvasID', this);
+ // ctx.setFillStyle("transparent"); //设置canvas背景颜色
+ // ctx.fillRect(0, 0, 346, 500) //设置canvas画布大小
+ this.fillRoundRect(ctx, 0, 0, 352, 1400, 15, 'transparent'); //绘制一个圆角矩形
+ // this.fillRoundRect(ctx, 0, 0, 346, 182, 15, '#333231'); //绘制一个圆角矩形
+ console.log('imgUrlbgimg', this.bgimg)
+ this.drawrectcular(ctx, this.bgimg, 86, 0, 180, 700) //绘制圆形头像
+ this.listdrawText(ctx, list)
+ // this.drawTextVertical(ctx, '绘制武将姓名:陆逊', x, y);
+ // // 绘制势力汉字:吴
+ // that.drawInfluence(ctx, that.data.hero.HERO.INFLUENCE);
+ // // 绘制武将姓名:陆逊
+ // that.drawName(ctx, that.data.hero.HERO.NAME);
+ // // 绘制武将称号:江陵侯
+ // that.drawHorner(ctx, that.data.hero.HERO.HORNER);
+ // ctx.setFontSize(18)
+ // ctx.setFillStyle("#ffffff")
+ // ctx.fillText('明天依然是晴天11', 98, 65)
+ // ctx.drawImage(this.hello, 240, 10, 86, 86) //二维码
+ // ctx.font = '20px normal'
+ // ctx.setFillStyle("#09CFB1")
+ // ctx.fillText('我为“贤马”带盐', 30, 122)
+ // ctx.font = '16px normal'
+ // ctx.fillText('“闲么?上贤马做兼职”', 20, 152)
+ // // 绘制职位标题,多余文字自动换行
+ // ctx.setFontSize(28)
+ // ctx.setFillStyle("#333333")
+ // let str = '店铺实习生ZAra重庆龙湖时代'
+ // // 字符串总长度
+ // let _strLength = str.length
+ // // 总结截取次数
+ // let _strNum = Math.ceil(_strLength / 9)
+ // // 每次开始截取字符串的索引
+ // let _strHeight = 0
+ // // 绘制的字体 x,y的初始位置
+ // let _strX = 27,
+ // _strY = 223
+ // let strIndex = 223
+ // // 开始截取
+ // for (let i = 0; i < _strNum; i++) {
+ // strIndex = _strY + i * 40
+ // ctx.fillText(str.substr(_strHeight + i * 9, 9), _strX, _strY + i * 40)
+ // strIndex += 30
+ // ctx.setFontSize(14)
+ // ctx.setFillStyle("#1BB99A")
+ // let strtitle = '环境好/结算快/时间短'
+ // ctx.fillText(strtitle, _strX, strIndex)
+ // ctx.setFontSize(20)
+ // ctx.setFillStyle("#333231")
+ // ctx.fillText('16元/小时', _strX, 418)
+ // this.drawCircular(ctx, this.mony, _strX, 429, 14, 14) //绘制圆形头像
+ // ctx.setFontSize(12)
+ // ctx.setFillStyle("#F7BA65")
+ // ctx.fillText('已预付', _strX + 20, 440)
+ // 绘制微信二维码
+ ctx.drawImage(this.hello, 208, 370, 120, 120) //二维码
+ ctx.draw(false, () => {
+ // 返回canvas图片信息
+ uni.canvasToTempFilePath({
+ canvasId: 'canvasID',
+ success: (res) => {
+ this.imgUrl = res.tempFilePath
+ // console.log(res.tempFilePath)
+ fail: function(err) {
+ console.log(err)
+ getBack() {
+ uni.navigateBack({
+ delta: 1
+ listdrawText(ctx, list){
+ let mylist = []
+ list.map(ele => {
+ if(ele && ele.length > 21){
+ mylist.push(ele.substring(21, ele.length))
+ mylist.push(ele.substring(0, 21))
+ }else {
+ mylist.push(ele)
+ // mylist.push(ele)
+ this.textX = 200 - (mylist.length/2)* 40;
+ console.log('mylist', mylist, this.textX)
+ mylist.map(ele => {
+ this.drawTextVertical(ctx, ele, this.textX, 160);
+ this.textX += 30;
+ drawTextVertical(context, text, xx, y) {
+ let x = xx || 0;
+ var arrText = text.split('');
+ var arrWidth = arrText.map(function (letter) {
+ return 18;
+ var align = context.textAlign;
+ var baseline = context.textBaseline;
+ if (align == 'left') {
+ x = x + Math.max.apply(null, arrWidth) / 2;
+ } else if (align == 'right') {
+ x = x - Math.max.apply(null, arrWidth) / 2;
+ if (baseline == 'bottom' || baseline == 'alphabetic' || baseline == 'ideographic') {
+ y = y - arrWidth[0] / 2;
+ } else if (baseline == 'top' || baseline == 'hanging') {
+ y = y + arrWidth[0] / 2;
+ context.textAlign = 'center';
+ context.textBaseline = 'middle';
+ context.font = '18px normal'
+ context.setFillStyle("#303030")
+ // 开始逐字绘制
+ arrText.forEach(function (letter, index) {
+ // 确定下一个字符的纵坐标位置
+ var letterWidth = arrWidth[index];
+ // 是否需要旋转判断
+ var code = letter.charCodeAt(0);
+ console.log('arrWidth', arrWidth, align, index, letter)
+ if (code <= 256) {
+ context.translate(x, y);
+ // 英文字符,旋转90°
+ context.rotate(90 * Math.PI / 180);
+ context.translate(-x, -y);
+ } else if (index > 0 && text.charCodeAt(index - 1) < 256) {
+ // y修正
+ y = y + arrWidth[index - 1] / 2;
+ context.fillText(letter, x, y);
+ // 旋转坐标系还原成初始态
+ context.setTransform(1, 0, 0, 1, 0, 0);
+ y = y + letterWidth;
+ // 水平垂直对齐方式还原
+ context.textAlign = align;
+ context.textBaseline = baseline;
+ saveImage() { //点击保存
+ var _this = this;
+ uni.saveImageToPhotosAlbum({
+ filePath: _this.imgUrl,
+ success() {
+ uni.showModal({
+ title: "保存成功",
+ content: "图片已成功保存到相册,快去分享到您的圈子吧",
+ showCancel: false
+ // 将网络图片转为临时图片地址
+ async getImageInfo({imgSrc}) {
+ return new Promise((resolve, errs) => {
+ uni.downloadFile({
+ src: imgSrc,
+ success: function(image) {
+ resolve(image);
+ fail(err) {
+ errs(err);
+ // 绘制圆形头像
+ drawCircular(ctx, url, x, y, width, height) {
+ //画圆形头像
+ var avatarurl_width = width;
+ var avatarurl_heigth = height;
+ var avatarurl_x = x;
+ var avatarurl_y = y;
+ ctx.save(); //先保存状态,已便于画完园再用
+ ctx.beginPath(); //开始绘制
+ ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math
+ .PI * 2, false);
+ ctx.setFillStyle("#FFFFFF")
+ ctx.fill() //保证图片无bug填充
+ ctx.clip(); //剪切
+ ctx.drawImage(url, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); //推进去图片
+ // 绘制矩形图片
+ drawrectcular(ctx, url, x, y, width, height) {
+ ctx.rect(x, y, width, height);
+ // ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math
+ // .PI * 2, false);
+ // 绘制带圆角的矩形方法
+ fillRoundRect(cxt, x, y, width, height, radius, fillColor) {
+ //圆的直径必然要小于矩形的宽高
+ if (2 * radius > width || 2 * radius > height) {
+ return false;
+ cxt.save();
+ cxt.translate(x, y);
+ //绘制圆角矩形的各个边
+ this.drawRoundRectPath(cxt, width, height, radius);
+ cxt.fillStyle = fillColor || '#fff'; //若是给定了值就用给定的值否则给予默认值
+ cxt.fill();
+ cxt.restore();
+ drawRoundRectPath(cxt, width, height, radius) {
+ cxt.beginPath(0);
+ //从右下角顺时针绘制,弧度从0到1/2PI
+ cxt.arc(width - radius, height - radius, radius, 0, Math.PI / 2);
+ //矩形下边线
+ cxt.lineTo(radius, height);
+ //左下角圆弧,弧度从1/2PI到PI
+ cxt.arc(radius, height - radius, radius, Math.PI / 2, Math.PI);
+ //矩形左边线
+ cxt.lineTo(0, radius);
+ //左上角圆弧,弧度从PI到3/2PI
+ cxt.arc(radius, radius, radius, Math.PI, (Math.PI * 3) / 2);
+ //上边线
+ cxt.lineTo(width - radius, 0);
+ //右上角圆弧
+ cxt.arc(width - radius, radius, radius, (Math.PI * 3) / 2, Math.PI * 2);
+ //右边线
+ cxt.lineTo(width, height - radius);
+ cxt.closePath();
+ .canvas-box {
+ background-color: #1ABC9C;
+ /deep/.nav-box {
+ padding: 0 20rpx;
+ z-index: 9999;
+ .title-top {
+ font-weight: 550;
+ margin-bottom: 30rpx;
+ .title-icon {
+ image {
+ width: 335px;
+ .footer {
+ justify-content: space-between;
+ padding: 0 40rpx;
+ bottom: 10%;
+ .download {
+ border: 1rpx solid #ffffff;
+ view {
+ height: 78rpx;
+ line-height: 78rpx;
+ font-size: 30rpx;
+ border-radius: 36rpx;
@@ -0,0 +1,725 @@
+ <view class="allList">
+ <view class="list">
+ <scroll-view
+ scroll-x="true"
+ class="content-scroll swiper"
+ scroll-with-animation
+ :scroll-left="scrollLeft"
+ class="scroll-item list"
+ :class="
+ current == index
+ ? `active index${index} ${curIndex == index ? 'fang' : ''}`
+ : `index${index}`
+ @click="changeTitle(index)"
+ <view class="activeItem" v-if="curIndex == index">
+ <view class="activetext" v-for="(items, a) in item.list" :key="a">
+ {{ items }}
+ <view class="item-text item" v-else>{{ item.name }}</view>
+ <view class="source">
+ <text style="color: #9c9590">来源</text>
+ <text>小阿飞</text>
+ <view class="tips">
+ 请选择祈愿类型
+ <view v-if="curIndex" class="downButtom flex justify-center">
+ <view class="downButtomItem">返回我的祈愿</view>
+ <view v-else class="downButtom flex justify-center">
+ <view class="downButtomItem">祈愿地图</view>
+ <view class="downButtomItem" @click="handleShare">分享</view>
+ <view class="img_signal" v-if="!show"></view>
+ :show="shareShow"
+ <view class="mySharepopup planel" ref="addImage" id="capture">
+ <!-- <view class="close" @click="close">
+ src="/static/img/icon_cancel@2x.png"
+ <view class="shareItem answer_draw_canvas">
+ <view class="activeItem answer_draw_canvas">
+ class="activetext answer_draw_canvas"
+ v-for="(items, a) in shareData.list"
+ :key="a"
+ <!-- <view class="share-page-box" id="box" v-if="shareShow"
+ :style="{ width: '180px', height: '600px' }">
+ <wxml-to-canvas class="widget" :width="canvasWidth" :height="canvasHeight"></wxml-to-canvas>
+ <view class="shareRight answer_draw_canvas">
+ <view class="close" @click="close">
+ <view class="downBut" @click="handleDown">
+ <view class="img">
+ <view class="downText" @click="handleDown">下载图片</view>
+ <view class="codeImg1 answer_draw_canvas">
+ background="#fff"
+ <view class="codeImg2 answer_draw_canvas">
+ <canvas
+ canvas-id="answerCanvas"
+ class="answerCanvas"
+ :style="'width:' + canvasWidth + 'px;height:' + canvasHeight + 'px;'"
+ ></canvas>
+import { wxml, style } from './DomData';
+import Wxml2Canvas from "wxml2canvas";
+import html2canvas from "html2canvas";
+ shareShow: false,
+ scrollLeft: 0, // 横向滚动条位置
+ current: 2,
+ curIndex: null,
+ shareData: {},
+ name: "学业祈愿1",
+ list: ["知识的殿堂中闪耀属于你的光芒。", "愿你学业精进,成绩辉煌,"],
+ name: "学业祈愿2",
+ name: "学业祈愿3",
+ name: "学业祈愿4",
+ name: "学业祈愿5",
+ name: "学业祈愿6",
+ canvasWidth: 180, // 默认canvas宽高
+ canvasHeight: 700,
+ screenWidth: null, // 设备宽度
+ screenHeight: null, // 设备高度
+ // name: '',
+ // pic: '',
+ // chapter1: '',
+ // chapter2: '',
+ widget: null,
+ msg: '加载中,请稍等...', // 提示语
+ // 获取标题区域宽度,和每个子元素节点的宽度
+ this.getScrollW();
+ renderInit(){
+ // 获取设备信息
+ wx.getSystemInfo({
+ console.log("屏幕", res);
+ this.screenWidth = 180;
+ this.canvasWidth = this.screenWidth;
+ this.canvasHeight = 600;
+ console.log("海报高度:", this.canvasHeight);
+ // 数字容器宽度 动态设置
+ wx.showLoading({ title: "海报加载中..." });
+ this.widget = this.selectComponent(".widget");
+ this.renderToCanvas();
+ }, 1000);
+ // 获取标题区域宽度,和每个子元素节点的宽度以及元素距离左边栏的距离
+ getScrollW() {
+ query
+ .select(".content-scroll")
+ .boundingClientRect((data) => {
+ // 拿到 scroll-view 组件宽度
+ this.contentScrollW = data.width;
+ .selectAll(".scroll-item")
+ let dataLen = data.length;
+ for (let i = 0; i < dataLen; i++) {
+ // scroll-view 子元素组件距离左边栏的距离
+ this.list[i].left = data[i].left;
+ // scroll-view 子元素组件宽度
+ this.list[i].width = data[i].width;
+ // 选择标题
+ changeTitle(index) {
+ if (index == this.current && this.curIndex != index) {
+ this.curIndex = index;
+ this.curIndex = null;
+ this.current = index;
+ console.log("changeTitle", index);
+ this.scrollLeft = (index - 1) * 118;
+ console.log("handleShare");
+ this.shareShow = true;
+ this.shareData = this.list[this.current];
+ setTimeout(()=>{
+ // this.renderInit();
+ },500)
+ this.shareShow = false;
+ console.log("handleShare", this.shareShow);
+ changeswiper(e) {
+ // 得加上这个方法!!!
+ this.currentswiper = e.detail.current;
+ confirm(val) {
+ handleDown() {
+ // this.extraImage()
+ // wxml 转 canvas
+ renderToCanvas() {
+ console.log('canvasStyle.widget', this.shareData)
+ const _wxml = wxml(this.shareData);
+ console.log('this.widget', this.widget)
+ const _style = style(this.screenWidth, this.canvasWidth, this.canvasHeight) //this.canvasHeight
+ const p1 = this.widget.renderToCanvas({ wxml: _wxml, style: _style })
+ console.log('renderToCanvas', p1)
+ p1.then((res) => {
+ console.log('海报生成成功', res);
+ wx.hideLoading()
+ }).catch((err) => {
+ console.log('生成失败', err)
+ // 保存到朋友圈
+ extraImage() {
+ if (!this.show) {
+ wx.showToast({ title: '海报生成失败,无法分享到朋友圈', icon: 'none' })
+ wx.showLoading({ title: '海报生成中...' })
+ const p2 = this.widget.canvasToTempFilePath({ fileType:'jpg', quality :0.5})
+ p2.then(result => {
+ let path = result.tempFilePath
+ wx.getSetting({
+ success: res => {
+ // 非初始化且未授权的情况,需要再次弹窗提示授权
+ if (res.authSetting['scope.writePhotosAlbum'] != undefined && res.authSetting['scope.writePhotosAlbum'] != true) {
+ wx.showModal({
+ title: '是否授权相册权限',
+ content: '需要获取相册权限,请确认授权,否则无法使用相关功能',
+ if (res.confirm) {
+ wx.openSetting({
+ success: dataAu => {
+ if (dataAu.authSetting["scope.writePhotosAlbum"] == true) {
+ title: '授权成功',
+ icon: 'none',
+ duration: 1000
+ that.saveIMg(path);
+ title: '授权失败',
+ icon: 'success',
+ // 初始化且未授权,系统默认会弹窗提示授权
+ // 非初始化且已授权,也会进入这里
+ //创建wxml2canvas对象
+ let drawImage = new Wxml2Canvas(
+ element: "answerCanvas", // canvas节点的id,
+ obj: that, // 在组件中使用时,需要传入当前组件的this
+ width: 180, // 宽 自定义
+ height: 700, // 高 自定义
+ background: "#fff", // 默认背景色 设置背景色
+ progress(percent) {
+ // 绘制进度
+ console.log(percent);
+ finish(url) {
+ filePath: url,
+ uni.hideLoading();
+ fail() {
+ error(res) {
+ // uni.hideLoading()
+ // 画失败的原因
+ this
+ //传入数据,画制canvas图片
+ let data = {
+ //直接获取wxml数据
+ type: "wxml",
+ class: ".planel .answer_draw_canvas", // answer_canvas这边为要绘制的wxml元素跟元素类名, answer_draw_canvas要绘制的元素的类名(所有要绘制的元素都要添加该类名)
+ limit: ".mySharepopup", // 这边为要绘制的wxml元素跟元素类名,最外面的元素
+ x: 0,
+ y: 0,
+ drawImage.draw(data, that);
+ // 保存到相册
+ async saveIMg(tempFilePath) {
+ wx.saveImageToPhotosAlbum({
+ filePath: tempFilePath,
+ success: async (res) => {
+ content: '图片已保存,分享给好友吧!',
+ showCancel: false,
+ confirmText: '好的',
+ confirmColor: '#333',
+ wx.navigateBack({
+ //返回
+ fail: function (res) {
+ console.log('res', res);
+ title: '您取消了授权',
+ duration: 2000
+// <script lang="renderjs" module="canvasImage">
+// import html2canvas from 'html2canvas'
+// export default {
+// methods: {
+// // 生成图片需要调用的方法
+// handleDown(){
+// // const query = uni.createSelectorQuery().in(this);
+// const dom = this.$refs.addImage;//query.select('#capture'); // 需要生成图片内容的 dom 节点
+// html2canvas(dom).then(canvas => {
+// this.imgs= canvas.toDataURL() //base64字符串 放到图片标签上
+// });
+// }
+// </script>
+ .mySharepopup {
+ right: -24px;
+ .shareItem {
+ width: 150px;
+ height: calc(100% - 512px);
+ padding: 160px 0px;
+ display: inline-block;
+ background-size: 100% 100%;
+ background: url(https://4dscene.4dage.com/new4dkk/deng/static/img/img_label@2x.png)
+ top: 44px;
+ line-height: 24px;
+ .activetext {
+ margin: 0 9px;
+ .shareRight {
+ right: -50px;
+ .downBut {
+ bottom: -70px;
+ right: -34px;
+ bottom: -90px;
+ width: 64px;
+ writing-mode: rl;
+ .codeImg1 {
+ right: -90px;
+ bottom: 50px;
+ padding: 3px;
+ background: #fff;
+ .codeImg2 {
+ bottom: 150px;
+ .qrcode {
+ padding-top: 20px;
+ // margin-top: 50rpx;
+ box-sizing: border-box;
+ .content-scroll {
+ white-space: nowrap;
+ .scroll-item {
+ width: 118px;
+ transition: all 0.3s;
+ .source {
+ color: #fff9b1;
+ bottom: 23%;
+ right: -10px;
+ .item-text {
+ // transform: rotateY(180deg);
+ line-height: 100rpx;
+ top: calc(50% - 133px);
+ left: calc(50% - 34px);
+ &.active {
+ color: #1468ff;
+ .activeItem {
+ .fang {
+ transform: rotateY(180deg);
+ transition: all 0.5s;
+ transform: rotateY(0deg);
+ top: calc(50% - 226px);
+ left: calc(50% - 50px);
+ height: 453px;
+ font-size: 27px;
+ line-height: 41px;
+ height: 263px;
+ padding: 95px 0px;
+ // .listitem {
+ // text-align: center;
+ // padding: 0 20px;
+ margin: 20px auto;
+ // .active {
+ // &:after {
+ // content: "";
+ // box-shadow: 0px 2px 18px 11px #fff86c,
+ // inset 0px 1px 1px 0px rgba(177, 156, 125, 0.7);
+ // position: absolute;
+ // height: 71%;
+ // width: 80%;
+ // top: 32px;
+ // left: 8px;
+ // border-radius: 25px;
+ // z-index: -1;
@@ -0,0 +1,277 @@
+ <web-view src="https://blog.csdn.net/m0_56104994/article/details/131311656"></web-view>
@@ -0,0 +1,93 @@
+import Wxml2Canvas from 'wxml2canvas'
+export const startDraw = ()=> {
+ let that = this
+ // 创建wxml2canvas对象
+ let drawMyImage = new Wxml2Canvas({
+ element: 'myCanvas', // canvas的id,
+ obj: that, // 传入当前组件的this
+ width: 200* 2,
+ height: 200 * 2,
+ background: '#141415', // 生成图片的背景色
+ progress(percent) { // 进度
+ finish(url) { // 生成的图片
+ savePoster(url)
+ error(res) { // 失败原因
+ }, this);
+ // 获取wxml数据
+ list: [{
+ type: 'wxml',
+ class: '.my_canvas .my_draw_canvas', // my_canvas要绘制的wxml元素根类名, my_draw_canvas单个元素的类名(所有要绘制的单个元素都要添加该类名)
+ limit: '.my_canvas', // 要绘制的wxml元素根类名
+ y: 0
+ }]
+ // 绘制canvas
+ drawMyImage.draw(data, this);
+export const drawMyCanvas = () => {
+ wx.showLoading()
+ const that = this
+ wx.createSelectorQuery()
+ .select('#my-canvas') // 在 WXML 中填入的 id
+ .fields({ scrollOffset: true, size: true }, () => {
+ startDraw()
+ }).exec(() => {
+ console.log(888)
+export const savePoster = (url) => {
+ success: function() {
+ title: '保存成功',
+ duration: 1500
+ if (err.errMsg === "saveImageToPhotosAlbum:fail:auth denied" || err.errMsg === "saveImageToPhotosAlbum:fail auth deny" || err.errMsg === "saveImageToPhotosAlbum:fail authorize no response") {
+ title: '提示',
+ content: '需要您授权保存相册',
+ success: modalSuccess => {
+ success(settingdata) {
+ if (settingdata.authSetting['scope.writePhotosAlbum']) {
+ success: function () {
+ title: '授权失败,请稍后重新获取',
@@ -0,0 +1,63 @@
+import Vuex from 'vuex'
+Vue.use(Vuex) // vue的插件机制
+let userInfo = uni.getStorageSync('userInfo')
+// Vuex.Store 构造器选项
+const store = new Vuex.Store({
+ // 为了不和页面或组件的data中的造成混淆,state中的变量前面建议加上$符号
+ state: {
+ active: 4,
+ token: uni.getStorageSync('token') || '',
+ // 用户信息
+ userInfo: userInfo && JSON.parse(userInfo) || {}
+ getters: {
+ // 获取用户信息
+ getActive(state) {
+ return state.active
+ getToken(state) {
+ return state.token || uni.getStorageSync('token')
+ mutations: {
+ // 修改用户信息
+ changeUseinf(state, payload) {
+ payload().then(res => {
+ console.log('wxLoginRes', res, state);
+ state.userInfo = res
+ // state.userInfo = payload
+ changeActive(state, payload) {
+ state.active = payload
+ wxLogininfo(state, payload) {
+ console.log('wxLogin', state, payload)
+ uni.login({
+ provider: 'weixin', //使用微信登录
+ success: function ({ code }) {
+ console.log('loginRes', code);
+ payload({
+ code: code
+ }).then(res => {
+ console.log('wxLoginRes', res, code);
+ uni.$u.toast("登录成功");
+ state.token = res.token;
+ state.userInfo = res.user;
+ uni.setStorageSync('token', res.token);
+ uni.setStorageSync('userInfo', JSON.stringify(res.user));
+ uni.$u.route("/pages/home/webview",
+ url: 'https://sit-nanhuacs.4dage.com/web/index.html#/home',
+ data: 20
+export default store
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <link rel="shortcut icon" type="image/x-icon" href="https://cdn.uviewui.com/uview/common/favicon.webp">
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <title>
+ <%= htmlWebpackPlugin.options.title %>
+ </title>
+ <!-- 正式发布的时候使用,开发期间不启用。↓ -->
+ <script src="/static/common/js/touch-emulator.js"></script>
+ <script>
+ TouchEmulator();
+ </script>
+ <style>
+ ::-webkit-scrollbar{
+ </style>
+ <!-- 正式发布的时候使用,开发期间不启用。↑ -->
+ document.addEventListener('DOMContentLoaded', function() {
+ document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
+ <link rel="stylesheet" href="<%= BASE_URL %>static/index.css" />
+ </head>
+ <body>
+ <!-- 该文件为 H5 平台的模板 HTML,并非应用入口。 -->
+ <!-- 请勿在此文件编写页面代码或直接运行此文件。 -->
+ <!-- 详见文档:https://uniapp.dcloud.io/collocation/manifest?id=h5-template -->
+ <noscript>
+ <strong>本站点必须要开启JavaScript才能运行</strong>
+ </noscript>
+ <div id="app"></div>
+ <!-- built files will be auto injected -->
+ /*BAIDU_STAT*/
+ </body>
+</html>
@@ -0,0 +1,69 @@
+```
+.
+├── common #演示需要的一些文件
+│ ├── api.js
+│ ├── config.js
+│ ├── demo.scss
+│ ├── mixin.js
+│ └── props.js
+├── components #演示项目封装的组件
+│ └── page-nav
+│ └── page-nav.vue
+├── pages #页面
+│ ├── componentsA #分包A
+│ │ ├── ...
+│ ├── componentsB #分包B
+│ ├── componentsC #分包C
+│ └── example #演示项目首页
+│ ├── components.config.js #演示页面数据
+│ └── components.nvue #主演示页面
+├── static #演示项目需要的一些文件
+│ ├── app-plus
+│ │ └── mp-html
+│ ├── common
+│ │ └── js
+│ └── uview
+│ └── example
+├── store
+│ └── index.js
+├── uni_modules
+│ └── uview-ui #uView2.0主包
+│ ├── LICENSE
+│ ├── README.md
+│ ├── changelog.md
+│ ├── components #所有的组件
+│ ├── index.js
+│ ├── index.scss
+│ ├── libs
+│ ├── package.json
+│ └── theme.scss
+├── unpackage
+│ └── res
+│ └── icons
+├── util
+│ └── request
+│ ├── requestInterceptors.js
+│ └── responseInterceptors.js
+├── App.vue
+├── LICENSE
+├── main.js
+├── manifest.json
+├── package-lock.json
+├── pages.json #页面配置
+├── package.json
+├── README.md
+├── template.h5.html #h5模板
+├── tree.md
+├── uni.scss
+└── vue.config.js
+created by beiqiao.
@@ -0,0 +1,6 @@
+ * 下方引入的为uView UI的集成样式文件,为scss预处理器,其中包含了一些"u-"开头的自定义变量
+ * 使用的时候,请将下面的一行复制到您的uniapp项目根目录的uni.scss中即可
+ * uView自定义的css类名和scss变量,均以"u-"开头,不会造成冲突,请放心使用
+@import '@/uni_modules/uview-ui/theme.scss';
@@ -0,0 +1,200 @@
+## 0.9.6(2024-07-23)
+- fix: 修复 uni is not defined
+## 0.9.5(2024-07-19)
+- chore: 鸿蒙`measureText`为异步,异步字体不正常,使用模拟方式。
+## 0.9.4(2024-07-18)
+- chore: 更新文档
+## 0.9.3(2024-07-16)
+- feat: 鸿蒙 canvas 事件缺失,待官方修复,如何在鸿蒙使用请看文档`常见问题 vue3`
+## 0.9.2(2024-07-12)
+- chore: 删除多余文件
+## 0.9.1(2024-07-12)
+- fix: 修复 安卓5不显示图表问题
+## 0.9.0(2024-06-13)
+- chore: 合并nvue和uvue
+## 0.8.9(2024-05-19)
+## 0.8.8(2024-05-13)
+- chore: 更新文档和uvue示例
+## 0.8.7(2024-04-26)
+- fix: uniapp x需要HBX 4.13以上
+## 0.8.6(2024-04-10)
+- feat: 支持 uniapp x ios
+## 0.8.5(2024-04-03)
+- fix: 修复 nvue `reset`传值不生效问题
+- feat: 支持 uniapp x web
+## 0.8.4(2024-01-27)
+## 0.8.3(2024-01-21)
+## 0.8.2(2024-01-21)
+- feat: 支持 `uvue`
+## 0.8.1(2023-08-24)
+- fix: app 的`touch`事件为`object` 导致无法显示 `tooltip`
+## 0.8.0(2023-08-22)
+- fix: 离屏 报错问题
+- fix: 微信小程序PC无法使用事件
+## 0.7.9(2023-07-29)
+## 0.7.8(2023-07-29)
+## 0.7.7(2023-07-27)
+- chore: lime-echart 里的示例使用自定tooltips
+- feat: 对支持离屏的使用离屏创建(微信、字节、支付宝)
+## 0.7.6(2023-06-30)
+- fix: vue3 报`width`的错
+## 0.7.5(2023-05-25)
+- chore: 更新文档 和 demo, 使用`lime-echart`这个标签即可查看示例
+## 0.7.4(2023-05-22)
+- chore: 增加关于钉钉小程序上传时提示安全问题的说明及修改建议
+## 0.7.3(2023-05-16)
+- chore: 更新 vue3 非微信小程序平台可能缺少`wx`的说明
+## 0.7.2(2023-05-16)
+- chore: 更新 vue3 非微信小程序平台的可以缺少`wx`的说明
+## 0.7.1(2023-04-26)
+- chore: 更新demo,使用`lime-echart`这个标签即可查看示例
+- chore:微信小程序的`tooltip`文字有阴影,怀疑是微信的锅,临时解决方法是`tooltip.shadowBlur = 0`
+## 0.7.0(2023-04-24)
+- fix: 修复`setAttribute is not a function`
+## 0.6.9(2023-04-15)
+- chore: 更新文档,vue3请使用echarts esm的包
+## 0.6.8(2023-03-22)
+- feat: mac pc无法使用canvas 2d
+## 0.6.7(2023-03-17)
+- feat: 更新文档
+## 0.6.6(2023-03-17)
+- feat: 微信小程序PC已经支持canvas 2d,故去掉判断PC
+## 0.6.5(2022-11-03)
+- fix: 某些手机touches为对象,导致无法交互。
+## 0.6.4(2022-10-28)
+- fix: 优化点击事件的触发条件
+## 0.6.3(2022-10-26)
+- fix: 修复 dataZoom 拖动问题
+## 0.6.2(2022-10-23)
+- fix: 修复 飞书小程序 尺寸问题
+## 0.6.1(2022-10-19)
+- fix: 修复 PC mousewheel 事件 鼠标位置不准确的BUG,不兼容火狐!
+- feat: showLoading 增加传参
+## 0.6.0(2022-09-16)
+- feat: 增加PC的mousewheel事件
+## 0.5.4(2022-09-16)
+- fix: 修复 nvue 动态数据不显示问题
+## 0.5.3(2022-09-16)
+- feat: 增加enableHover属性, 在PC端时当鼠标进入显示tooltip,不必按下。
+## 0.5.2(2022-09-16)
+## 0.5.1(2022-09-16)
+- fix: 修复nvue报错
+## 0.5.0(2022-09-15)
+- feat: init(echarts, theme?:string, opts?:{}, callback: function(chart))
+## 0.4.8(2022-09-11)
+- feat: 增加 @finished
+## 0.4.7(2022-08-24)
+- chore: 去掉 stylus
+## 0.4.6(2022-08-24)
+- feat: 增加 beforeDelay
+## 0.4.5(2022-08-12)
+## 0.4.4(2022-08-12)
+- fix: 修复 resize 无参数时报错
+## 0.4.3(2022-08-07)
+# 评论有说本插件对新手不友好,让我做不好就不要发出来。 还有的说跟官网一样,发出来做什么,给我整无语了。
+# 所以在此提醒一下准备要下载的你,如果你从未使用过 echarts 请不要下载 或 谨慎下载。
+# 如果你确认要下载,麻烦看完文档。还有请注意插件是让echarts在uniapp能运行,API 配置请自行去官网查阅!
+# 如果你不会echarts 但又需要图表,市场上有个很优秀的图表插件 uchart 你可以去使用这款插件,uchart的作者人很好,也热情。
+# 每个人都有自己的本职工作,如果你能力强可以自行兼容,如果使用了他人的插件也麻烦尊重他人的成果和劳动时间。谢谢。
+# 为了心情愉悦,本人已经使用插件屏蔽差评。
+## 0.4.2(2022-07-20)
+- feat: 增加 resize
+## 0.4.1(2022-06-07)
+- fix: 修复 canvasToTempFilePath 不生效问题
+## 0.4.0(2022-06-04)
+- chore 为了词云 增加一个canvas 标签
+- 词云下载地址[echart-wordcloud](https://ext.dcloud.net.cn/plugin?id=8430)
+## 0.3.9(2022-06-02)
+- tips: lines 不支持 `trailLength`
+## 0.3.8(2022-05-31)
+- fix: 修复 因mouse事件冲突tooltip跳动问题
+## 0.3.7(2022-05-26)
+- chore: 设置默认宽高300px
+- fix: 修复 vue3 微信小程序 拖影BUG
+- chore: 支持PC
+## 0.3.5(2022-04-28)
+- chore: 更新使用方式
+- 🔔 必须使用hbuilderx 3.4.8-alpha以上
+## 0.3.4(2021-08-03)
+- chore: 增加 setOption的参数值
+## 0.3.3(2021-07-22)
+- fix: 修复 径向渐变报错的问题
+## 0.3.2(2021-07-09)
+- chore: 统一命名规范,无须主动引入组件
+## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example)
+## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example)
+## 0.3.1(2021-06-21)
+- fix: 修复 app-nvue ios is-enable 无效的问题
+## 0.3.0(2021-06-14)
+- fix: 修复 头条系小程序 2d 报 JSON.stringify 的问题
+- 目前 头条系小程序 2d 无法在开发工具上预览,划动图表页面无法滚动,axisLabel 字体颜色无法更改,建议使用非2d。
+## 0.2.9(2021-06-06)
+- fix: 修复 头条系小程序 2d 放大的BUG
+- 头条系小程序 2d 无法在开发工具上预览,也存在划动图表页面无法滚动的问题。
+## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
+## 0.2.8(2021-05-19)
+- fix: 修复 微信小程序 PC 显示过大的问题
+## 0.2.7(2021-05-19)
+- fix: 修复 微信小程序 PC 不显示问题
+## 0.2.6(2021-05-14)
+- feat: 支持 `image`
+- feat: props 增加 `ec.clear`,更新时是否先删除图表样式
+- feat: props 增加 `isDisableScroll` ,触摸图表时是否禁止页面滚动
+- feat: props 增加 `webviewStyles` ,webview 的样式, 仅nvue有效
+## 0.2.5(2021-05-13)
+- docs: 插件用到了css 预编译器 [stylus](https://ext.dcloud.net.cn/plugin?name=compile-stylus) 请安装它
+## 0.2.4(2021-05-12)
+- fix: 修复 百度平台 多个图表ctx 和 渐变色 bug
+- ## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
+## 0.2.3(2021-05-10)
+- feat: 增加 `canvasToTempFilePath` 方法,用于生成图片
+```js
+this.$refs.chart.canvasToTempFilePath({success: (res) => {
+ console.log('tempFilePath:', res.tempFilePath)
+}})
+## 0.2.2(2021-05-10)
+- feat: 增加 `dispose` 方法,用于销毁实例
+- feat: 增加 `isClickable` 是否派发点击
+- feat: 实验性的支持 `nvue` 使用要慎重考虑
+## 0.2.1(2021-05-06)
+- fix:修复 微信小程序 json 报错
+- chore: `reset` 更改为 `setChart`
+- feat: 增加 `isEnable` 开启初始化 启用这个后 无须再使用`init`方法
+```html
+<l-echart ref="chart" is-enable />
+// 显示加载
+this.$refs.chart.showLoading()
+// 使用实例回调
+this.$refs.chart.setChart(chart => ...code)
+// 直接设置图表配置
+this.$refs.chart.setOption(data)
+## 0.2.0(2021-05-05)
+- fix:修复 头条 百度 偏移的问题
+- docs: 更新文档
+## 0.1.0(2021-05-02)
+- chore: 第一次上传,基本全端兼容,使用方法与官网一致。
+- 已知BUG:非2d 无法使用背景色,已反馈官方
+- 已知BUG:头条 百度 有许些偏移
+- 后期计划:兼容nvue
+const cacheChart = {}
+const fontSizeReg = /([\d\.]+)px/;
+class EventEmit {
+ constructor() {
+ this.__events = {};
+ on(type, listener) {
+ if (!type || !listener) {
+ const events = this.__events[type] || [];
+ events.push(listener);
+ this.__events[type] = events;
+ emit(type, e) {
+ if (type.constructor === Object) {
+ e = type;
+ type = e && e.type;
+ if (!type) {
+ const events = this.__events[type];
+ if (!events || !events.length) {
+ events.forEach((listener) => {
+ listener.call(this, e);
+ off(type, listener) {
+ const __events = this.__events;
+ const events = __events[type];
+ if (!listener) {
+ delete __events[type];
+ for (let i = 0, len = events.length; i < len; i++) {
+ if (events[i] === listener) {
+ events.splice(i, 1);
+ i--;
+class Image {
+ this.currentSrc = null
+ this.naturalHeight = 0
+ this.naturalWidth = 0
+ this.width = 0
+ this.height = 0
+ this.tagName = 'IMG'
+ set src(src) {
+ this.currentSrc = src
+ uni.getImageInfo({
+ src,
+ this.naturalWidth = this.width = res.width
+ this.naturalHeight = this.height = res.height
+ this.onload()
+ fail: () => {
+ this.onerror()
+ get src() {
+ return this.currentSrc
+class OffscreenCanvas {
+ constructor(ctx, com, canvasId) {
+ this.tagName = 'canvas'
+ this.com = com
+ this.canvasId = canvasId
+ this.ctx = ctx
+ set width(w) {
+ this.com.offscreenWidth = w
+ set height(h) {
+ this.com.offscreenHeight = h
+ get width() {
+ return this.com.offscreenWidth || 0
+ get height() {
+ return this.com.offscreenHeight || 0
+ getContext(type) {
+ return this.ctx
+ getImageData() {
+ return new Promise((resolve, reject) => {
+ this.com.$nextTick(() => {
+ uni.canvasGetImageData({
+ x:0,
+ y:0,
+ width: this.com.offscreenWidth,
+ height: this.com.offscreenHeight,
+ canvasId: this.canvasId,
+ resolve(res)
+ fail: (err) => {
+ reject(err)
+ }, this.com)
+export class Canvas {
+ constructor(ctx, com, isNew, canvasNode={}) {
+ cacheChart[com.canvasId] = {ctx}
+ this.canvasId = com.canvasId;
+ this.chart = null;
+ this.isNew = isNew
+ this.canvasNode = canvasNode;
+ this.com = com;
+ if (!isNew) {
+ this._initStyle(ctx)
+ this._initEvent();
+ this._ee = new EventEmit()
+ if (type === '2d') {
+ return this.ctx;
+ setAttribute(key, value) {
+ if(key === 'aria-label') {
+ this.com['ariaLabel'] = value
+ setChart(chart) {
+ this.chart = chart;
+ createOffscreenCanvas(param){
+ if(!this.children) {
+ this.com.isOffscreenCanvas = true
+ this.com.offscreenWidth = param.width||300
+ this.com.offscreenHeight = param.height||300
+ const com = this.com
+ const canvasId = this.com.offscreenCanvasId
+ const context = uni.createCanvasContext(canvasId, this.com)
+ this._initStyle(context)
+ this.children = new OffscreenCanvas(context, com, canvasId)
+ return this.children
+ appendChild(child) {
+ console.log('child', child)
+ dispatchEvent(type, e) {
+ if(typeof type == 'object') {
+ this._ee.emit(type.type, type);
+ this._ee.emit(type, e);
+ return true
+ attachEvent() {
+ detachEvent() {
+ addEventListener(type, listener) {
+ this._ee.on(type, listener)
+ removeEventListener(type, listener) {
+ this._ee.off(type, listener)
+ _initCanvas(zrender, ctx) {
+ // zrender.util.getContext = function() {
+ // return ctx;
+ // };
+ // zrender.util.$override('measureText', function(text, font) {
+ // ctx.font = font || '12px sans-serif';
+ // return ctx.measureText(text, font);
+ _initStyle(ctx, child) {
+ const styles = [
+ 'fillStyle',
+ 'strokeStyle',
+ 'fontSize',
+ 'globalAlpha',
+ 'opacity',
+ 'textAlign',
+ 'textBaseline',
+ 'shadow',
+ 'lineWidth',
+ 'lineCap',
+ 'lineJoin',
+ 'lineDash',
+ 'miterLimit',
+ 'font',
+ ];
+ const colorReg = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b/g;
+ styles.forEach(style => {
+ Object.defineProperty(ctx, style, {
+ set: value => {
+ if (style === 'font' && fontSizeReg.test(value)) {
+ const match = fontSizeReg.exec(value);
+ ctx.setFontSize(match[1]);
+ if (style === 'opacity') {
+ ctx.setGlobalAlpha(value)
+ if (style !== 'fillStyle' && style !== 'strokeStyle' || value !== 'none' && value !== null) {
+ // #ifdef H5 || APP-PLUS || MP-BAIDU
+ if(typeof value == 'object') {
+ if (value.hasOwnProperty('colorStop') || value.hasOwnProperty('colors')) {
+ ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
+ // #ifdef MP-TOUTIAO
+ if(colorReg.test(value)) {
+ value = value.replace(colorReg, '#$1$1$2$2$3$3')
+ if(!this.isNew && !child) {
+ ctx.uniDrawImage = ctx.drawImage
+ ctx.drawImage = (...a) => {
+ a[0] = a[0].src
+ ctx.uniDrawImage(...a)
+ if(!ctx.createRadialGradient) {
+ ctx.createRadialGradient = function() {
+ return ctx.createCircularGradient(...[...arguments].slice(-3))
+ // 字节不支持
+ if (!ctx.strokeText) {
+ ctx.strokeText = (...a) => {
+ ctx.fillText(...a)
+ // 钉钉不支持 , 鸿蒙是异步
+ if (!ctx.measureText || uni.getSystemInfoSync().osName == 'harmonyos') {
+ ctx._measureText = ctx.measureText
+ const strLen = (str) => {
+ let len = 0;
+ for (let i = 0; i < str.length; i++) {
+ if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
+ len++;
+ len += 2;
+ return len;
+ ctx.measureText = (text, font) => {
+ let fontSize = ctx?.state?.fontSize || 12;
+ if (font) {
+ fontSize = parseInt(font.match(/([\d\.]+)px/)[1])
+ fontSize /= 2;
+ let isBold = fontSize >= 16;
+ const widthFactor = isBold ? 1.3 : 1;
+ // ctx._measureText(text, (res) => {})
+ width: strLen(text) * fontSize * widthFactor
+ _initEvent(e) {
+ this.event = {};
+ const eventNames = [{
+ wxName: 'touchStart',
+ ecName: 'mousedown'
+ }, {
+ wxName: 'touchMove',
+ ecName: 'mousemove'
+ wxName: 'touchEnd',
+ ecName: 'mouseup'
+ ecName: 'click'
+ }];
+ eventNames.forEach(name => {
+ this.event[name.wxName] = e => {
+ const touch = e.touches[0];
+ this.chart.getZr().handler.dispatch(name.ecName, {
+ zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
+ zrY: name.wxName === 'tap' ? touch.clientY : touch.y
+ this.canvasNode.width = w
+ this.canvasNode.height = h
+ return this.canvasNode.width || 0
+ return this.canvasNode.height || 0
+ get ctx() {
+ return cacheChart[this.canvasId]['ctx'] || null
+ set chart(chart) {
+ cacheChart[this.canvasId]['chart'] = chart
+ get chart() {
+ return cacheChart[this.canvasId]['chart'] || null
+export function dispatch(name, {x,y, wheelDelta}) {
+ this.dispatch(name, {
+ zrX: x,
+ zrY: y,
+ zrDelta: wheelDelta,
+ preventDefault: () => {},
+ stopPropagation: () =>{}
+export function setCanvasCreator(echarts, {canvas, node}) {
+ // echarts.setCanvasCreator(() => canvas);
+ if(echarts && !echarts.registerPreprocessor) {
+ return console.warn('echarts 版本不对或未传入echarts,vue3请使用esm格式')
+ echarts.registerPreprocessor(option => {
+ if (option && option.series) {
+ if (option.series.length > 0) {
+ option.series.forEach(series => {
+ series.progressive = 0;
+ } else if (typeof option.series === 'object') {
+ option.series.progressive = 0;
+ function loadImage(src, onload, onerror) {
+ let img = null
+ if(node && node.createImage) {
+ img = node.createImage()
+ img.onload = onload.bind(img);
+ img.onerror = onerror.bind(img);
+ img.src = src;
+ return img
+ img = new Image()
+ img.onload = onload.bind(img)
+ img.src = src
+ if(echarts.setPlatformAPI) {
+ echarts.setPlatformAPI({
+ loadImage: canvas.setChart ? loadImage : null,
+ createCanvas(){
+ const key = 'createOffscreenCanvas'
+ return uni.canIUse(key) && uni[key] ? uni[key]({type: '2d'}) : canvas
@@ -0,0 +1,252 @@
+ <!-- #ifdef APP -->
+ <web-view class="lime-echart" ref="chartRef" @load="loaded" :style="[customStyle]"
+ :webview-styles="[webviewStyles]" src="/uni_modules/lime-echart/static/uvue.html?v=10112">
+ </web-view>
+ <!-- #ifdef H5 -->
+ <div class="lime-echart" ref="chartRef"></div>
+<script lang="uts" setup>
+ // @ts-nocheck
+ import { Echarts } from './uvue';
+ type EchartsResolve = (value : Echarts) => void
+ defineOptions({
+ name: 'l-echart'
+ const emits = defineEmits(['finished'])
+ const props = defineProps({
+ // #ifdef APP
+ webviewStyles: {
+ type: Object
+ customStyle: {
+ // #ifndef APP
+ type: [String, Object]
+ isDisableScroll: {
+ default: false
+ isClickable: {
+ default: true
+ enableHover: {
+ beforeDelay: {
+ default: 30
+ const finished = ref(false)
+ const map = [] as EchartsResolve[]
+ const callbackMap = [] as EchartsResolve[]
+ // let context = null as UniWebViewElement | null
+ let chart = null as Echarts | null
+ let chartRef = ref<UniWebViewElement | null>(null)
+ const trigger = () => {
+ if (finished.value) {
+ if (chart == null) {
+ chart = new Echarts(chartRef.value!)
+ while (map.length > 0) {
+ const resolve = map.pop() as EchartsResolve
+ resolve(chart!)
+ if(chart != null){
+ while(callbackMap.length > 0){
+ const callback = callbackMap.pop() as EchartsResolve
+ callback(chart!)
+ const loaded = (event : UniWebViewLoadEvent) => {
+ event.stopPropagation()
+ event.preventDefault()
+ finished.value = true
+ trigger()
+ emits('finished')
+ const _next = () : boolean => {
+ console.warn(`组件还未初始化,请先使用 init`)
+ return false
+ const setOption = (option : UTSJSONObject) => {
+ if (_next()) return
+ chart!.setOption(option);
+ const showLoading = () => {
+ chart!.showLoading();
+ const hideLoading = () => {
+ chart!.hideLoading();
+ const clear = () => {
+ chart!.clear();
+ const dispose = () => {
+ chart!.dispose();
+ const resize = (size : UTSJSONObject) => {
+ chart!.resize(size);
+ const canvasToTempFilePath = (opt : UTSJSONObject) => {
+ chart!.canvasToTempFilePath(opt);
+ // function init() : Promise<Echarts> {
+ // return new Promise((resolve) => {
+ // map.push(resolve)
+ // trigger()
+ function init(callback : ((chart : Echarts) => void) | null) : Promise<Echarts> {
+ // if (chart !== null && callback != null) {
+ // callback(chart!)
+ // console.warn('echarts 未加载完成,您可以延时一下')
+ if(callback!=null){
+ callbackMap.push(callback)
+ return new Promise<Echarts>((resolve) => {
+ map.push(resolve)
+ const touchstart = (e) => {
+ if(chart == null) return
+ const handler = chart.getZr().handler;
+ const rect = chart.getZr().dom.getBoundingClientRect()
+ handler.dispatch('mousedown', {
+ zrX: e.touches[0].clientX - rect.left,
+ zrY: e.touches[0].clientY - rect.top
+ handler.dispatch('click', {
+ const touchmove = (e) => {
+ handler.dispatch('mousemove', {
+ const mouseup = (e) => {
+ zrX: 999999999,
+ zrY: 999999999
+ handler.dispatch('mouseup', {
+ function init(echarts: any, ...args: any[]): Promise<Echarts>{
+ if(echarts == null){
+ console.error('请确保已经引入了 ECharts 库');
+ return Promise.reject('请确保已经引入了 ECharts 库');
+ let theme:string|null=null
+ let opts={}
+ let callback:Function|null=null;
+ args.forEach(item =>{
+ if(typeof item === 'function') {
+ callback = item
+ } else if(['string'].includes(typeof item)){
+ theme = item
+ } else if(typeof item === 'object'){
+ opts = item
+ chart = echarts.init(chartRef.value, theme, opts)
+ window.addEventListener('touchstart', touchstart)
+ window.addEventListener('touchmove', touchmove)
+ window.addEventListener('touchend', mouseup)
+ if(callback!=null && typeof callback == 'function'){
+ onMounted(()=>{
+ onUnmounted(()=>{
+ window.removeEventListener('touchstart', touchstart)
+ window.removeEventListener('touchmove', touchmove)
+ window.removeEventListener('touchend', mouseup)
+ defineExpose({
+ init,
+ setOption,
+ showLoading,
+ hideLoading,
+ clear,
+ dispose,
+ resize,
+ canvasToTempFilePath
+ .lime-echart {
@@ -0,0 +1,514 @@
+ <view class="lime-echart" style="width: 100%; height: 100%" v-if="canvasId" ref="limeEchart" :aria-label="ariaLabel">
+ <!-- #ifndef APP-NVUE -->
+ class="lime-echart__canvas"
+ v-if="use2dCanvas"
+ type="2d"
+ :id="canvasId"
+ style="width: 100%; height: 100%"
+ :disable-scroll="isDisableScroll"
+ @touchstart="touchStart"
+ @touchmove="touchMove"
+ @touchend="touchEnd"
+ :width="nodeWidth"
+ :height="nodeHeight"
+ :style="canvasStyle"
+ :canvas-id="canvasId"
+ <view class="lime-echart__mask"
+ v-if="isPC"
+ @mousedown="touchStart"
+ @mousemove="touchMove"
+ @mouseup="touchEnd"
+ @touchend="touchEnd">
+ <canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas>
+ <!-- #ifdef APP-NVUE -->
+ :webview-styles="webviewStyles"
+ src="/uni_modules/lime-echart/static/uvue.html?v=1"
+ @pagefinish="finished = true"
+ @onPostMessage="onMessage"
+// #ifndef APP-NVUE
+import {Canvas, setCanvasCreator, dispatch} from './canvas';
+import {wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect} from './utils';
+// #ifdef APP-NVUE
+import { base64ToPath, sleep } from './utils';
+import {Echarts} from './nvue'
+const charts = {}
+const echartsObj = {}
+ * LimeChart 图表
+ * @description 全端兼容的eCharts
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=4899
+ * @property {String} customStyle 自定义样式
+ * @property {String} type 指定 canvas 类型
+ * @value 2d 使用canvas 2d,部分小程序支持
+ * @value '' 使用原生canvas,会有层级问题
+ * @value bottom right 不缩放图片,只显示图片的右下边区域
+ * @property {Boolean} isDisableScroll
+ * @property {number} beforeDelay = [30] 延迟初始化 (毫秒)
+ * @property {Boolean} enableHover PC端使用鼠标悬浮
+ * @event {Function} finished 加载完成触发
+ name: 'lime-echart',
+ // #ifdef MP-WEIXIN || MP-TOUTIAO
+ default: '2d'
+ // #ifdef APP-NVUE
+ webviewStyles: Object,
+ // hybrid: Boolean,
+ customStyle: String,
+ isDisableScroll: Boolean,
+ enableHover: Boolean,
+ // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
+ use2dCanvas: true,
+ // #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
+ use2dCanvas: false,
+ ariaLabel: '图表',
+ width: null,
+ height: null,
+ nodeWidth: null,
+ nodeHeight: null,
+ // canvasNode: null,
+ config: {},
+ inited: false,
+ finished: false,
+ file: '',
+ platform: '',
+ isPC: false,
+ isDown: false,
+ isOffscreenCanvas: false,
+ offscreenWidth: 0,
+ offscreenHeight: 0
+ canvasId() {
+ return `lime-echart${this._ && this._.uid || this._uid}`
+ offscreenCanvasId() {
+ return `${this.canvasId}_offscreen`
+ offscreenStyle() {
+ return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
+ canvasStyle() {
+ return this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : ''
+ // #ifndef VUE3
+ beforeDestroy() {
+ this.clear()
+ this.dispose()
+ if(this.isPC) {
+ document.removeEventListener('mousewheel', this.mousewheel)
+ // #ifdef VUE3
+ created() {
+ if(!('ontouchstart' in window)) {
+ this.isPC = true
+ document.addEventListener('mousewheel', this.mousewheel)
+ const { platform } = uni.getSystemInfoSync();
+ this.isPC = /windows/i.test(platform)
+ this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
+ this.$emit('finished')
+ onMessage(e) {
+ const detail = e?.detail?.data[0] || null;
+ const data = detail?.data
+ const key = detail?.event
+ const options = data?.options
+ const event = data?.event
+ const file = detail?.file
+ if (key == 'log' && data) {
+ console.log(data)
+ if(event) {
+ this.chart.dispatchAction(event.replace(/"/g,''), options)
+ if(file) {
+ thie.file = file
+ setChart(callback) {
+ if(!this.chart) {
+ if(typeof callback === 'function' && this.chart) {
+ callback(this.chart);
+ if(typeof callback === 'function') {
+ this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`);
+ setOption() {
+ if (!this.chart || !this.chart.setOption) {
+ this.chart.setOption(...arguments);
+ showLoading() {
+ if(this.chart) {
+ this.chart.showLoading(...arguments)
+ hideLoading() {
+ this.chart.hideLoading()
+ clear() {
+ this.chart.clear()
+ dispose() {
+ this.chart.dispose()
+ resize(size) {
+ if(size && size.width && size.height) {
+ this.height = size.height
+ this.width = size.width
+ if(this.chart) {this.chart.resize(size)}
+ uni.createSelectorQuery()
+ .in(this)
+ .select(`.lime-echart`)
+ .boundingClientRect()
+ .exec(res => {
+ if (res) {
+ let { width, height } = res[0];
+ this.width = width = width || 300;
+ this.height = height = height || 300;
+ this.chart.resize({width, height})
+ canvasToTempFilePath(args = {}) {
+ // #ifndef APP-NVUE
+ const { use2dCanvas, canvasId } = this;
+ const copyArgs = Object.assign({
+ canvasId,
+ success: resolve,
+ fail: reject
+ }, args);
+ if (use2dCanvas) {
+ delete copyArgs.canvasId;
+ copyArgs.canvas = this.canvasNode;
+ uni.canvasToTempFilePath(copyArgs, this);
+ this.file = ''
+ this.$refs.webview.evalJs(`canvasToTempFilePath()`);
+ this.$watch('file', async (file) => {
+ const tempFilePath = await base64ToPath(file)
+ resolve(args.success({tempFilePath}))
+ reject(args.fail({error: ``}))
+ async init(echarts, ...args) {
+ if(args && args.length == 0 && !echarts) {
+ console.error('缺少参数:init(echarts, theme?:string, opts?: object, callback?: function)')
+ let theme=null,opts={},callback;
+ Array.from(arguments).forEach(item => {
+ if(['string'].includes(typeof item)) {
+ if(typeof item === 'object') {
+ if(this.beforeDelay) {
+ await sleep(this.beforeDelay)
+ let config = await this.getContext();
+ setCanvasCreator(echarts, config)
+ try {
+ this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
+ callback(this.chart)
+ return this.chart
+ } catch(e) {
+ console.error(e.messges)
+ return null
+ this.chart = new Echarts(this.$refs.webview)
+ this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
+ if(callback) {
+ getContext() {
+ if(this.finished) {
+ return Promise.resolve(this.finished)
+ this.$watch('finished', (val) => {
+ if(val) {
+ resolve(this.finished)
+ return getRect(`#${this.canvasId}`, {context: this, type: this.use2dCanvas ? 'fields': 'boundingClientRect'}).then(res => {
+ if(res) {
+ let dpr = devicePixelRatio
+ let {width, height, node} = res
+ let canvas;
+ if(node) {
+ const ctx = node.getContext('2d');
+ canvas = new Canvas(ctx, this, true, node);
+ this.canvasNode = node
+ dpr = !this.isPC ? devicePixelRatio : 1// 1.25
+ // #ifndef MP-ALIPAY || MP-TOUTIAO
+ dpr = this.isPC ? devicePixelRatio : 1
+ // #ifdef MP-ALIPAY || MP-LARK
+ dpr = devicePixelRatio
+ // #ifdef WEB
+ dpr = 1
+ this.rect = res
+ this.nodeWidth = width * dpr;
+ this.nodeHeight = height * dpr;
+ const ctx = uni.createCanvasContext(this.canvasId, this);
+ canvas = new Canvas(ctx, this, false);
+ return { canvas, width, height, devicePixelRatio: dpr, node };
+ return {}
+ getRelative(e, touches) {
+ let { clientX, clientY } = e
+ if(!(clientX && clientY) && touches && touches[0]) {
+ clientX = touches[0].clientX
+ clientY = touches[0].clientY
+ return {x: clientX - this.rect.left, y: clientY - this.rect.top, wheelDelta: e.wheelDelta || 0}
+ getTouch(e, touches) {
+ const {x} = touches && touches[0] || {}
+ return x ? touches[0] : this.getRelative(e, touches);
+ touchStart(e) {
+ this.isDown = true
+ const next = () => {
+ const touches = convertTouchesToArray(e.touches)
+ const touch = this.getTouch(e, touches)
+ this.startX = touch.x
+ this.startY = touch.y
+ this.startT = new Date()
+ const handler = this.chart.getZr().handler;
+ dispatch.call(handler, 'mousedown', touch)
+ dispatch.call(handler, 'mousemove', touch)
+ handler.processGesture(wrapTouch(e), 'start');
+ clearTimeout(this.endTimer);
+ getRect(`#${this.canvasId}`, {context: this}).then(res => {
+ next()
+ touchMove(e) {
+ if(this.isPC && this.enableHover && !this.isDown) {this.isDown = true}
+ if (this.chart && this.isDown) {
+ dispatch.call(handler, 'mousemove', this.getTouch(e, touches))
+ handler.processGesture(wrapTouch(e), 'change');
+ touchEnd(e) {
+ this.isDown = false
+ if (this.chart) {
+ const touches = convertTouchesToArray(e.changedTouches)
+ const touch = (x ? touches[0] : this.getRelative(e, touches)) || {};
+ const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
+ dispatch.call(handler, 'mouseup', touch)
+ handler.processGesture(wrapTouch(e), 'end');
+ if(isClick) {
+ dispatch.call(handler, 'click', touch)
+ this.endTimer = setTimeout(() => {
+ dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
+ dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
+ },50)
+ mousewheel(e){
+ dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
+.lime-echart {
+ /* #ifdef APP-NVUE */
+.lime-echart__canvas {
+/* #ifndef APP-NVUE */
+.lime-echart__mask {
+/* #endif */
@@ -0,0 +1,51 @@
+export class Echarts {
+ eventMap = new Map()
+ constructor(webview) {
+ this.webview = webview
+ this.options = null
+ this.options = arguments
+ this.webview.evalJs(`setOption(${JSON.stringify(arguments)})`);
+ getOption() {
+ return this.options
+ this.webview.evalJs(`showLoading(${JSON.stringify(arguments)})`);
+ this.webview.evalJs(`hideLoading()`);
+ this.webview.evalJs(`clear()`);
+ this.webview.evalJs(`dispose()`);
+ if(size) {
+ this.webview.evalJs(`resize(${JSON.stringify(size)})`);
+ this.webview.evalJs(`resize()`);
+ on(type, ...args) {
+ const query = args[0]
+ const useQuery = query && typeof query != 'function'
+ const param = useQuery ? [type, query] : [type]
+ const key = `${type}${useQuery ? JSON.stringify(query): '' }`
+ const callback = useQuery ? args[1]: args[0]
+ if(typeof callback == 'function'){
+ this.eventMap.set(key, callback)
+ this.webview.evalJs(`on(${JSON.stringify(param)})`);
+ console.warn('nvue 暂不支持事件')
+ dispatchAction(type, options){
+ const handler = this.eventMap.get(type)
+ if(handler){
+ handler(options)
+// 计算版本
+export function compareVersion(v1, v2) {
+ v1 = v1.split('.')
+ v2 = v2.split('.')
+ const len = Math.max(v1.length, v2.length)
+ while (v1.length < len) {
+ v1.push('0')
+ while (v2.length < len) {
+ v2.push('0')
+ for (let i = 0; i < len; i++) {
+ const num1 = parseInt(v1[i], 10)
+ const num2 = parseInt(v2[i], 10)
+ if (num1 > num2) {
+ return 1
+ } else if (num1 < num2) {
+ return -1
+ return 0
+const systemInfo = uni.getSystemInfoSync();
+function gte(version) {
+ // 截止 2023-03-22 mac pc小程序不支持 canvas 2d
+ let {
+ SDKVersion,
+ platform
+ } = systemInfo;
+ // #ifdef MP-ALIPAY
+ SDKVersion = my.SDKVersion
+ return platform !== 'mac' && compareVersion(SDKVersion, version) >= 0;
+ return compareVersion(SDKVersion, version) >= 0;
+export function canIUseCanvas2d() {
+ return gte('2.9.0');
+ return gte('2.7.0');
+ return gte('1.78.0');
+export function convertTouchesToArray(touches) {
+ // 如果 touches 是一个数组,则直接返回它
+ if (Array.isArray(touches)) {
+ return touches;
+ // 如果touches是一个对象,则转换为数组
+ if (typeof touches === 'object' && touches !== null) {
+ return Object.values(touches);
+ // 对于其他类型,直接返回它
+export function wrapTouch(event) {
+ for (let i = 0; i < event.touches.length; ++i) {
+ const touch = event.touches[i];
+ touch.offsetX = touch.x;
+ touch.offsetY = touch.y;
+ return event;
+export const devicePixelRatio = uni.getSystemInfoSync().pixelRatio
+export function base64ToPath(base64) {
+ const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
+ const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
+ bitmap.loadBase64Data(base64, () => {
+ if (!format) {
+ reject(new Error('ERROR_BASE64SRC_PARSE'))
+ const time = new Date().getTime();
+ const filePath = `_doc/uniapp_temp/${time}.${format}`
+ bitmap.save(filePath, {},
+ () => {
+ bitmap.clear()
+ resolve(filePath)
+ (error) => {
+ console.error(`${JSON.stringify(error)}`)
+ reject(error)
+ }, (error) => {
+export function sleep(time) {
+ return new Promise((resolve) => {
+ resolve(true)
+ }, time)
+export function getRect(selector, options = {}) {
+ const typeDefault = 'boundingClientRect'
+ const {
+ context,
+ type = typeDefault
+ } = options
+ const dom = uni.createSelectorQuery().in(context).select(selector);
+ const result = (rect) => {
+ if (rect) {
+ resolve(rect)
+ reject()
+ if (type == typeDefault) {
+ dom[type](result).exec()
+ dom[type]({
+ rect: true
+ }, result).exec()
@@ -0,0 +1,133 @@
+// @ts-nocheck
+// #ifdef APP
+type EchartsEventHandler = (event: UTSJSONObject)=>void
+// type EchartsTempResolve = (obj : UTSJSONObject) => void
+// type EchartsTempOptions = UTSJSONObject
+ options: UTSJSONObject = {} as UTSJSONObject
+ context: UniWebViewElement
+ eventMap: Map<string, EchartsEventHandler> = new Map()
+ private temp: UTSJSONObject[] = []
+ constructor(context: UniWebViewElement){
+ this.context = context
+ this.init()
+ init(){
+ this.context.evalJS(`init(null, null, ${JSON.stringify({})})`)
+ this.context.addEventListener('message', (e : UniWebViewMessageEvent) => {
+ // event.stopPropagation()
+ // event.preventDefault()
+ const detail = e.detail.data[0]
+ const file = detail.getString('file')
+ const data = detail.get('data')
+ const key = detail.getString('event')
+ const options = typeof data == 'object' ? (data as UTSJSONObject).getJSON('options'): null
+ const event = typeof data == 'object' ? (data as UTSJSONObject).getString('event'): null
+ if (key == 'log' && data != null) {
+ if (event != null && options != null) {
+ this.dispatchAction(event.replace(/"/g,''), options)
+ if(file != null){
+ while (this.temp.length > 0) {
+ const opt = this.temp.pop()
+ const success = opt?.get('success')
+ if(typeof success == 'function'){
+ success as (res: UTSJSONObject) => void
+ success({tempFilePath: file})
+ setOption(option: UTSJSONObject){
+ this.options = option;
+ this.context.evalJS(`setOption(${JSON.stringify([option])})`)
+ setOption(option: UTSJSONObject, notMerge: boolean = false, lazyUpdate: boolean = false){
+ this.context.evalJS(`setOption(${JSON.stringify([option, notMerge, lazyUpdate])})`)
+ setOption(option: UTSJSONObject, notMerge: UTSJSONObject){
+ this.context.evalJS(`setOption(${JSON.stringify([option, notMerge])})`)
+ getOption(): UTSJSONObject {
+ showLoading(){
+ this.context.evalJS(`showLoading(${JSON.stringify([] as any[])})`);
+ showLoading(type: string, opts: UTSJSONObject){
+ this.context.evalJS(`showLoading(${JSON.stringify([type, opts])})`);
+ hideLoading(){
+ this.context.evalJS(`hideLoading()`);
+ clear(){
+ this.context.evalJS(`clear()`);
+ dispose(){
+ this.context.evalJS(`dispose()`);
+ resize(size:UTSJSONObject){
+ this.context.evalJS(`resize(${JSON.stringify(size)})`);
+ },0)
+ resize(){
+ this.context.evalJS(`resize()`);
+ },10)
+ on(type:string, query: any, callback: EchartsEventHandler) {
+ const key = `${type}${JSON.stringify(query)}`
+ this.context.evalJS(`on(${JSON.stringify([type, query])})`);
+ console.warn('uvue 暂不支持事件')
+ on(type:string, callback: EchartsEventHandler) {
+ const key = `${type}`
+ this.context.evalJS(`on(${JSON.stringify([type])})`);
+ dispatchAction(type:string, options: UTSJSONObject){
+ if(handler!=null){
+ canvasToTempFilePath(opt: UTSJSONObject){
+ // this.context.evalJS(`on(${JSON.stringify(opt)})`);
+ this.context.evalJS(`canvasToTempFilePath(${JSON.stringify(opt)})`);
+ this.temp.push(opt)
+// #ifndef APP
+ constructor() {}
+ setOption(option: UTSJSONObject): void
+ isDisposed(): boolean;
+ clear(): void;
+ resize(size:UTSJSONObject): void;
+ resize(): void;
+ canvasToTempFilePath(opt : UTSJSONObject): void;
+ dispose(): void;
+ showLoading(cfg?: UTSJSONObject): void;
+ showLoading(name?: string, cfg?: UTSJSONObject): void;
+ hideLoading(): void;
+ getZr(): any
@@ -0,0 +1,159 @@
+ <view style="width: 100%; height: 408px;">
+ <l-echart ref="chartRef" @finished="init"></l-echart>
+ showTip: false,
+ option: {
+ trigger: 'axis',
+ // shadowBlur: 0,
+ textStyle: {
+ textShadowBlur: 0
+ renderMode: 'richText',
+ legend: {
+ data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ containLabel: true
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
+ yAxis: {
+ type: 'value'
+ name: '邮件营销',
+ type: 'line',
+ stack: '总量',
+ data: [120, 132, 101, 134, 90, 230, 210]
+ name: '联盟广告',
+ data: [220, 182, 191, 234, 290, 330, 310]
+ name: '视频广告',
+ data: [150, 232, 201, 154, 190, 330, 410]
+ name: '直接访问',
+ data: [320, 332, 301, 334, 390, 330, 320]
+ name: '搜索引擎',
+ data: [820, 932, 901, 934, 1290, 1330, 1320]
+ console.log('lime echarts nvue')
+ init() {
+ const chartRef = this.$refs['chartRef']
+ chartRef.init(chart => {
+ chart.setOption(this.option);
+ },1000)
+ save() {
+ // this.$refs.chart.canvasToTempFilePath({
+ // success(res) {
+ // console.log('res::::', res)
@@ -0,0 +1,160 @@
+ <view style="width: 100%; height: 408px;background-color: aqua;">
+ import * as echarts from 'echarts/dist/echarts.esm.js'
+ const chartRef = ref<LEchartComponentPublicInstance|null>(null)
+ // formatter: async (params: any) => {
+ // console.log('params', params)
+ // return 1
+ const init = async () =>{
+ if(chartRef.value== null) return
+ const chart = await chartRef.value!.init(null)
+ const chart = await chartRef.value!.init(echarts, null)
+ chart.setOption(option)
+ chart.on('mouseover', function (params) {
+ console.log('params', params);
+ // setTimeout(()=> {
+ // const option1 = {
+ // tooltip: {
+ // trigger: 'axis',
+ // // shadowBlur: 0,
+ // textStyle: {
+ // textShadowBlur: 0
+ // renderMode: 'richText',
+ // legend: {
+ // data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
+ // grid: {
+ // left: '3%',
+ // right: '4%',
+ // bottom: '3%',
+ // containLabel: true
+ // xAxis: {
+ // type: 'category',
+ // boundaryGap: false,
+ // data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
+ // yAxis: {
+ // type: 'value'
+ // series: [
+ // {
+ // name: '邮件营销',
+ // type: 'line',
+ // stack: '总量',
+ // data: [820, 132, 101, 134, 90, 230, 210]
+ // name: '联盟广告',
+ // data: [220, 182, 191, 234, 290, 330, 310]
+ // name: '视频广告',
+ // data: [950, 232, 201, 154, 190, 330, 410]
+ // name: '直接访问',
+ // data: [320, 332, 301, 334, 390, 330, 320]
+ // name: '搜索引擎',
+ // data: [820, 932, 901, 934, 1290, 1330, 1320]
+ // ]
+ // chart.setOption(option1)
+ // },1000)
@@ -0,0 +1,226 @@
+ <view >
+ <view style="height: 750rpx; position: relative">
+ <l-echart ref="chart" @finished="init"></l-echart>
+ <view class="customTooltips" :style="{left: position[0] + 'px',top: position[1] + 'px'}" v-if="params.length && position.length && showTip">
+ <view>这是个自定的tooltips</view>
+ <view>{{params[0]['axisValue']}}</view>
+ <view v-for="item in params">
+ <text>{{item.seriesName}}</text>
+ <text>{{item.value}}</text>
+ // nvue 不需要引入
+ // #ifdef VUE2
+ import * as echarts from '@/uni_modules/lime-echart/static/echarts.min';
+ // #ifdef MP
+ // 由于vue3 使用vite 不支持umd格式的包,小程序依然可以使用,但需要使用require
+ const echarts = require('../../static/echarts.min');
+ // #ifndef MP
+ // 由于 vue3 使用vite 不支持umd格式的包,故引入npm的包
+ import * as echarts from 'echarts/dist/echarts.esm';
+ position: [],
+ params: [],
+ textShadowBlur : 0
+ position: (point, params, dom, rect, size) => {
+ // 假设自定义的tooltips尺寸
+ const box = [170, 170]
+ // 偏移
+ const offsetX = point[0] < size.viewSize[0] / 2 ? 20 : -box[0] - 20;
+ const offsetY = point[1] < size.viewSize[1] / 2 ? 20 : -box[1] - 20;
+ const x = point[0] + offsetX;
+ const y = point[1] + offsetY;
+ this.position = [x, y]
+ this.params = params
+ formatter: (params, ticket, callback) => {
+ // init(echarts, theme?:string, opts?:{}, chart => {})
+ // echarts 必填, 非nvue必填,nvue不用填
+ // theme 可选,应用的主题,目前只支持名称,如:'dark'
+ // opts = { // 可选
+ // locale?: string // 从 `5.0.0` 开始支持
+ // chart => {} , callback 返回图表实例
+ // setTimeout(()=>{
+ // this.$refs.chart.init(echarts, chart => {
+ // chart.setOption(this.option);
+ // },300)
+ this.$refs.chart.init(echarts, chart => {
+ // 监听tooltip显示事件
+ chart.on('showTip', (params) => {
+ this.showTip = true
+ console.log('showTip::')
+ chart.on('hideTip', (params) => {
+ this.showTip = false
+ },300)
+ data: [1120, 132, 101, 134, 90, 230, 210]
+ data: [150, 632, 201, 154, 190, 330, 410]
+ data: [820, 332, 301, 334, 390, 330, 320]
+ this.$refs.chart.canvasToTempFilePath({
+ console.log('res::::', res)
+ .customTooltips {
+ background-color: rgba(255, 255, 255, 0.8);
+ padding: 20rpx;
@@ -0,0 +1,90 @@
+ "id": "lime-echart",
+ "displayName": "echarts",
+ "version": "0.9.6",
+ "description": "echarts 全端兼容,一款使echarts图表能跑在uniapp各端中的插件, 支持uniapp/uniappx(web,ios,安卓)",
+ "echarts",
+ "canvas",
+ "图表",
+ "可视化"
+],
+ "repository": "https://gitee.com/liangei/lime-echart",
+ "engines": {
+ "HBuilderX": "^3.6.4"
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ "sourcecode": {
+ "contact": {
+ "qq": ""
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ "npmurl": "",
+ "type": "component-vue"
+ "uni_modules": {
+ "dependencies": [],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y",
+ "alipay": "n"
+ "client": {
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y",
+ "app-uvue": "y"
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ "H5-pc": {
+ "Chrome": "u",
+ "IE": "u",
+ "Edge": "u",
+ "Firefox": "u",
+ "Safari": "u"
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ "echarts": "^5.4.1",
+ "zrender": "^5.4.3"
@@ -0,0 +1,406 @@
+# echarts 图表 <span style="font-size:16px;">👑👑👑👑👑 <span style="background:#ff9d00;padding:2px 4px;color:#fff;font-size:10px;border-radius: 3px;">全端</span></span>
+> 一个基于 JavaScript 的开源可视化图表库 [查看更多](https://limeui.qcoon.cn/#/echart) <br>
+> 基于 echarts 做了兼容处理,更多示例请访问 [uni示例](https://limeui.qcoon.cn/#/echart-example) | [官方示例](https://echarts.apache.org/examples/zh/index.html) <br>
+## 平台兼容
+| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
+| --- | ---------- | ------------ | ---------- | ---------- | --------- | ---- |
+| √ | √ | √ | √ | √ | √ | √ |
+## 安装
+- 第一步:在市场导入 [百度图表](https://ext.dcloud.net.cn/plugin?id=4899)
+- 第二步:选择插件依赖:<br>
+ 1、可以选插件内的`echarts`包或自定义包,自定义包[下载地址](https://echarts.apache.org/zh/builder.html)<br>
+ 2、或者使用`npm`安装`echarts`
+**注意**
+* 🔔 echarts 5.3.0及以上
+* 🔔 如果是 `cli` 项目请下载插件到`src`目录下的`uni_modules`,没有这个目录就创建一个
+## 代码演示
+### Vue2
+- 引入依赖,可以是插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html),也可以是`npm`包
+<view style="width:750rpx; height:750rpx"><l-echart ref="chartRef" @finished="init"></l-echart></view>
+// 插件内的 三选一
+import * as echarts from '@/uni_modules/lime-echart/static/echarts.min'
+// 自定义的 三选一 下载后放入项目的路径
+import * as echarts from 'xxx/echarts.min'
+// npm包 三选一 需要在控制台 输入命令:npm install echarts
+import * as echarts from 'echarts'
+ axisPointer: {
+ type: 'shadow'
+ confine: true
+ data: ['热度', '正面', '负面']
+ left: 20,
+ right: 20,
+ bottom: 15,
+ top: 40,
+ xAxis: [
+ type: 'value',
+ axisLine: {
+ lineStyle: {
+ color: '#999999'
+ axisLabel: {
+ color: '#666666'
+ yAxis: [
+ axisTick: { show: false },
+ data: ['汽车之家', '今日头条', '百度贴吧', '一点资讯', '微信', '微博', '知乎'],
+ name: '热度',
+ type: 'bar',
+ position: 'inside'
+ data: [300, 270, 340, 344, 300, 320, 310],
+ name: '正面',
+ data: [120, 102, 141, 174, 190, 250, 220]
+ name: '负面',
+ position: 'left'
+ data: [-20, -32, -21, -34, -90, -130, -110]
+ // 组件能被调用必须是组件的节点已经被渲染到页面上
+ async init() {
+ // chart 图表实例不能存在data里
+ const chart = await this.$refs.chartRef.init(echarts);
+ chart.setOption(this.option)
+### Vue3
+- 小程序可以使用`require`引入插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html)
+- `require`仅支持相对路径,不支持路径别名
+- 非小程序使用 `npm` 包
+<view style="width:750rpx; height:750rpx"><l-echart ref="chartRef"></l-echart></view>
+// 小程序 二选一
+// 插件内的 二选一
+const echarts = require('../../uni_modules/lime-echart/static/echarts.min');
+// 自定义的 二选一 下载后放入项目的路径
+const echarts = require('xxx/xxx/echarts');
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// 非小程序
+// 需要在控制台 输入命令:npm install echarts
+const chartRef = ref(null)
+const option = {
+onMounted( ()=>{
+ setTimeout(async()=>{
+ if(!chartRef.value) return
+ const myChart = await chartRef.value.init(echarts)
+ myChart.setOption(option)
+### Uvue
+- Uvue和Nvue不需要引入`echarts`,因为它们的实现方式是`webview`
+- uniapp x需要HBX 4.13以上
+<view style="width: 100%; height: 408px;">
+// #ifdef H5
+import * as echarts from 'echarts/dist/echarts.esm.js'
+const chartRef = ref<LEchartComponentPublicInstance|null>(null);
+const init = async () => {
+## 数据更新
+- 1、使用 `ref` 可获取`setOption`设置更新
+- 2、也可以拿到图表实例`chart`设置`myChart.setOption(data)`
+// ref
+// 图表实例
+myChart.setOption(data)
+## 图表大小
+- 在有些场景下,我们希望当容器大小改变时,图表的大小也相应地改变。
+// 默认获取容器尺寸
+this.$refs.chart.resize()
+// 指定尺寸
+this.$refs.chart.resize({width: 375, height: 375})
+## 自定义Tooltips
+- uvue\nvue 不支持
+由于除H5之外都不存在dom,但又有tooltips个性化的需求,代码就不贴了,看示例吧
+代码位于/uni_modules/lime-echart/component/lime-echart
+## 插件标签
+- 默认 l-echart 为 component
+- 默认 lime-echart 为 demo
+ // 在任意地方使用可查看domo, 代码位于/uni_modules/lime-echart/component/lime-echart
+<lime-echart></lime-echart>
+## 常见问题
+- 钉钉小程序 由于没有`measureText`,模拟的`measureText`又无法得到当前字体的`fontWeight`,故可能存在估计不精细的问题
+- 微信小程序 `2d` 只支持 真机调试2.0
+- 微信开发工具会出现 `canvas` 不跟随页面的情况,真机不影响
+- 微信开发工具会出现 `canvas` 层级过高的问题,真机一般不受影响,可以先测只有两个元素的页面看是否会有层级问题。
+- toolbox 不支持 `saveImage`
+- echarts 5.3.0 的 lines 不支持 trailLength,故需设置为 `0`
+- dataZoom H5不要设置 `showDetail`
+- 如果微信小程序的`tooltip`文字有阴影,可能是微信的锅,临时解决方法是`tooltip.shadowBlur = 0`
+- 如果钉钉小程序上传时报安全问题`Uint8Clamped`,可以向钉钉反馈是安全代码扫描把Uint8Clamped数组错误识别了,也可以在 echarts 文件修改`Uint8Clamped`
+// 找到这段代码把代码中`Uint8Clamped`改成`Uint8_Clamped`,再把下划线去掉,不过直接去掉`Uint8Clamped`也是可行的
+// ["Int8","Uint8","Uint8Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e+"Array]"]
+// 改成如下
+["Int8","Uint8","Uint8_Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e.replace('_','')+"Array]"]
+### vue3
+如果您是使用 **vite + vue3** 非微信小程序可能会遇到`echarts`文件缺少`wx`判断导致无法使用或缺少`tooltip`<br>
+方式一:可以在`echarts.min.js`文件开头增加以下内容,参考插件内的echart.min.js的做法
+let global = null
+let wx = uni
+方式二:在`vite.config.js`的`define`设置环境
+// 或者在`vite.config.js`的`define`设置环境
+import { defineConfig } from 'vite';
+import uni from '@dcloudio/vite-plugin-uni';
+const define = {}
+if(!["mp-weixin", "h5", "web"].includes(process.env.UNI_PLATFORM)) {
+ define['global'] = null
+ define['wx'] = 'uni'
+export default defineConfig({
+ plugins: [uni()],
+ define
+});
+## Props
+| 参数 | 说明 | 类型 | 默认值 | 版本 |
+| --------------- | -------- | ------- | ------------ | ----- |
+| custom-style | 自定义样式 | `string` | - | - |
+| type | 指定 canvas 类型 | `string` | `2d` | |
+| is-disable-scroll | 触摸图表时是否禁止页面滚动 | `boolean` | `false` | |
+| beforeDelay | 延迟初始化 (毫秒) | `number` | `30` | |
+| enableHover | PC端使用鼠标悬浮 | `boolean` | `false` | |
+## 事件
+| 参数 | 说明 |
+| --------------- | --------------- |
+| init(echarts, chart => {}) | 初始化调用函数,第一个参数是传入`echarts`,第二个参数是回调函数,回调函数的参数是 `chart` 实例 |
+| setChart(chart => {}) | 已经初始化后,请使用这个方法,是个回调函数,参数是 `chart` 实例 |
+| setOption(data) | [图表配置项](https://echarts.apache.org/zh/option.html#title),用于更新 ,传递是数据 `option` |
+| clear() | 清空当前实例,会移除实例中所有的组件和图表。 |
+| dispose() | 销毁实例 |
+| showLoading() | 显示加载 |
+| hideLoading() | 隐藏加载 |
+| [canvasToTempFilePath](https://uniapp.dcloud.io/api/canvas/canvasToTempFilePath.html#canvastotempfilepath)(opt) | 用于生成图片,与官方使用方法一致,但不需要传`canvasId` |
+## 打赏
+如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
+
+
@@ -0,0 +1,173 @@
+<html lang="zh">
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title></title>
+ <style type="text/css">
+ html,
+ body,
+ .canvas {
+ margin: 0;
+ overflow-y: hidden;
+ background-color: transparent;
+ <div class="canvas" id="limeChart"></div>
+ <script type="text/javascript" src="./uni.webview.1.5.5.js"></script>
+ <script type="text/javascript" src="./echarts.min.js"></script>
+ <script type="text/javascript" src="./ecStat.min.js"></script>
+ <!-- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-liquidfill@latest/dist/echarts-liquidfill.min.js"></script> -->
+ let chart = null;
+ let cache = [];
+ console.log = function() {
+ emit('log', {
+ log: arguments,
+ function emit(event, data) {
+ postMessage({
+ event,
+ data
+ cache = []
+ function postMessage(data) {
+ uni.webView.postMessage({
+ // window.__uniapp_x_.postMessage(JSON.stringify(data))
+ function stringify(key, value) {
+ if (typeof value === 'object' && value !== null) {
+ if (cache.indexOf(value) !== -1) {
+ cache.push(value);
+ return value;
+ function parse(name, callback, options) {
+ const optionNameReg = /[\w]+\.setOption\(([\w]+\.)?([\w]+)\)/
+ if (optionNameReg.test(callback)) {
+ const optionNames = callback.match(optionNameReg)
+ if (optionNames[1]) {
+ const _this = optionNames[1].split('.')[0]
+ window[_this] = {}
+ window[_this][optionNames[2]] = options
+ return optionNames[2]
+ function init(callback, options, opts, theme) {
+ if (!chart) {
+ chart = echarts.init(document.getElementById('limeChart'), theme, opts)
+ if (options) {
+ chart.setOption(options)
+ function on(data) {
+ if (chart && data.length > 0) {
+ const [type, query] = data
+ const key = `${type}${JSON.stringify(query||'')}`
+ if (query) {
+ chart.on(type, query, function(options) {
+ var obj = {};
+ Object.keys(options).forEach(function(key) {
+ if (key != 'event') {
+ obj[key] = options[key];
+ emit(key, {
+ event: key,
+ options: obj,
+ chart.on(type, function(options) {
+ function setChart(callback, options) {
+ if (!callback) return
+ if (chart && callback && options) {
+ var r = null
+ const name = parse('r', callback, options)
+ if (name) this[name] = options
+ eval(`r = ${callback};`)
+ if (r) {
+ r(chart)
+ function setOption(data) {
+ if (chart) chart.setOption(data[0], data[1])
+ function showLoading(data) {
+ if (chart) chart.showLoading(data[0], data[1])
+ function hideLoading() {
+ if (chart) chart.hideLoading()
+ function clear() {
+ if (chart) chart.clear()
+ function dispose() {
+ if (chart) chart.dispose()
+ function resize(size) {
+ if (chart) chart.resize(size)
+ function canvasToTempFilePath(opt) {
+ if (chart) {
+ delete opt.success
+ const src = chart.getDataURL(opt)
+ // event: 'file',
+ file: src
@@ -0,0 +1,66 @@
+<p align="center">
+ <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
+</p>
+<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView 2.0</h3>
+<h3 align="center">多平台快速开发的UI框架</h3>
+[](https://github.com/umicro/uView2.0)
+[](https://github.com/umicro/uView2.0)
+[](https://github.com/umicro/uView2.0/issues)
+[](https://uviewui.com)
+[](https://gitee.com/umicro/uView2.0/releases)
+[](https://en.wikipedia.org/wiki/MIT_License)
+## 说明
+uView UI,是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
+## [官方文档:https://uviewui.com](https://uviewui.com)
+## 预览
+您可以通过**微信**扫码,查看最佳的演示效果。
+<br>
+<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
+## 链接
+- [官方文档](https://www.uviewui.com/)
+- [更新日志](https://www.uviewui.com/components/changelog.html)
+- [升级指南](https://www.uviewui.com/components/changeGuide.html)
+- [关于我们](https://www.uviewui.com/cooperation/about.html)
+## 交流反馈
+欢迎加入我们的QQ群交流反馈:[点此跳转](https://www.uviewui.com/components/addQQGroup.html)
+## 关于PR
+> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uView2.0是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。
+> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢!
+#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?id=1593](https://ext.dcloud.net.cn/plugin?id=1593)
+请通过[官网安装文档](https://www.uviewui.com/components/install.html)了解更详细的内容
+## 快速上手
+请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
+## 使用方法
+配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
+ <u-button text="按钮"></u-button>
+## 版权信息
+uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。
@@ -0,0 +1,374 @@
+## 2.0.37(2024-03-17)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+1. 修复表单校验`trigger`触发器参数无效问题
+2. 修复`u-input`组件的`password`属性在动态切换为`false`时失效的问题
+3. 添加微信小程序用户同意隐私协议事件回调
+4. 修复支付宝小程序picker样式问题
+5. `u-modal`添加`duration`字段控制动画过度时间
+6. 修复`picker` `lastIndex`异常导致的`column`异常问题
+7. `tabs`增加长按事件支持
+8. 修复`u-avatar` `square`属性在小程序`open-data`下无效问题
+9. 其他一些修复
+## 2.0.36(2023-03-27)
+1. 重构`deepClone` & `deepMerge`方法
+2. 其他优化
+## 2.0.34(2022-09-24)
+1. `u-input`、`u-textarea`增加`ignoreCompositionEvent`属性
+2. 修复`route`方法调用可能报错的问题
+3. 修复`u-no-network`组件`z-index`无效的问题
+4. 修复`textarea`组件在h5上confirmType=""报错的问题
+5. `u-rate`适配`nvue`
+6. 优化验证手机号码的正则表达式(根据工信部发布的《电信网编号计划(2017年版)》进行修改。)
+7. `form-item`添加`labelPosition`属性
+8. `u-calendar`修复`maxDate`设置为当前日期,并且当前时间大于08:00时无法显示日期列表的问题 (#724)
+9. `u-radio`增加一个默认插槽用于自定义修改label内容 (#680)
+10. 修复`timeFormat`函数在safari重的兼容性问题 (#664)
+## 2.0.33(2022-06-17)
+1. 修复`loadmore`组件`lineColor`类型错误问题
+2. 修复`u-parse`组件`imgtap`、`linktap`不生效问题
+## 2.0.32(2022-06-16)
+1. `u-loadmore`新增自定义颜色、虚/实线
+2. 修复`u-swiper-action`组件部分平台不能上下滑动的问题
+3. 修复`u-list`回弹问题
+4. 修复`notice-bar`组件动画在低端安卓机可能会抖动的问题
+5. `u-loading-page`添加控制图标大小的属性`iconSize`
+6. 修复`u-tooltip`组件`color`参数不生效的问题
+7. 修复`u--input`组件使用`blur`事件输出为`undefined`的bug
+8. `u-code-input`组件新增键盘弹起时,是否自动上推页面参数`adjustPosition`
+9. 修复`image`组件`load`事件无回调对象问题
+10. 修复`button`组件`loadingSize`设置无效问题
+10. 其他修复
+## 2.0.31(2022-04-19)
+1. 修复`upload`在`vue`页面上传成功后没有成功标志的问题
+2. 解决演示项目中微信小程序模拟上传图片一直出于上传中问题
+3. 修复`u-code-input`组件在`nvue`页面编译到`app`平台上光标异常问题(`app`去除此功能)
+4. 修复`actionSheet`组件标题关闭按钮点击事件名称错误的问题
+5. 其他修复
+## 2.0.30(2022-04-04)
+1. `u-rate`增加`readonly`属性
+2. `tabs`滑块支持设置背景图片
+3. 修复`u-subsection` `mode`为`subsection`时,滑块样式不正确的问题
+4. `u-code-input`添加光标效果动画
+5. 修复`popup`的`open`事件不触发
+6. 修复`u-flex-column`无效的问题
+7. 修复`u-datetime-picker`索引在特定场合异常问题
+8. 修复`u-datetime-picker`最小时间字符串模板错误问题
+9. `u-swiper`添加`m3u8`验证
+10. `u-swiper`修改判断image和video逻辑
+11. 修复`swiper`无法使用本地图片问题,增加`type`参数
+12. 修复`u-row-notice`格式错误问题
+13. 修复`u-switch`组件当`unit`为`rpx`时,`nodeStyle`消失的问题
+14. 修复`datetime-picker`组件`showToolbar`与`visibleItemCount`属性无效的问题
+15. 修复`upload`组件条件编译位置判断错误,导致`previewImage`属性设置为`false`时,整个组件都会被隐藏的问题
+16. 修复`u-checkbox-group`设置`shape`属性无效的问题
+17. 修复`u-upload`的`capture`传入字符串的时候不生效的问题
+18. 修复`u-action-sheet`组件,关闭事件逻辑错误的问题
+19. 修复`u-list`触顶事件的触发错误的问题
+20. 修复`u-text`只有手机号可拨打的问题
+21. 修复`u-textarea`不能换行的问题
+22. 其他修复
+## 2.0.29(2022-03-13)
+1. 修复`u--text`组件设置`decoration`属性未生效的问题
+2. 修复`u-datetime-picker`使用`formatter`后返回值不正确
+3. 修复`u-datetime-picker` `intercept` 可能为undefined
+4. 修复已设置单位 uni..config.unit = 'rpx'时,线型指示器 `transform` 的位置翻倍,导致指示器超出宽度
+5. 修复mixin中bem方法生成的类名在支付宝和字节小程序中失效
+6. 修复默认值传值为空的时候,打开`u-datetime-picker`报错,不能选中第一列时间的bug
+7. 修复`u-datetime-picker`使用`formatter`后返回值不正确
+8. 修复`u-image`组件`loading`无效果的问题
+9. 修复`config.unit`属性设为`rpx`时,导航栏占用高度不足导致塌陷的问题
+10. 修复`u-datetime-picker`组件`itemHeight`无效问题
+11. 其他修复
+## 2.0.28(2022-02-22)
+1. search组件新增searchIconSize属性
+2. 兼容Safari/Webkit中传入时间格式如2022-02-17 12:00:56
+3. 修复text value.js 判断日期出format错误问题
+4. priceFormat格式化金额出现精度错误
+5. priceFormat在部分情况下出现精度损失问题
+6. 优化表单rules提示
+7. 修复avatar组件src为空时,展示状态不对
+8. 其他修复
+## 2.0.27(2022-01-28)
+1.样式修复
+## 2.0.26(2022-01-28)
+## 2.0.25(2022-01-27)
+1. 修复text组件mode=price时,可能会导致精度错误的问题
+2. 添加$u.setConfig()方法,可设置uView内置的config, props, zIndex, color属性,详见:[修改uView内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
+3. 优化form组件在errorType=toast时,如果输入错误页面会有抖动的问题
+4. 修复$u.addUnit()对配置默认单位可能无效的问题
+## 2.0.24(2022-01-25)
+1. 修复swiper在current指定非0时缩放有误
+2. 修复u-icon添加stop属性的时候报错
+3. 优化遗留的通过正则判断rpx单位的问题
+4. 优化Layout布局 vue使用gutter时,会超出固定区域
+5. 优化search组件高度单位问题(rpx -> px)
+6. 修复u-image slot 加载和错误的图片失去了高度
+7. 修复u-index-list中footer插槽与header插槽存在性判断错误
+8. 修复部分机型下u-popup关闭时会闪烁
+9. 修复u-image在nvue-app下失去宽高
+10. 修复u-popup运行报错
+11. 修复u-tooltip报错
+12. 修复box-sizing在app下的警告
+13. 修复u-navbar在小程序中报运行时错误
+14. 其他修复
+## 2.0.23(2022-01-24)
+1. 修复image组件在hx3.3.9的nvue下可能会显示异常的问题
+2. 修复col组件gutter参数带rpx单位处理不正确的问题
+3. 修复text组件单行时无法显示省略号的问题
+4. navbar添加titleStyle参数
+5. 升级到hx3.3.9可消除nvue下控制台样式警告的问题
+## 2.0.22(2022-01-19)
+1. $u.page()方法优化,避免在特殊场景可能报错的问题
+2. picker组件添加immediateChange参数
+3. 新增$u.pages()方法
+## 2.0.21(2022-01-19)
+1. 优化:form组件在用户设置rules的时候提示用户model必传
+2. 优化遗留的通过正则判断rpx单位的问题
+3. 修复微信小程序环境中tabbar组件开启safeAreaInsetBottom属性后,placeholder高度填充不正确
+4. 修复swiper在current指定非0时缩放有误
+5. 修复u-icon添加stop属性的时候报错
+6. 修复upload组件在accept=all的时候没有作用
+7. 修复在text组件mode为phone时call属性无效的问题
+8. 处理u-form clearValidate方法
+9. 其他修复
+## 2.0.20(2022-01-14)
+1. 修复calendar默认会选择一个日期,如果直接点确定的话,无法取到值的问题
+2. 修复Slider缺少disabled props 还有注释
+3. 修复u-notice-bar点击事件无法拿到index索引值的问题
+4. 修复u-collapse-item在vue文件下,app端自定义插槽不生效的问题
+5. 优化头像为空时显示默认头像
+6. 修复图片地址赋值后判断加载状态为完成问题
+7. 修复日历滚动到默认日期月份区域
+8. search组件暴露点击左边icon事件
+9. 修复u-form clearValidate方法不生效
+10. upload h5端增加返回文件参数(文件的name参数)
+11. 处理upload选择文件后url为blob类型无法预览的问题
+12. u-code-input 修复输入框没有往左移出一半屏幕
+13. 修复Upload上传 disabled为true时,控制台报hoverClass类型错误
+14. 临时处理ios app下grid点击坍塌问题
+15. 其他修复
+## 2.0.19(2021-12-29)
+1. 优化微信小程序包体积可在微信中预览,请升级HbuilderX3.3.4,同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码”
+2. 优化微信小程序setData性能,处理某些方法如$u.route()无法在模板中使用的问题
+3. navbar添加autoBack参数
+4. 允许avatar组件的事件冒泡
+5. 修复cell组件报错问题
+6. 其他修复
+## 2.0.18(2021-12-28)
+1. 修复app端编译报错问题
+2. 重新处理微信小程序端setData过大的性能问题
+3. 修复边框问题
+4. 修复最大最小月份不大于0则没有数据出现的问题
+5. 修复SwipeAction微信小程序端无法上下滑动问题
+6. 修复input的placeholder在小程序端默认显示为true问题
+7. 修复divider组件click事件无效问题
+8. 修复u-code-input maxlength 属性值为 String 类型时显示异常
+9. 修复当 grid只有 1到2时 在小程序端algin设置无效的问题
+10. 处理form-item的label为top时,取消错误提示的左边距
+## 2.0.17(2021-12-26)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+1. 解决HBuilderX3.3.3.20211225版本导致的样式问题
+2. calendar日历添加monthNum参数
+3. navbar添加center slot
+## 2.0.16(2021-12-25)
+1. 解决微信小程序setData性能问题
+2. 修复count-down组件change事件不触发问题
+## 2.0.15(2021-12-21)
+1. 修复Cell单元格titleWidth无效
+2. 修复cheakbox组件ischecked不更新
+3. 修复keyboard是否显示"."按键默认值问题
+4. 修复number-keyboard是否显示键盘的"."符号问题
+5. 修复Input输入框 readonly无效
+6. 修复u-avatar 导致打包app、H5时候报错问题
+7. 修复Upload上传deletable无效
+8. 修复upload当设置maxSize时无效的问题
+9. 修复tabs lineWidth传入带单位的字符串的时候偏移量计算错误问题
+10. 修复rate组件在有padding的view内,显示的星星位置和可触摸区域不匹配,无法正常选中星星
+## 2.0.13(2021-12-14)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+1. 修复配置默认单位为rpx可能会导致自定义导航栏高度异常的问题
+## 2.0.12(2021-12-14)
+1. 修复tabs组件在vue环境下划线消失的问题
+2. 修复upload组件在安卓小程序无法选择视频的问题
+3. 添加uni.$u.config.unit配置,用于配置参数默认单位,详见:[默认单位配置](https://www.uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
+4. 修复textarea组件在没绑定v-model时,字符统计不生效问题
+5. 修复nvue下控制是否出现滚动条失效问题
+## 2.0.11(2021-12-13)
+1. text组件align参数无效的问题
+2. subsection组件添加keyName参数
+3. upload组件无法判断[Object file]类型的问题
+4. 处理notify层级过低问题
+5. codeInput组件添加disabledDot参数
+6. 处理actionSheet组件round参数无效的问题
+7. calendar组件添加round参数用于控制圆角值
+8. 处理swipeAction组件在vue环境下默认被打开的问题
+9. button组件的throttleTime节流参数无效的问题
+10. 解决u-notify手动关闭方法close()无效的问题
+11. input组件readonly不生效问题
+12. tag组件type参数为info不生效问题
+## 2.0.10(2021-12-08)
+1. 修复button sendMessagePath属性不生效
+2. 修复DatetimePicker选择器title无效
+3. 修复u-toast设置loading=true不生效
+4. 修复u-text金额模式传0报错
+5. 修复u-toast组件的icon属性配置不生效
+6. button的icon在特殊场景下的颜色优化
+7. IndexList优化,增加#
+## 2.0.9(2021-12-01)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+1. 优化swiper的height支持100%值(仅vue有效),修复嵌入视频时click事件无法触发的问题
+2. 优化tabs组件对list值为空的判断,或者动态变化list时重新计算相关尺寸的问题
+3. 优化datetime-picker组件逻辑,让其后续打开的默认值为上一次的选中值,需要通过v-model绑定值才有效
+4. 修复upload内嵌在其他组件中,选择图片可能不会换行的问题
+## 2.0.8(2021-12-01)
+1. 修复toast的position参数无效问题
+2. 处理input在ios nvue上无法获得焦点的问题
+3. avatar-group组件添加extraValue参数,让剩余展示数量可手动控制
+4. tabs组件添加keyName参数用于配置从对象中读取的键名
+5. 处理text组件名字脱敏默认配置无效的问题
+6. 处理picker组件item文本太长换行问题
+## 2.0.7(2021-11-30)
+1. 修复radio和checkbox动态改变v-model无效的问题。
+2. 优化form规则validator在微信小程序用法
+3. 修复backtop组件mode参数在微信小程序无效的问题
+4. 处理Album的previewFullImage属性无效的问题
+5. 处理u-datetime-picker组件mode='time'在选择改变时间时,控制台报错的问题
+## 2.0.6(2021-11-27)
+1. 处理tag组件在vue下边框无效的问题。
+2. 处理popup组件圆角参数可能无效的问题。
+3. 处理tabs组件lineColor参数可能无效的问题。
+4. propgress组件在值很小时,显示异常的问题。
+## 2.0.5(2021-11-25)
+1. calendar在vue下显示异常问题。
+2. form组件labelPosition和errorType参数无效的问题
+3. input组件inputAlign无效的问题
+4. 其他一些修复
+## 2.0.4(2021-11-23)
+0. input组件缺失@confirm事件,以及subfix和prefix无效问题
+1. component.scss文件样式在vue下干扰全局布局问题
+2. 修复subsection在vue环境下表现异常的问题
+3. tag组件的bgColor等参数无效的问题
+4. upload组件不换行的问题
+5. 其他的一些修复处理
+## 2.0.3(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 处理modal的confirm回调事件拼写错误问题
+6. 处理input组件@input事件参数错误问题
+7. 其他一些修复
+## 2.0.2(2021-11-16)
+5. 修复input组件formatter参数缺失问题
+6. 优化loading-icon组件的scss写法问题,防止不兼容新版本scss
+## 2.0.0(2020-11-15)
+ <uvForm
+ ref="uForm"
+ :model="model"
+ :rules="rules"
+ :errorType="errorType"
+ :borderBottom="borderBottom"
+ :labelPosition="labelPosition"
+ :labelWidth="labelWidth"
+ :labelAlign="labelAlign"
+ :labelStyle="labelStyle"
+ :customStyle="customStyle"
+ <slot />
+ </uvForm>
+ /**
+ * 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件
+ * 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转
+ import uvForm from '../u-form/u-form.vue';
+ import props from '../u-form/props.js'
+ * Form 表单
+ * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
+ * @tutorial https://www.uviewui.com/components/form.html
+ * @property {Object} model 当前form的需要验证字段的集合
+ * @property {Object | Function | Array} rules 验证规则
+ * @property {String} errorType 错误的提示方式,见上方说明 ( 默认 message )
+ * @property {Boolean} borderBottom 是否显示表单域的下划线边框 ( 默认 true )
+ * @property {String} labelPosition 表单域提示文字的位置,left-左侧,top-上方 ( 默认 'left' )
+ * @property {String | Number} labelWidth 提示文字的宽度,单位px ( 默认 45 )
+ * @property {String} labelAlign lable字体的对齐方式 ( 默认 ‘left' )
+ * @property {Object} labelStyle lable的样式,对象形式
+ * @example <u--formlabelPosition="left" :model="model1" :rules="rules" ref="form1"></u--form>
+ name: 'u-form',
+ // #ifndef MP-WEIXIN
+ name: 'u--form',
+ mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+ uvForm
+ this.children = []
+ // 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
+ setRules(rules) {
+ this.$refs.uForm.setRules(rules)
+ validate() {
+ * 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form
+ * 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的
+ * 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children
+ this.setMpData()
+ return this.$refs.uForm.validate()
+ validateField(value, callback, event) {
+ return this.$refs.uForm.validateField(value, callback, event)
+ resetFields() {
+ return this.$refs.uForm.resetFields()
+ clearValidate(props) {
+ return this.$refs.uForm.clearValidate(props)
+ setMpData() {
+ this.$refs.uForm.children = this.children
@@ -0,0 +1,73 @@
+ <uvImage
+ :src="src"
+ :mode="mode"
+ :width="width"
+ :height="height"
+ :shape="shape"
+ :radius="radius"
+ :lazyLoad="lazyLoad"
+ :showMenuByLongpress="showMenuByLongpress"
+ :loadingIcon="loadingIcon"
+ :errorIcon="errorIcon"
+ :showLoading="showLoading"
+ :showError="showError"
+ :fade="fade"
+ :webp="webp"
+ :duration="duration"
+ :bgColor="bgColor"
+ @click="$emit('click')"
+ @error="$emit('error')"
+ @load="$emit('load')"
+ <template v-slot:loading>
+ <slot name="loading"></slot>
+ </template>
+ <template v-slot:error>
+ <slot name="error"></slot>
+ </uvImage>
+ * 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件
+ * 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转
+ import uvImage from '../u-image/u-image.vue';
+ import props from '../u-image/props.js';
+ * Image 图片
+ * @description 此组件为uni-app的image组件的加强版,在继承了原有功能外,还支持淡入动画、加载中、加载失败提示、圆角值和形状等。
+ * @tutorial https://uviewui.com/components/image.html
+ * @property {String} src 图片地址
+ * @property {String} mode 裁剪模式,见官网说明 (默认 'aspectFill' )
+ * @property {String | Number} width 宽度,单位任意,如果为数值,则为px单位 (默认 '300' )
+ * @property {String | Number} height 高度,单位任意,如果为数值,则为px单位 (默认 '225' )
+ * @property {String} shape 图片形状,circle-圆形,square-方形 (默认 'square' )
+ * @property {String | Number} radius 圆角值,单位任意,如果为数值,则为px单位 (默认 0 )
+ * @property {Boolean} lazyLoad 是否懒加载,仅微信小程序、App、百度小程序、字节跳动小程序有效 (默认 true )
+ * @property {Boolean} showMenuByLongpress 是否开启长按图片显示识别小程序码菜单,仅微信小程序有效 (默认 true )
+ * @property {String} loadingIcon 加载中的图标,或者小图片 (默认 'photo' )
+ * @property {String} errorIcon 加载失败的图标,或者小图片 (默认 'error-circle' )
+ * @property {Boolean} showLoading 是否显示加载中的图标或者自定义的slot (默认 true )
+ * @property {Boolean} showError 是否显示加载错误的图标或者自定义的slot (默认 true )
+ * @property {Boolean} fade 是否需要淡入效果 (默认 true )
+ * @property {Boolean} webp 只支持网络资源,只对微信小程序有效 (默认 false )
+ * @property {String | Number} duration 搭配fade参数的过渡时间,单位ms (默认 500 )
+ * @property {String} bgColor 背景颜色,用于深色页面加载图片时,为了和背景色融合 (默认 '#f3f4f6' )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @event {Function} click 点击图片时触发
+ * @event {Function} error 图片加载失败时触发
+ * @event {Function} load 图片加载成功时触发
+ * @example <u--image width="100%" height="300px" :src="src"></u--image>
+ name: 'u--image',
+ uvImage
@@ -0,0 +1,115 @@
+ <uvInput
+ :value="value"
+ :type="type"
+ :fixed="fixed"
+ :disabled="disabled"
+ :disabledColor="disabledColor"
+ :clearable="clearable"
+ :password="password"
+ :maxlength="maxlength"
+ :placeholder="placeholder"
+ :placeholderClass="placeholderClass"
+ :placeholderStyle="placeholderStyle"
+ :showWordLimit="showWordLimit"
+ :confirmType="confirmType"
+ :confirmHold="confirmHold"
+ :holdKeyboard="holdKeyboard"
+ :focus="focus"
+ :autoBlur="autoBlur"
+ :disableDefaultPadding="disableDefaultPadding"
+ :cursor="cursor"
+ :cursorSpacing="cursorSpacing"
+ :selectionStart="selectionStart"
+ :selectionEnd="selectionEnd"
+ :adjustPosition="adjustPosition"
+ :inputAlign="inputAlign"
+ :fontSize="fontSize"
+ :color="color"
+ :prefixIcon="prefixIcon"
+ :suffixIcon="suffixIcon"
+ :suffixIconStyle="suffixIconStyle"
+ :prefixIconStyle="prefixIconStyle"
+ :border="border"
+ :readonly="readonly"
+ :formatter="formatter"
+ :ignoreCompositionEvent="ignoreCompositionEvent"
+ @focus="e => $emit('focus', e)"
+ @blur="e => $emit('blur', e)"
+ @keyboardheightchange="e => $emit('keyboardheightchange', e)"
+ @change="e => $emit('change', e)"
+ @input="e => $emit('input', e)"
+ @confirm="e => $emit('confirm', e)"
+ @clear="$emit('clear')"
+ <!-- #ifdef MP -->
+ <slot name="prefix"></slot>
+ <slot name="suffix"></slot>
+ <!-- #ifndef MP -->
+ <slot name="prefix" slot="prefix"></slot>
+ <slot name="suffix" slot="suffix"></slot>
+ </uvInput>
+ * 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件
+ * 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转
+ import uvInput from '../u-input/u-input.vue';
+ import props from '../u-input/props.js'
+ * Input 输入框
+ * @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
+ * @tutorial https://uviewui.com/components/input.html
+ * @property {String | Number} value 输入的值
+ * @property {String} type 输入框类型,见上方说明 ( 默认 'text' )
+ * @property {Boolean} fixed 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true,兼容性:微信小程序、百度小程序、字节跳动小程序、QQ小程序 ( 默认 false )
+ * @property {Boolean} disabled 是否禁用输入框 ( 默认 false )
+ * @property {String} disabledColor 禁用状态时的背景色( 默认 '#f5f7fa' )
+ * @property {Boolean} clearable 是否显示清除控件 ( 默认 false )
+ * @property {Boolean} password 是否密码类型 ( 默认 false )
+ * @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度 ( 默认 -1 )
+ * @property {String} placeholder 输入框为空时的占位符
+ * @property {String} placeholderClass 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ ( 默认 'input-placeholder' )
+ * @property {String | Object} placeholderStyle 指定placeholder的样式,字符串/对象形式,如"color: red;"
+ * @property {Boolean} showWordLimit 是否显示输入字数统计,只在 type ="text"或type ="textarea"时有效 ( 默认 false )
+ * @property {String} confirmType 设置右下角按钮的文字,兼容性详见uni-app文档 ( 默认 'done' )
+ * @property {Boolean} confirmHold 点击键盘右下角按钮时是否保持键盘不收起,H5无效 ( 默认 false )
+ * @property {Boolean} holdKeyboard focus时,点击页面的时候不收起键盘,微信小程序有效 ( 默认 false )
+ * @property {Boolean} focus 自动获取焦点,在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点 ( 默认 false )
+ * @property {Boolean} autoBlur 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效 ( 默认 false )
+ * @property {Boolean} disableDefaultPadding 是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效 ( 默认 false )
+ * @property {String | Number} cursor 指定focus时光标的位置( 默认 -1 )
+ * @property {String | Number} cursorSpacing 输入框聚焦时底部与键盘的距离 ( 默认 30 )
+ * @property {String | Number} selectionStart 光标起始位置,自动聚集时有效,需与selection-end搭配使用 ( 默认 -1 )
+ * @property {String | Number} selectionEnd 光标结束位置,自动聚集时有效,需与selection-start搭配使用 ( 默认 -1 )
+ * @property {Boolean} adjustPosition 键盘弹起时,是否自动上推页面 ( 默认 true )
+ * @property {String} inputAlign 输入框内容对齐方式( 默认 'left' )
+ * @property {String | Number} fontSize 输入框字体的大小 ( 默认 '15px' )
+ * @property {String} color 输入框字体颜色 ( 默认 '#303133' )
+ * @property {Function} formatter 内容式化函数
+ * @property {String} prefixIcon 输入框前置图标
+ * @property {String | Object} prefixIconStyle 前置图标样式,对象或字符串
+ * @property {String} suffixIcon 输入框后置图标
+ * @property {String | Object} suffixIconStyle 后置图标样式,对象或字符串
+ * @property {String} border 边框类型,surround-四周边框,bottom-底部边框,none-无边框 ( 默认 'surround' )
+ * @property {Boolean} readonly 是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会 ( 默认 false )
+ * @property {String} shape 输入框形状,circle-圆形,square-方形 ( 默认 'square' )
+ * @property {Boolean} ignoreCompositionEvent 是否忽略组件内对文本合成系统事件的处理。
+ * @example <u--input v-model="value" :password="true" suffix-icon="lock-fill" />
+ name: 'u--input',
+ uvInput
@@ -0,0 +1,72 @@
+ <uvText
+ :text="text"
+ :href="href"
+ :format="format"
+ :call="call"
+ :openType="openType"
+ :bold="bold"
+ :block="block"
+ :lines="lines"
+ :decoration="decoration"
+ :size="size"
+ :iconStyle="iconStyle"
+ :margin="margin"
+ :lineHeight="lineHeight"
+ :align="align"
+ :wordWrap="wordWrap"
+ ></uvText>
+ * 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件
+ * 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转
+ * 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法
+import uvText from "../u-text/u-text.vue";
+import props from "../u-text/props.js";
+ * Text 文本
+ * @description 此组件集成了文本类在项目中的常用功能,包括状态,拨打电话,格式化日期,*替换,超链接...等功能。 您大可不必在使用特殊文本时自己定义,text组件几乎涵盖您能使用的大部分场景。
+ * @tutorial https://www.uviewui.com/components/loading.html
+ * @property {String} type 主题颜色
+ * @property {Boolean} show 是否显示(默认 true )
+ * @property {String | Number} text 显示的值
+ * @property {String} prefixIcon 前置图标
+ * @property {String} suffixIcon 后置图标
+ * @property {String} mode 文本处理的匹配模式 text-普通文本,price-价格,phone-手机号,name-姓名,date-日期,link-超链接
+ * @property {String} href mode=link下,配置的链接
+ * @property {String | Function} format 格式化规则
+ * @property {Boolean} call mode=phone时,点击文本是否拨打电话(默认 false )
+ * @property {String} openType 小程序的打开方式
+ * @property {Boolean} bold 是否粗体,默认normal(默认 false )
+ * @property {Boolean} block 是否块状(默认 false )
+ * @property {String | Number} lines 文本显示的行数,如果设置,超出此行数,将会显示省略号
+ * @property {String} color 文本颜色(默认 '#303133' )
+ * @property {String | Number} size 字体大小(默认 15 )
+ * @property {Object | String} iconStyle 图标的样式 (默认 {fontSize: '15px'} )
+ * @property {String} decoration 文字装饰,下划线,中划线等,可选值 none|underline|line-through(默认 'none' )
+ * @property {Object | String | Number} margin 外边距,对象、字符串,数值形式均可(默认 0 )
+ * @property {String | Number} lineHeight 文本行高
+ * @property {String} align 文本对齐方式,可选值left|center|right(默认 'left' )
+ * @property {String} wordWrap 文字换行,可选值break-word|normal|anywhere(默认 'normal' )
+ * @event {Function} click 点击触发事件
+ * @example <u--text text="我用十年青春,赴你最后之约"></u--text>
+ name: "u--text",
+ uvText,
@@ -0,0 +1,85 @@
+ <uvTextarea
+ :count="count"
+ :autoHeight="autoHeight"
+ :showConfirmBar="showConfirmBar"
+ @linechange="e => $emit('linechange', e)"
+ ></uvTextarea>
+ * 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件
+ * 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转
+ import uvTextarea from '../u-textarea/u-textarea.vue';
+ import props from '../u-textarea/props.js'
+ * Textarea 文本域
+ * @description 文本域此组件满足了可能出现的表单信息补充,编辑等实际逻辑的功能,内置了字数校验等
+ * @tutorial https://www.uviewui.com/components/textarea.html
+ * @property {String | Number} value 输入框的内容
+ * @property {String | Number} placeholder 输入框为空时占位符
+ * @property {String | Number} height 输入框高度(默认 70 )
+ * @property {String} confirmType 设置键盘右下角按钮的文字,仅微信小程序,App-vue和H5有效(默认 'done' )
+ * @property {Boolean} disabled 是否禁用(默认 false )
+ * @property {Boolean} count 是否显示统计字数(默认 false )
+ * @property {Boolean} focus 是否自动获取焦点,nvue不支持,H5取决于浏览器的实现(默认 false )
+ * @property {Boolean | Function} autoHeight 是否自动增加高度(默认 false )
+ * @property {Boolean} fixed 如果textarea是在一个position:fixed的区域,需要显示指定属性fixed为true(默认 false )
+ * @property {Number} cursorSpacing 指定光标与键盘的距离(默认 0 )
+ * @property {String | Number} cursor 指定focus时的光标位置
+ * @property {Boolean} showConfirmBar 是否显示键盘上方带有”完成“按钮那一栏,(默认 true )
+ * @property {Number} selectionStart 光标起始位置,自动聚焦时有效,需与selection-end搭配使用,(默认 -1 )
+ * @property {Number | Number} selectionEnd 光标结束位置,自动聚焦时有效,需与selection-start搭配使用(默认 -1 )
+ * @property {Boolean} adjustPosition 键盘弹起时,是否自动上推页面(默认 true )
+ * @property {Boolean | Number} disableDefaultPadding 是否去掉 iOS 下的默认内边距,只微信小程序有效(默认 false )
+ * @property {Boolean} holdKeyboard focus时,点击页面的时候不收起键盘,只微信小程序有效(默认 false )
+ * @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认 140 )
+ * @property {String} border 边框类型,surround-四周边框,none-无边框,bottom-底部边框(默认 'surround' )
+ * @property {Boolean} ignoreCompositionEvent 是否忽略组件内对文本合成系统事件的处理
+ * @event {Function(e)} focus 输入框聚焦时触发,event.detail = { value, height },height 为键盘高度
+ * @event {Function(e)} blur 输入框失去焦点时触发,event.detail = {value, cursor}
+ * @event {Function(e)} linechange 输入框行数变化时调用,event.detail = {height: 0, heightRpx: 0, lineCount: 0}
+ * @event {Function(e)} input 当键盘输入时,触发 input 事件
+ * @event {Function(e)} confirm 点击完成时, 触发 confirm 事件
+ * @event {Function(e)} keyboardheightchange 键盘高度发生变化的时候触发此事件
+ * @example <u--textarea v-model="value1" placeholder="请输入内容" ></u--textarea>
+ name: 'u--textarea',
+ uvTextarea
@@ -0,0 +1,54 @@
+ // 操作菜单是否展示 (默认false)
+ show: {
+ default: uni.$u.props.actionSheet.show
+ // 标题
+ default: uni.$u.props.actionSheet.title
+ // 选项上方的描述信息
+ description: {
+ default: uni.$u.props.actionSheet.description
+ // 数据
+ actions: {
+ type: Array,
+ default: uni.$u.props.actionSheet.actions
+ // 取消按钮的文字,不为空时显示按钮
+ cancelText: {
+ default: uni.$u.props.actionSheet.cancelText
+ // 点击某个菜单项时是否关闭弹窗
+ closeOnClickAction: {
+ default: uni.$u.props.actionSheet.closeOnClickAction
+ // 处理底部安全区(默认true)
+ safeAreaInsetBottom: {
+ default: uni.$u.props.actionSheet.safeAreaInsetBottom
+ // 小程序的打开方式
+ openType: {
+ default: uni.$u.props.actionSheet.openType
+ // 点击遮罩是否允许关闭 (默认true)
+ closeOnClickOverlay: {
+ default: uni.$u.props.actionSheet.closeOnClickOverlay
+ // 圆角值
+ round: {
+ type: [String, Number],
+ default: uni.$u.props.actionSheet.round
@@ -0,0 +1,278 @@
+ mode="bottom"
+ @close="closeHandler"
+ :safeAreaInsetBottom="safeAreaInsetBottom"
+ :round="round"
+ <view class="u-action-sheet">
+ class="u-action-sheet__header"
+ v-if="title"
+ <text class="u-action-sheet__header__title u-line-1">{{title}}</text>
+ class="u-action-sheet__header__icon-wrap"
+ @tap.stop="cancel"
+ name="close"
+ size="17"
+ color="#c8c9cc"
+ bold
+ <text
+ class="u-action-sheet__description"
+ :style="[{
+ marginTop: `${title && description ? 0 : '18px'}`
+ }]"
+ v-if="description"
+ >{{description}}</text>
+ <slot>
+ <u-line v-if="description"></u-line>
+ <view class="u-action-sheet__item-wrap">
+ <template v-for="(item, index) in actions">
+ <button
+ class="u-reset-button"
+ :openType="item.openType"
+ @getuserinfo="onGetUserInfo"
+ @contact="onContact"
+ @getphonenumber="onGetPhoneNumber"
+ @error="onError"
+ @launchapp="onLaunchApp"
+ @opensetting="onOpenSetting"
+ :lang="lang"
+ :session-from="sessionFrom"
+ :send-message-title="sendMessageTitle"
+ :send-message-path="sendMessagePath"
+ :send-message-img="sendMessageImg"
+ :show-message-card="showMessageCard"
+ :app-parameter="appParameter"
+ @tap="selectHandler(index)"
+ :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
+ class="u-action-sheet__item-wrap__item"
+ @tap.stop="selectHandler(index)"
+ :hover-stay-time="150"
+ <template v-if="!item.loading">
+ class="u-action-sheet__item-wrap__item__name"
+ :style="[itemStyle(index)]"
+ >{{ item.name }}</text>
+ v-if="item.subname"
+ class="u-action-sheet__item-wrap__item__subname"
+ >{{ item.subname }}</text>
+ <u-loading-icon
+ custom-class="van-action-sheet__loading"
+ size="18"
+ mode="circle"
+ <u-line v-if="index !== actions.length - 1"></u-line>
+ </slot>
+ <u-gap
+ bgColor="#eaeaec"
+ height="6"
+ v-if="cancelText"
+ ></u-gap>
+ <view hover-class="u-action-sheet--hover">
+ @touchmove.stop.prevent
+ class="u-action-sheet__cancel-text"
+ @tap="cancel"
+ >{{cancelText}}</text>
+ import openType from '../../libs/mixin/openType'
+ import button from '../../libs/mixin/button'
+ import props from './props.js';
+ * ActionSheet 操作菜单
+ * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
+ * @tutorial https://www.uviewui.com/components/actionSheet.html
+ * @property {Boolean} show 操作菜单是否展示 (默认 false )
+ * @property {String} title 操作菜单标题
+ * @property {String} description 选项上方的描述信息
+ * @property {Array<Object>} actions 按钮的文字数组,见官方文档示例
+ * @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮
+ * @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true )
+ * @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true )
+ * @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
+ * @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true )
+ * @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
+ * @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
+ * @property {String} sessionFrom 会话来源,openType="contact"时有效
+ * @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
+ * @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+ * @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
+ * @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
+ * @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
+ * @event {Function} select 点击ActionSheet列表项时触发
+ * @event {Function} close 点击取消按钮时触发
+ * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
+ * @event {Function} contact 客服消息回调,openType="contact"时有效
+ * @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效
+ * @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效
+ * @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效
+ * @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效
+ * @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
+ name: "u-action-sheet",
+ // 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
+ mixins: [openType, button, uni.$u.mixin, props],
+ // 操作项目的样式
+ itemStyle() {
+ return (index) => {
+ let style = {};
+ if (this.actions[index].color) style.color = this.actions[index].color
+ if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize)
+ // 选项被禁用的样式
+ if (this.actions[index].disabled) style.color = '#c0c4cc'
+ return style;
+ closeHandler() {
+ // 允许点击遮罩关闭时,才发出close事件
+ if(this.closeOnClickOverlay) {
+ this.$emit('close')
+ // 点击取消按钮
+ cancel() {
+ selectHandler(index) {
+ const item = this.actions[index]
+ if (item && !item.disabled && !item.loading) {
+ this.$emit('select', item)
+ if (this.closeOnClickAction) {
+ @import "../../libs/css/components.scss";
+ $u-action-sheet-reset-button-width:100% !default;
+ $u-action-sheet-title-font-size: 16px !default;
+ $u-action-sheet-title-padding: 12px 30px !default;
+ $u-action-sheet-title-color: $u-main-color !default;
+ $u-action-sheet-header-icon-wrap-right:15px !default;
+ $u-action-sheet-header-icon-wrap-top:15px !default;
+ $u-action-sheet-description-font-size:13px !default;
+ $u-action-sheet-description-color:14px !default;
+ $u-action-sheet-description-margin: 18px 15px !default;
+ $u-action-sheet-item-wrap-item-padding:15px !default;
+ $u-action-sheet-item-wrap-name-font-size:16px !default;
+ $u-action-sheet-item-wrap-subname-font-size:13px !default;
+ $u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
+ $u-action-sheet-item-wrap-subname-margin-top:10px !default;
+ $u-action-sheet-cancel-text-font-size:16px !default;
+ $u-action-sheet-cancel-text-color:$u-content-color !default;
+ $u-action-sheet-cancel-text-font-size:15px !default;
+ $u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
+ .u-reset-button {
+ width: $u-action-sheet-reset-button-width;
+ .u-action-sheet {
+ &__header {
+ padding: $u-action-sheet-title-padding;
+ font-size: $u-action-sheet-title-font-size;
+ color: $u-action-sheet-title-color;
+ &__icon-wrap {
+ right: $u-action-sheet-header-icon-wrap-right;
+ top: $u-action-sheet-header-icon-wrap-top;
+ &__description {
+ font-size: $u-action-sheet-description-font-size;
+ margin: $u-action-sheet-description-margin;
+ &__item-wrap {
+ &__item {
+ padding: $u-action-sheet-item-wrap-item-padding;
+ &__name {
+ font-size: $u-action-sheet-item-wrap-name-font-size;
+ &__subname {
+ font-size: $u-action-sheet-item-wrap-subname-font-size;
+ color: $u-action-sheet-item-wrap-subname-color;
+ margin-top: $u-action-sheet-item-wrap-subname-margin-top;
+ &__cancel-text {
+ font-size: $u-action-sheet-cancel-text-font-size;
+ color: $u-action-sheet-cancel-text-color;
+ padding: $u-action-sheet-cancel-text-font-size;
+ &--hover {
+ background-color: $u-action-sheet-cancel-text-hover-background-color;
@@ -0,0 +1,59 @@
+ // 图片地址,Array<String>|Array<Object>形式
+ urls: {
+ default: uni.$u.props.album.urls
+ // 指定从数组的对象元素中读取哪个属性作为图片地址
+ keyName: {
+ default: uni.$u.props.album.keyName
+ // 单图时,图片长边的长度
+ singleSize: {
+ default: uni.$u.props.album.singleSize
+ // 多图时,图片边长
+ multipleSize: {
+ default: uni.$u.props.album.multipleSize
+ // 多图时,图片水平和垂直之间的间隔
+ space: {
+ default: uni.$u.props.album.space
+ // 单图时,图片缩放裁剪的模式
+ singleMode: {
+ default: uni.$u.props.album.singleMode
+ // 多图时,图片缩放裁剪的模式
+ multipleMode: {
+ default: uni.$u.props.album.multipleMode
+ // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
+ maxCount: {
+ default: uni.$u.props.album.maxCount
+ // 是否可以预览图片
+ previewFullImage: {
+ default: uni.$u.props.album.previewFullImage
+ // 每行展示图片数量,如设置,singleSize和multipleSize将会无效
+ rowCount: {
+ default: uni.$u.props.album.rowCount
+ // 超出maxCount时是否显示查看更多的提示
+ showMore: {
+ default: uni.$u.props.album.showMore
@@ -0,0 +1,259 @@
+ <view class="u-album">
+ class="u-album__row"
+ ref="u-album__row"
+ v-for="(arr, index) in showUrls"
+ :forComputedUse="albumWidth"
+ class="u-album__row__wrapper"
+ v-for="(item, index1) in arr"
+ :key="index1"
+ :style="[imageStyle(index + 1, index1 + 1)]"
+ @tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''"
+ <image
+ :src="getSrc(item)"
+ :mode="
+ urls.length === 1
+ ? imageHeight > 0
+ ? singleMode
+ : 'widthFix'
+ : multipleMode
+ :style="[
+ width: imageWidth,
+ height: imageHeight
+ ]"
+ ></image>
+ v-if="
+ showMore &&
+ urls.length > rowCount * showUrls.length &&
+ index === showUrls.length - 1 &&
+ index1 === showUrls[showUrls.length - 1].length - 1
+ class="u-album__row__wrapper__text"
+ <u--text
+ :text="`+${urls.length - maxCount}`"
+ :size="multipleSize * 0.3"
+ align="center"
+ customStyle="justify-content: center"
+ ></u--text>
+import props from './props.js'
+// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
+const dom = uni.requireNativePlugin('dom')
+ * Album 相册
+ * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
+ * @tutorial https://www.uviewui.com/components/album.html
+ * @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
+ * @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
+ * @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 )
+ * @property {String | Number} multipleSize 多图时,图片边长 (默认 70 )
+ * @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 )
+ * @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
+ * @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
+ * @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 )
+ * @property {Boolean} previewFullImage 是否可以预览图片 (默认 true )
+ * @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 )
+ * @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
+ * @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width )
+ * @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
+ name: 'u-album',
+ mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+ // 单图的宽度
+ singleWidth: 0,
+ // 单图的高度
+ singleHeight: 0,
+ // 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
+ singlePercent: 0.6
+ immediate: true,
+ handler(newVal) {
+ if (newVal.length === 1) {
+ this.getImageRect()
+ imageStyle() {
+ return (index1, index2) => {
+ const { space, rowCount, multipleSize, urls } = this,
+ { addUnit, addStyle } = uni.$u,
+ rowLen = this.showUrls.length,
+ allLen = this.urls.length
+ const style = {
+ marginRight: addUnit(space),
+ marginBottom: addUnit(space)
+ // 如果为最后一行,则每个图片都无需下边框
+ if (index1 === rowLen) style.marginBottom = 0
+ // 每行的最右边一张和总长度的最后一张无需右边框
+ if (
+ index2 === rowCount ||
+ (index1 === rowLen &&
+ index2 === this.showUrls[index1 - 1].length)
+ style.marginRight = 0
+ return style
+ // 将数组划分为二维数组
+ showUrls() {
+ const arr = []
+ this.urls.map((item, index) => {
+ // 限制最大展示数量
+ if (index + 1 <= this.maxCount) {
+ // 计算该元素为第几个素组内
+ const itemIndex = Math.floor(index / this.rowCount)
+ // 判断对应的索引是否存在
+ if (!arr[itemIndex]) {
+ arr[itemIndex] = []
+ arr[itemIndex].push(item)
+ return arr
+ imageWidth() {
+ return uni.$u.addUnit(
+ this.urls.length === 1 ? this.singleWidth : this.multipleSize
+ imageHeight() {
+ this.urls.length === 1 ? this.singleHeight : this.multipleSize
+ // 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
+ // 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
+ albumWidth() {
+ let width = 0
+ if (this.urls.length === 1) {
+ width = this.singleWidth
+ width =
+ this.showUrls[0].length * this.multipleSize +
+ this.space * (this.showUrls[0].length - 1)
+ this.$emit('albumWidth', width)
+ return width
+ // 预览图片
+ onPreviewTap(url) {
+ const urls = this.urls.map((item) => {
+ return this.getSrc(item)
+ uni.previewImage({
+ current: url,
+ urls
+ // 获取图片的路径
+ getSrc(item) {
+ return uni.$u.test.object(item)
+ ? (this.keyName && item[this.keyName]) || item.src
+ : item
+ // 单图时,获取图片的尺寸
+ // 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
+ // 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
+ getImageRect() {
+ const src = this.getSrc(this.urls[0])
+ // 判断图片横向还是竖向展示方式
+ const isHorizotal = res.width >= res.height
+ this.singleWidth = isHorizotal
+ ? this.singleSize
+ : (res.width / res.height) * this.singleSize
+ this.singleHeight = !isHorizotal
+ : (res.height / res.width) * this.singleWidth
+ this.getComponentWidth()
+ // 获取组件的宽度
+ async getComponentWidth() {
+ // 延时一定时间,以获取dom尺寸
+ await uni.$u.sleep(30)
+ this.$uGetRect('.u-album__row').then((size) => {
+ this.singleWidth = size.width * this.singlePercent
+ // 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
+ const ref = this.$refs['u-album__row'][0]
+ ref &&
+ dom.getComponentRect(ref, (res) => {
+ this.singleWidth = res.size.width * this.singlePercent
+@import '../../libs/css/components.scss';
+.u-album {
+ &__row {
+ @include flex(row);
+ flex-wrap: wrap;
+ &__wrapper {
+ background-color: rgba(0, 0, 0, 0.3);
@@ -0,0 +1,44 @@
+ // 显示文字
+ default: uni.$u.props.alert.title
+ // 主题,success/warning/info/error
+ default: uni.$u.props.alert.type
+ // 辅助性文字
+ default: uni.$u.props.alert.description
+ // 是否可关闭
+ closable: {
+ default: uni.$u.props.alert.closable
+ // 是否显示图标
+ showIcon: {
+ default: uni.$u.props.alert.showIcon
+ // 浅或深色调,light-浅色,dark-深色
+ effect: {
+ default: uni.$u.props.alert.effect
+ // 文字是否居中
+ center: {
+ default: uni.$u.props.alert.center
+ // 字体大小
+ fontSize: {
+ default: uni.$u.props.alert.fontSize
@@ -0,0 +1,243 @@
+ <u-transition
+ mode="fade"
+ class="u-alert"
+ :class="[`u-alert--${type}--${effect}`]"
+ @tap.stop="clickHandler"
+ :style="[$u.addStyle(customStyle)]"
+ class="u-alert__icon"
+ v-if="showIcon"
+ :name="iconName"
+ :color="iconColor"
+ class="u-alert__content"
+ paddingRight: closable ? '20px' : 0
+ class="u-alert__content__title"
+ fontSize: $u.addUnit(fontSize),
+ textAlign: center ? 'center' : 'left'
+ :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
+ >{{ title }}</text>
+ class="u-alert__content__desc"
+ >{{ description }}</text>
+ class="u-alert__close"
+ v-if="closable"
+ @tap.stop="closeHandler"
+ size="15"
+ </u-transition>
+ * Alert 警告提示
+ * @description 警告提示,展现需要关注的信息。
+ * @tutorial https://www.uviewui.com/components/alertTips.html
+ * @property {String} title 显示的文字
+ * @property {String} type 使用预设的颜色 (默认 'warning' )
+ * @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
+ * @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false )
+ * @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false )
+ * @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' )
+ * @property {Boolean} center 文字是否居中 (默认 false )
+ * @property {String | Number} fontSize 字体大小 (默认 14 )
+ * @event {Function} click 点击组件时触发
+ * @example <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert>
+ name: 'u-alert',
+ iconColor() {
+ return this.effect === 'light' ? this.type : '#fff'
+ // 不同主题对应不同的图标
+ iconName() {
+ switch (this.type) {
+ case 'success':
+ return 'checkmark-circle-fill';
+ break;
+ case 'error':
+ return 'close-circle-fill';
+ case 'warning':
+ return 'error-circle-fill';
+ case 'info':
+ return 'info-circle-fill';
+ case 'primary':
+ return 'more-circle-fill';
+ default:
+ // 点击内容
+ clickHandler() {
+ this.$emit('click')
+ // 点击关闭按钮
+ .u-alert {
+ background-color: $u-primary;
+ padding: 8px 10px;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ &--primary--dark {
+ &--primary--light {
+ background-color: #ecf5ff;
+ &--error--dark {
+ background-color: $u-error;
+ &--error--light {
+ background-color: #FEF0F0;
+ &--success--dark {
+ background-color: $u-success;
+ &--success--light {
+ background-color: #f5fff0;
+ &--warning--dark {
+ background-color: $u-warning;
+ &--warning--light {
+ background-color: #FDF6EC;
+ &--info--dark {
+ background-color: $u-info;
+ &--info--light {
+ background-color: #f4f4f5;
+ &__icon {
+ margin-right: 5px;
+ margin-bottom: 2px;
+ &__desc {
+ &__title--dark,
+ &__desc--dark {
+ &__text--primary--light,
+ &__text--primary--light {
+ &__text--success--light,
+ &__text--success--light {
+ color: $u-success;
+ &__text--warning--light,
+ &__text--warning--light {
+ color: $u-warning;
+ &__text--error--light,
+ &__text--error--light {
+ color: $u-error;
+ &__text--info--light,
+ &__text--info--light {
+ color: $u-info;
+ &__close {
+ top: 11px;
+ // 头像图片组
+ default: uni.$u.props.avatarGroup.urls
+ // 最多展示的头像数量
+ default: uni.$u.props.avatarGroup.maxCount
+ // 头像形状
+ shape: {
+ default: uni.$u.props.avatarGroup.shape
+ // 图片裁剪模式
+ mode: {
+ default: uni.$u.props.avatarGroup.mode
+ default: uni.$u.props.avatarGroup.showMore
+ // 头像大小
+ default: uni.$u.props.avatarGroup.size
+ default: uni.$u.props.avatarGroup.keyName
+ // 头像之间的遮挡比例
+ gap: {
+ validator(value) {
+ return value >= 0 && value <= 1
+ default: uni.$u.props.avatarGroup.gap
+ // 需额外显示的值
+ extraValue: {
+ type: [Number, String],
+ default: uni.$u.props.avatarGroup.extraValue
@@ -0,0 +1,103 @@
+ <view class="u-avatar-group">
+ class="u-avatar-group__item"
+ v-for="(item, index) in showUrl"
+ :style="{
+ marginLeft: index === 0 ? 0 : $u.addUnit(-size * gap)
+ }"
+ <u-avatar
+ :src="$u.test.object(item) ? keyName && item[keyName] || item.url : item"
+ ></u-avatar>
+ class="u-avatar-group__item__show-more"
+ v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
+ @tap="clickHandler"
+ color="#ffffff"
+ :size="size * 0.4"
+ :text="`+${extraValue || urls.length - showUrl.length}`"
+ * AvatarGroup 头像组
+ * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
+ * @tutorial https://www.uviewui.com/components/avatar.html
+ * @property {Array} urls 头像图片组 (默认 [] )
+ * @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
+ * @property {String} shape 头像形状( 'circle' (默认) | 'square' )
+ * @property {String} mode 图片裁剪模式(默认 'scaleToFill' )
+ * @property {String | Number} size 头像大小 (默认 40 )
+ * @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 )
+ * @property {String | Number} extraValue 需额外显示的值
+ * @event {Function} showMore 头像组更多点击
+ * @example <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
+ name: 'u-avatar-group',
+ showUrl() {
+ return this.urls.slice(0, this.maxCount)
+ this.$emit('showMore')
+ .u-avatar-group {
+ margin-left: -10px;
+ &--no-indent {
+ // 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
+ margin-left: 0;
+ &__show-more {
+ border-radius: 100px;
@@ -0,0 +1,78 @@
+ // 头像图片路径(不能为相对路径)
+ src: {
+ default: uni.$u.props.avatar.src
+ // 头像形状,circle-圆形,square-方形
+ default: uni.$u.props.avatar.shape
+ // 头像尺寸
+ default: uni.$u.props.avatar.size
+ // 裁剪模式
+ default: uni.$u.props.avatar.mode
+ // 显示的文字
+ default: uni.$u.props.avatar.text
+ bgColor: {
+ default: uni.$u.props.avatar.bgColor
+ // 文字颜色
+ color: {
+ default: uni.$u.props.avatar.color
+ // 文字大小
+ default: uni.$u.props.avatar.fontSize
+ // 显示的图标
+ icon: {
+ default: uni.$u.props.avatar.icon
+ // 显示小程序头像,只对百度,微信,QQ小程序有效
+ mpAvatar: {
+ default: uni.$u.props.avatar.mpAvatar
+ // 是否使用随机背景色
+ randomBgColor: {
+ default: uni.$u.props.avatar.randomBgColor
+ // 加载失败的默认头像(组件有内置默认图片)
+ defaultUrl: {
+ default: uni.$u.props.avatar.defaultUrl
+ // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
+ colorIndex: {
+ // 校验参数规则,索引在0-19之间
+ validator(n) {
+ return uni.$u.test.range(n, [0, 19]) || n === ''
+ default: uni.$u.props.avatar.colorIndex
+ // 组件标识符
+ name: {
+ default: uni.$u.props.avatar.name
+ // 返回顶部的形状,circle-圆形,square-方形
+ default: uni.$u.props.backtop.mode
+ // 自定义图标
+ default: uni.$u.props.backtop.icon
+ // 提示文字
+ default: uni.$u.props.backtop.text
+ // 返回顶部滚动时间
+ duration: {
+ default: uni.$u.props.backtop.duration
+ // 滚动距离
+ scrollTop: {
+ default: uni.$u.props.backtop.scrollTop
+ // 距离顶部多少距离显示,单位px
+ top: {
+ default: uni.$u.props.backtop.top
+ // 返回顶部按钮到底部的距离,单位px
+ bottom: {
+ default: uni.$u.props.backtop.bottom
+ // 返回顶部按钮到右边的距离,单位px
+ right: {
+ default: uni.$u.props.backtop.right
+ // 层级
+ zIndex: {
+ default: uni.$u.props.backtop.zIndex
+ // 图标的样式,对象形式
+ iconStyle: {
+ type: Object,
+ default: uni.$u.props.backtop.iconStyle
@@ -0,0 +1,129 @@
+ :customStyle="backTopStyle"
+ class="u-back-top"
+ :style="[contentStyle]"
+ v-if="!$slots.default && !$slots.$default"
+ @click="backToTop"
+ :name="icon"
+ :custom-style="iconStyle"
+ v-if="text"
+ class="u-back-top__text"
+ >{{text}}</text>
+ <slot v-else />
+ const dom = weex.requireModule('dom')
+ * backTop 返回顶部
+ * @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
+ * @tutorial https://uviewui.com/components/backTop.html
+ * @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' )
+ * @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例
+ * @property {String} text 提示文字
+ * @property {String | Number} duration 返回顶部滚动时间 (默认 100)
+ * @property {String | Number} scrollTop 滚动距离 (默认 0 )
+ * @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 )
+ * @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 )
+ * @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 )
+ * @property {String | Number} zIndex 层级 (默认 9 )
+ * @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'})
+ * @example <u-back-top :scrollTop="scrollTop"></u-back-top>
+ name: 'u-back-top',
+ mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+ backTopStyle() {
+ // 动画组件样式
+ bottom: uni.$u.addUnit(this.bottom),
+ right: uni.$u.addUnit(this.right),
+ width: '40px',
+ height: '40px',
+ position: 'fixed',
+ zIndex: 10,
+ show() {
+ return uni.$u.getPx(this.scrollTop) > uni.$u.getPx(this.top)
+ contentStyle() {
+ const style = {}
+ let radius = 0
+ // 是否圆形
+ if(this.mode === 'circle') {
+ radius = '100px'
+ radius = '4px'
+ // 为了兼容安卓nvue,只能这么分开写
+ style.borderTopLeftRadius = radius
+ style.borderTopRightRadius = radius
+ style.borderBottomLeftRadius = radius
+ style.borderBottomRightRadius = radius
+ return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
+ backToTop() {
+ if (!this.$parent.$refs['u-back-top']) {
+ uni.$u.error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`)
+ dom.scrollToElement(this.$parent.$refs['u-back-top'], {
+ offset: 0
+ uni.pageScrollTo({
+ scrollTop: 0,
+ duration: this.duration
+ @import '../../libs/css/components.scss';
+ $u-back-top-flex:1 !default;
+ $u-back-top-height:100% !default;
+ $u-back-top-background-color:#E1E1E1 !default;
+ $u-back-top-tips-font-size:12px !default;
+ .u-back-top {
+ flex:$u-back-top-flex;
+ height: $u-back-top-height;
+ background-color: $u-back-top-background-color;
+ &__tips {
+ font-size:$u-back-top-tips-font-size;
+ transform: scale(0.8);
+ // 是否显示圆点
+ isDot: {
+ default: uni.$u.props.badge.isDot
+ // 显示的内容
+ value: {
+ default: uni.$u.props.badge.value
+ // 是否显示
+ default: uni.$u.props.badge.show
+ // 最大值,超过最大值会显示 '{max}+'
+ max: {
+ default: uni.$u.props.badge.max
+ // 主题类型,error|warning|success|primary
+ default: uni.$u.props.badge.type
+ // 当数值为 0 时,是否展示 Badge
+ showZero: {
+ default: uni.$u.props.badge.showZero
+ // 背景颜色,优先级比type高,如设置,type参数会失效
+ type: [String, null],
+ default: uni.$u.props.badge.bgColor
+ // 字体颜色
+ default: uni.$u.props.badge.color
+ // 徽标形状,circle-四角均为圆角,horn-左下角为直角
+ default: uni.$u.props.badge.shape
+ // 设置数字的显示方式,overflow|ellipsis|limit
+ // overflow会根据max字段判断,超出显示`${max}+`
+ // ellipsis会根据max判断,超出显示`${max}...`
+ // limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
+ numberType: {
+ default: uni.$u.props.badge.numberType
+ // 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+ offset: {
+ default: uni.$u.props.badge.offset
+ // 是否反转背景和字体颜色
+ inverted: {
+ default: uni.$u.props.badge.inverted
+ // 是否绝对定位
+ absolute: {
+ default: uni.$u.props.badge.absolute
@@ -0,0 +1,171 @@
+ v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
+ :class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
+ :style="[$u.addStyle(customStyle), badgeStyle]"
+ class="u-badge"
+ >{{ isDot ? '' :showValue }}</text>
+ * badge 徽标数
+ * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
+ * @tutorial https://uviewui.com/components/badge.html
+ * @property {Boolean} isDot 是否显示圆点 (默认 false )
+ * @property {String | Number} value 显示的内容
+ * @property {Boolean} show 是否显示 (默认 true )
+ * @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999)
+ * @property {String} type 主题类型,error|warning|success|primary (默认 'error' )
+ * @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false )
+ * @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
+ * @property {String} color 字体颜色 (默认 '#ffffff' )
+ * @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
+ * @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' )
+ * @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+ * @property {Boolean} inverted 是否反转背景和字体颜色(默认 false )
+ * @property {Boolean} absolute 是否绝对定位(默认 false )
+ * @example <u-badge :type="type" :count="count"></u-badge>
+ name: 'u-badge',
+ // 是否将badge中心与父组件右上角重合
+ boxStyle() {
+ // 整个组件的样式
+ badgeStyle() {
+ if(this.color) {
+ style.color = this.color
+ if (this.bgColor && !this.inverted) {
+ style.backgroundColor = this.bgColor
+ if (this.absolute) {
+ style.position = 'absolute'
+ // 如果有设置offset参数
+ if(this.offset.length) {
+ // top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
+ const top = this.offset[0]
+ const right = this.offset[1] || top
+ style.top = uni.$u.addUnit(top)
+ style.right = uni.$u.addUnit(right)
+ showValue() {
+ switch (this.numberType) {
+ case "overflow":
+ return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
+ case "ellipsis":
+ return Number(this.value) > Number(this.max) ? "..." : this.value
+ case "limit":
+ return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
+ Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
+ 1e3 * 100) / 100 + "k" : this.value
+ return Number(this.value)
+ $u-badge-primary: $u-primary !default;
+ $u-badge-error: $u-error !default;
+ $u-badge-success: $u-success !default;
+ $u-badge-info: $u-info !default;
+ $u-badge-warning: $u-warning !default;
+ $u-badge-dot-radius: 100px !default;
+ $u-badge-dot-size: 8px !default;
+ $u-badge-dot-right: 4px !default;
+ $u-badge-dot-top: 0 !default;
+ $u-badge-text-font-size: 11px !default;
+ $u-badge-text-right: 10px !default;
+ $u-badge-text-padding: 2px 5px !default;
+ $u-badge-text-align: center !default;
+ $u-badge-text-color: #FFFFFF !default;
+ .u-badge {
+ border-top-right-radius: $u-badge-dot-radius;
+ border-top-left-radius: $u-badge-dot-radius;
+ border-bottom-left-radius: $u-badge-dot-radius;
+ border-bottom-right-radius: $u-badge-dot-radius;
+ line-height: $u-badge-text-font-size;
+ text-align: $u-badge-text-align;
+ font-size: $u-badge-text-font-size;
+ color: $u-badge-text-color;
+ &--dot {
+ height: $u-badge-dot-size;
+ width: $u-badge-dot-size;
+ &--inverted {
+ font-size: 13px;
+ &--not-dot {
+ padding: $u-badge-text-padding;
+ &--horn {
+ border-bottom-left-radius: 0;
+ &--primary {
+ background-color: $u-badge-primary;
+ &--primary--inverted {
+ color: $u-badge-primary;
+ &--error {
+ background-color: $u-badge-error;
+ &--error--inverted {
+ color: $u-badge-error;
+ &--success {
+ background-color: $u-badge-success;
+ &--success--inverted {
+ color: $u-badge-success;
+ &--info {
+ background-color: $u-badge-info;
+ &--info--inverted {
+ color: $u-badge-info;
+ &--warning {
+ background-color: $u-badge-warning;
+ &--warning--inverted {
+ color: $u-badge-warning;
@@ -0,0 +1,46 @@
+$u-button-active-opacity:0.75 !default;
+$u-button-loading-text-margin-left:4px !default;
+$u-button-text-color: #FFFFFF !default;
+$u-button-text-plain-error-color:$u-error !default;
+$u-button-text-plain-warning-color:$u-warning !default;
+$u-button-text-plain-success-color:$u-success !default;
+$u-button-text-plain-info-color:$u-info !default;
+$u-button-text-plain-primary-color:$u-primary !default;
+.u-button {
+ &--active {
+ opacity: $u-button-active-opacity;
+ &--active--plain {
+ background-color: rgb(217, 217, 217);
+ &__loading-text {
+ margin-left:$u-button-loading-text-margin-left;
+ &__text,
+ color:$u-button-text-color;
+ &__text--plain--error {
+ color:$u-button-text-plain-error-color;
+ &__text--plain--warning {
+ color:$u-button-text-plain-warning-color;
+ &__text--plain--success{
+ color:$u-button-text-plain-success-color;
+ &__text--plain--info {
+ color:$u-button-text-plain-info-color;
+ &__text--plain--primary {
+ color:$u-button-text-plain-primary-color;
@@ -0,0 +1,161 @@
+/*
+ * @Author : LQ
+ * @Description :
+ * @version : 1.0
+ * @Date : 2021-08-16 10:04:04
+ * @LastAuthor : LQ
+ * @lastTime : 2021-08-16 10:04:24
+ * @FilePath : /u-view2.0/uview-ui/components/u-button/props.js
+ // 是否细边框
+ hairline: {
+ default: uni.$u.props.button.hairline
+ // 按钮的预置样式,info,primary,error,warning,success
+ default: uni.$u.props.button.type
+ // 按钮尺寸,large,normal,small,mini
+ default: uni.$u.props.button.size
+ // 按钮形状,circle(两边为半圆),square(带圆角)
+ default: uni.$u.props.button.shape
+ // 按钮是否镂空
+ plain: {
+ default: uni.$u.props.button.plain
+ // 是否禁止状态
+ disabled: {
+ default: uni.$u.props.button.disabled
+ // 是否加载中
+ loading: {
+ default: uni.$u.props.button.loading
+ // 加载中提示文字
+ loadingText: {
+ default: uni.$u.props.button.loadingText
+ // 加载状态图标类型
+ loadingMode: {
+ default: uni.$u.props.button.loadingMode
+ // 加载图标大小
+ loadingSize: {
+ default: uni.$u.props.button.loadingSize
+ // 开放能力,具体请看uniapp稳定关于button组件部分说明
+ // https://uniapp.dcloud.io/component/button
+ default: uni.$u.props.button.openType
+ // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+ // 取值为submit(提交表单),reset(重置表单)
+ formType: {
+ default: uni.$u.props.button.formType
+ // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
+ // 只微信小程序、QQ小程序有效
+ appParameter: {
+ default: uni.$u.props.button.appParameter
+ // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
+ hoverStopPropagation: {
+ default: uni.$u.props.button.hoverStopPropagation
+ // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
+ lang: {
+ default: uni.$u.props.button.lang
+ // 会话来源,open-type="contact"时有效。只微信小程序有效
+ sessionFrom: {
+ default: uni.$u.props.button.sessionFrom
+ // 会话内消息卡片标题,open-type="contact"时有效
+ // 默认当前标题,只微信小程序有效
+ sendMessageTitle: {
+ default: uni.$u.props.button.sendMessageTitle
+ // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
+ // 默认当前分享路径,只微信小程序有效
+ sendMessagePath: {
+ default: uni.$u.props.button.sendMessagePath
+ // 会话内消息卡片图片,open-type="contact"时有效
+ // 默认当前页面截图,只微信小程序有效
+ sendMessageImg: {
+ default: uni.$u.props.button.sendMessageImg
+ // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
+ // 用户点击后可以快速发送小程序消息,open-type="contact"时有效
+ showMessageCard: {
+ default: uni.$u.props.button.showMessageCard
+ // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+ dataName: {
+ default: uni.$u.props.button.dataName
+ // 节流,一定时间内只能触发一次
+ throttleTime: {
+ default: uni.$u.props.button.throttleTime
+ // 按住后多久出现点击态,单位毫秒
+ hoverStartTime: {
+ default: uni.$u.props.button.hoverStartTime
+ // 手指松开后点击态保留时间,单位毫秒
+ hoverStayTime: {
+ default: uni.$u.props.button.hoverStayTime
+ // 按钮文字,之所以通过props传入,是因为slot传入的话
+ // nvue中无法控制文字的样式
+ default: uni.$u.props.button.text
+ // 按钮图标
+ default: uni.$u.props.button.icon
+ iconColor: {
+ // 按钮颜色,支持传入linear-gradient渐变色
+ default: uni.$u.props.button.color
@@ -0,0 +1,495 @@
+ :hover-start-time="Number(hoverStartTime)"
+ :hover-stay-time="Number(hoverStayTime)"
+ :form-type="formType"
+ :open-type="openType"
+ :hover-stop-propagation="hoverStopPropagation"
+ :data-name="dataName"
+ @getphonenumber="getphonenumber"
+ @getuserinfo="getuserinfo"
+ @error="error"
+ @opensetting="opensetting"
+ @launchapp="launchapp"
+ @agreeprivacyauthorization="agreeprivacyauthorization"
+ :hover-class="!disabled && !loading ? 'u-button--active' : ''"
+ class="u-button u-reset-button"
+ :style="[baseColor, $u.addStyle(customStyle)]"
+ :class="bemClass"
+ <template v-if="loading">
+ :mode="loadingMode"
+ :size="loadingSize * 1.15"
+ :color="loadingColor"
+ ></u-loading-icon>
+ class="u-button__loading-text"
+ :style="[{ fontSize: textSize + 'px' }]"
+ >{{ loadingText || text }}</text
+ <template v-else>
+ v-if="icon"
+ :color="iconColorCom"
+ :size="textSize * 1.35"
+ :customStyle="{ marginRight: '2px' }"
+ class="u-button__text"
+ >{{ text }}</text
+ class="u-button"
+ :hover-class="
+ !disabled && !loading && !color && (plain || type === 'info')
+ ? 'u-button--active--plain'
+ : !disabled && !loading && !plain
+ ? 'u-button--active'
+ : ''
+ :style="[nvueTextStyle]"
+ :class="[plain && `u-button__text--plain--${type}`]"
+ marginLeft: icon ? '2px' : 0,
+ nvueTextStyle,
+import button from "../../libs/mixin/button.js";
+import openType from "../../libs/mixin/openType.js";
+import props from "./props.js";
+ * button 按钮
+ * @description Button 按钮
+ * @tutorial https://www.uviewui.com/components/button.html
+ * @property {Boolean} hairline 是否显示按钮的细边框 (默认 true )
+ * @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' )
+ * @property {String} size 按钮尺寸,large,normal,mini (默认 normal)
+ * @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
+ * @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false)
+ * @property {Boolean} disabled 是否禁用 (默认 false)
+ * @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
+ * @property {String | Number} loadingText 加载中提示文字
+ * @property {String} loadingMode 加载状态图标类型 (默认 'spinner' )
+ * @property {String | Number} loadingSize 加载图标大小 (默认 15 )
+ * @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明
+ * @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+ * @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
+ * @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
+ * @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
+ * @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
+ * @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+ * @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 )
+ * @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 )
+ * @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 )
+ * @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
+ * @property {String} icon 按钮图标
+ * @property {String} iconColor 按钮图标颜色
+ * @property {String} color 按钮颜色,支持传入linear-gradient渐变色
+ * @event {Function} click 非禁止并且非加载中,才能点击
+ * @event {Function} getphonenumber open-type="getPhoneNumber"时有效
+ * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
+ * @event {Function} error 当使用开放能力时,发生错误的回调
+ * @event {Function} opensetting 在打开授权设置页并关闭后回调
+ * @event {Function} launchapp 打开 APP 成功的回调
+ * @event {Function} agreeprivacyauthorization 用户同意隐私协议事件回调
+ * @example <u-button>月落</u-button>
+ name: "u-button",
+ mixins: [uni.$u.mpMixin, uni.$u.mixin, button, openType, props],
+ return {};
+ // 生成bem风格的类名
+ bemClass() {
+ // this.bem为一个computed变量,在mixin中
+ if (!this.color) {
+ return this.bem(
+ "button",
+ ["type", "shape", "size"],
+ ["disabled", "plain", "hairline"]
+ // 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
+ ["shape", "size"],
+ loadingColor() {
+ if (this.plain) {
+ // 如果有设置color值,则用color值,否则使用type主题颜色
+ return this.color
+ ? this.color
+ : uni.$u.config.color[`u-${this.type}`];
+ if (this.type === "info") {
+ return "#c9c9c9";
+ return "rgb(200, 200, 200)";
+ iconColorCom() {
+ // 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
+ // u-icon的color能接受一个主题颜色的值
+ if (this.iconColor) return this.iconColor;
+ return this.color ? this.color : this.type;
+ return this.type === "info" ? "#000000" : "#ffffff";
+ baseColor() {
+ if (this.color) {
+ // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+ style.color = this.plain ? this.color : "white";
+ if (!this.plain) {
+ // 非镂空,背景色使用自定义的颜色
+ style["background-color"] = this.color;
+ if (this.color.indexOf("gradient") !== -1) {
+ // 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
+ // weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
+ // 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
+ style.borderTopWidth = 0;
+ style.borderRightWidth = 0;
+ style.borderBottomWidth = 0;
+ style.borderLeftWidth = 0;
+ style.backgroundImage = this.color;
+ // 非渐变色,则设置边框相关的属性
+ style.borderColor = this.color;
+ style.borderWidth = "1px";
+ style.borderStyle = "solid";
+ // nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
+ nvueTextStyle() {
+ style.color = "#323233";
+ style.fontSize = this.textSize + "px";
+ textSize() {
+ let fontSize = 14,
+ { size } = this;
+ if (size === "large") fontSize = 16;
+ if (size === "normal") fontSize = 14;
+ if (size === "small") fontSize = 12;
+ if (size === "mini") fontSize = 10;
+ return fontSize;
+ // 非禁止并且非加载中,才能点击
+ if (!this.disabled && !this.loading) {
+ // 进行节流控制,每this.throttle毫秒内,只在开始处执行
+ uni.$u.throttle(() => {
+ this.$emit("click");
+ }, this.throttleTime);
+ // 下面为对接uniapp官方按钮开放能力事件回调的对接
+ getphonenumber(res) {
+ this.$emit("getphonenumber", res);
+ getuserinfo(res) {
+ this.$emit("getuserinfo", res);
+ this.$emit("error", res);
+ opensetting(res) {
+ this.$emit("opensetting", res);
+ launchapp(res) {
+ this.$emit("launchapp", res);
+ agreeprivacyauthorization(res) {
+ this.$emit("agreeprivacyauthorization", res);
+@import "../../libs/css/components.scss";
+@import "./vue.scss";
+/* #ifdef APP-NVUE */
+@import "./nvue.scss";
+$u-button-u-button-height: 40px !default;
+$u-button-text-font-size: 15px !default;
+$u-button-loading-text-font-size: 15px !default;
+$u-button-loading-text-margin-left: 4px !default;
+$u-button-large-width: 100% !default;
+$u-button-large-height: 50px !default;
+$u-button-normal-padding: 0 12px !default;
+$u-button-large-padding: 0 15px !default;
+$u-button-normal-font-size: 14px !default;
+$u-button-small-min-width: 60px !default;
+$u-button-small-height: 30px !default;
+$u-button-small-padding: 0px 8px !default;
+$u-button-mini-padding: 0px 8px !default;
+$u-button-small-font-size: 12px !default;
+$u-button-mini-height: 22px !default;
+$u-button-mini-font-size: 10px !default;
+$u-button-mini-min-width: 50px !default;
+$u-button-disabled-opacity: 0.5 !default;
+$u-button-info-color: #323233 !default;
+$u-button-info-background-color: #fff !default;
+$u-button-info-border-color: #ebedf0 !default;
+$u-button-info-border-width: 1px !default;
+$u-button-info-border-style: solid !default;
+$u-button-success-color: #fff !default;
+$u-button-success-background-color: $u-success !default;
+$u-button-success-border-color: $u-button-success-background-color !default;
+$u-button-success-border-width: 1px !default;
+$u-button-success-border-style: solid !default;
+$u-button-primary-color: #fff !default;
+$u-button-primary-background-color: $u-primary !default;
+$u-button-primary-border-color: $u-button-primary-background-color !default;
+$u-button-primary-border-width: 1px !default;
+$u-button-primary-border-style: solid !default;
+$u-button-error-color: #fff !default;
+$u-button-error-background-color: $u-error !default;
+$u-button-error-border-color: $u-button-error-background-color !default;
+$u-button-error-border-width: 1px !default;
+$u-button-error-border-style: solid !default;
+$u-button-warning-color: #fff !default;
+$u-button-warning-background-color: $u-warning !default;
+$u-button-warning-border-color: $u-button-warning-background-color !default;
+$u-button-warning-border-width: 1px !default;
+$u-button-warning-border-style: solid !default;
+$u-button-block-width: 100% !default;
+$u-button-circle-border-top-right-radius: 100px !default;
+$u-button-circle-border-top-left-radius: 100px !default;
+$u-button-circle-border-bottom-left-radius: 100px !default;
+$u-button-circle-border-bottom-right-radius: 100px !default;
+$u-button-square-border-top-right-radius: 3px !default;
+$u-button-square-border-top-left-radius: 3px !default;
+$u-button-square-border-bottom-left-radius: 3px !default;
+$u-button-square-border-bottom-right-radius: 3px !default;
+$u-button-icon-min-width: 1em !default;
+$u-button-plain-background-color: #fff !default;
+$u-button-hairline-border-width: 0.5px !default;
+ height: $u-button-u-button-height;
+ font-size: $u-button-text-font-size;
+ font-size: $u-button-loading-text-font-size;
+ margin-left: $u-button-loading-text-margin-left;
+ &--large {
+ width: $u-button-large-width;
+ height: $u-button-large-height;
+ padding: $u-button-large-padding;
+ &--normal {
+ padding: $u-button-normal-padding;
+ font-size: $u-button-normal-font-size;
+ &--small {
+ min-width: $u-button-small-min-width;
+ height: $u-button-small-height;
+ padding: $u-button-small-padding;
+ font-size: $u-button-small-font-size;
+ &--mini {
+ height: $u-button-mini-height;
+ font-size: $u-button-mini-font-size;
+ min-width: $u-button-mini-min-width;
+ padding: $u-button-mini-padding;
+ &--disabled {
+ opacity: $u-button-disabled-opacity;
+ color: $u-button-info-color;
+ background-color: $u-button-info-background-color;
+ border-color: $u-button-info-border-color;
+ border-width: $u-button-info-border-width;
+ border-style: $u-button-info-border-style;
+ color: $u-button-success-color;
+ background-color: $u-button-success-background-color;
+ border-color: $u-button-success-border-color;
+ border-width: $u-button-success-border-width;
+ border-style: $u-button-success-border-style;
+ color: $u-button-primary-color;
+ background-color: $u-button-primary-background-color;
+ border-color: $u-button-primary-border-color;
+ border-width: $u-button-primary-border-width;
+ border-style: $u-button-primary-border-style;
+ color: $u-button-error-color;
+ background-color: $u-button-error-background-color;
+ border-color: $u-button-error-border-color;
+ border-width: $u-button-error-border-width;
+ border-style: $u-button-error-border-style;
+ color: $u-button-warning-color;
+ background-color: $u-button-warning-background-color;
+ border-color: $u-button-warning-border-color;
+ border-width: $u-button-warning-border-width;
+ border-style: $u-button-warning-border-style;
+ &--block {
+ width: $u-button-block-width;
+ &--circle {
+ border-top-right-radius: $u-button-circle-border-top-right-radius;
+ border-top-left-radius: $u-button-circle-border-top-left-radius;
+ border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;
+ border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;
+ &--square {
+ border-bottom-left-radius: $u-button-square-border-top-right-radius;
+ border-bottom-right-radius: $u-button-square-border-top-left-radius;
+ border-top-left-radius: $u-button-square-border-bottom-left-radius;
+ border-top-right-radius: $u-button-square-border-bottom-right-radius;
+ min-width: $u-button-icon-min-width;
+ line-height: inherit !important;
+ vertical-align: top;
+ &--plain {
+ background-color: $u-button-plain-background-color;
+ &--hairline {
+ border-width: $u-button-hairline-border-width !important;
@@ -0,0 +1,80 @@
+// nvue下hover-class无效
+$u-button-before-top:50% !default;
+$u-button-before-left:50% !default;
+$u-button-before-width:100% !default;
+$u-button-before-height:100% !default;
+$u-button-before-transform:translate(-50%, -50%) !default;
+$u-button-before-opacity:0 !default;
+$u-button-before-background-color:#000 !default;
+$u-button-before-border-color:#000 !default;
+$u-button-active-before-opacity:.15 !default;
+$u-button-icon-margin-left:4px !default;
+$u-button-plain-u-button-info-color:$u-info;
+$u-button-plain-u-button-success-color:$u-success;
+$u-button-plain-u-button-error-color:$u-error;
+$u-button-plain-u-button-warning-color:$u-error;
+ line-height: 1;
+ &:before {
+ top:$u-button-before-top;
+ left:$u-button-before-left;
+ width:$u-button-before-width;
+ height:$u-button-before-height;
+ border: inherit;
+ border-radius: inherit;
+ transform:$u-button-before-transform;
+ opacity:$u-button-before-opacity;
+ content: " ";
+ background-color:$u-button-before-background-color;
+ border-color:$u-button-before-border-color;
+ opacity: .15
+ &__icon+&__text:not(:empty),
+ margin-left:$u-button-icon-margin-left;
+ &.u-button--primary {
+ &.u-button--info {
+ color:$u-button-plain-u-button-info-color;
+ &.u-button--success {
+ color:$u-button-plain-u-button-success-color;
+ &.u-button--error {
+ color:$u-button-plain-u-button-error-color;
+ &.u-button--warning {
+ color:$u-button-plain-u-button-warning-color;
@@ -0,0 +1,99 @@
+ <view class="u-calendar-header u-border-bottom">
+ class="u-calendar-header__title"
+ v-if="showTitle"
+ class="u-calendar-header__subtitle"
+ v-if="showSubtitle"
+ >{{ subtitle }}</text>
+ <view class="u-calendar-header__weekdays">
+ <text class="u-calendar-header__weekdays__weekday">一</text>
+ <text class="u-calendar-header__weekdays__weekday">二</text>
+ <text class="u-calendar-header__weekdays__weekday">三</text>
+ <text class="u-calendar-header__weekdays__weekday">四</text>
+ <text class="u-calendar-header__weekdays__weekday">五</text>
+ <text class="u-calendar-header__weekdays__weekday">六</text>
+ <text class="u-calendar-header__weekdays__weekday">日</text>
+ name: 'u-calendar-header',
+ mixins: [uni.$u.mpMixin, uni.$u.mixin],
+ default: ''
+ // 副标题
+ subtitle: {
+ // 是否显示标题
+ showTitle: {
+ // 是否显示副标题
+ showSubtitle: {
+ name() {
+ .u-calendar-header {
+ padding-bottom: 4px;
+ &__subtitle {
+ height: 40px;
+ line-height: 40px;
+ &__weekdays {
+ &__weekday {
@@ -0,0 +1,579 @@
+ <view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
+ <view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
+ :ref="`u-calendar-month-${index}`" :id="`month-${index}`">
+ <text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
+ <view class="u-calendar-month__days">
+ <view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
+ <text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
+ <view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
+ :style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
+ :class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
+ <view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
+ <text class="u-calendar-month__days__day__select__info"
+ :class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']"
+ :style="[textStyle(item1)]">{{ item1.day }}</text>
+ <text v-if="getBottomInfo(index, index1, item1)"
+ class="u-calendar-month__days__day__select__buttom-info"
+ :class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']"
+ :style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
+ <text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
+ // 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
+ const dom = uni.requireNativePlugin('dom')
+ import dayjs from '../../libs/util/dayjs.js';
+ name: 'u-calendar-month',
+ // 是否显示月份背景色
+ showMark: {
+ // 主题色,对底部按钮和选中日期有效
+ default: '#3c9cff'
+ // 月份数据
+ months: {
+ default: () => []
+ // 日期选择类型
+ default: 'single'
+ // 日期行高
+ rowHeight: {
+ default: 58
+ // mode=multiple时,最多可选多少个日期
+ default: Infinity
+ // mode=range时,第一个日期底部的提示文字
+ startText: {
+ default: '开始'
+ // mode=range时,最后一个日期底部的提示文字
+ endText: {
+ default: '结束'
+ // 默认选中的日期,mode为multiple或range是必须为数组格式
+ defaultDate: {
+ type: [Array, String, Date],
+ default: null
+ // 最小的可选日期
+ minDate: {
+ default: 0
+ // 最大可选日期
+ maxDate: {
+ // 如果没有设置maxDate,则往后推多少个月
+ maxMonth: {
+ default: 2
+ // 是否为只读状态,只读状态下禁止选择日期
+ readonly: {
+ default: uni.$u.props.calendar.readonly
+ // 日期区间最多可选天数,默认无限制,mode = range时有效
+ maxRange: {
+ // 范围选择超过最多可选天数时的提示文案,mode = range时有效
+ rangePrompt: {
+ // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+ showRangePrompt: {
+ // 是否允许日期范围的起止时间为同一天,mode = range时有效
+ allowSameDay: {
+ // 每个日期的宽度
+ width: 0,
+ // 当前选中的日期item
+ item: {},
+ selected: []
+ selectedChange: {
+ handler(n) {
+ this.setDefaultDate()
+ // 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+ selectedChange() {
+ return [this.minDate, this.maxDate, this.defaultDate]
+ dayStyle(index1, index2, item) {
+ return (index1, index2, item) => {
+ let week = item.week
+ // 不进行四舍五入的形式保留2位小数
+ const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
+ // 得出每个日期的宽度
+ style.width = uni.$u.addUnit(dayWidth)
+ style.height = uni.$u.addUnit(this.rowHeight)
+ if (index2 === 0) {
+ // 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
+ week = (week === 0 ? 7 : week) - 1
+ style.marginLeft = uni.$u.addUnit(week * dayWidth)
+ if (this.mode === 'range') {
+ // 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+ style.paddingLeft = 0
+ style.paddingRight = 0
+ style.paddingBottom = 0
+ style.paddingTop = 0
+ daySelectStyle() {
+ let date = dayjs(item.date).format("YYYY-MM-DD"),
+ style = {}
+ // 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
+ if (this.selected.some(item => this.dateSame(item, date))) {
+ style.backgroundColor = this.color
+ if (this.mode === 'single') {
+ if (date === this.selected[0]) {
+ // 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
+ style.borderTopLeftRadius = '3px'
+ style.borderBottomLeftRadius = '3px'
+ style.borderTopRightRadius = '3px'
+ style.borderBottomRightRadius = '3px'
+ } else if (this.mode === 'range') {
+ if (this.selected.length >= 2) {
+ const len = this.selected.length - 1
+ // 第一个日期设置左上角和左下角的圆角
+ if (this.dateSame(date, this.selected[0])) {
+ // 最后一个日期设置右上角和右下角的圆角
+ if (this.dateSame(date, this.selected[len])) {
+ // 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
+ if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+ .selected[len]))) {
+ style.backgroundColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[90]
+ // 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
+ style.opacity = 0.7
+ } else if (this.selected.length === 1) {
+ // 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
+ // 某个日期是否被选中
+ textStyle() {
+ return (item) => {
+ const date = dayjs(item.date).format("YYYY-MM-DD"),
+ // 选中的日期,提示文字设置白色
+ style.color = '#ffffff'
+ // 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
+ // 获取底部的提示文字
+ getBottomInfo() {
+ const date = dayjs(item.date).format("YYYY-MM-DD")
+ const bottomInfo = item.bottomInfo
+ // 当为日期范围模式时,且选择的日期个数大于0时
+ if (this.mode === 'range' && this.selected.length > 0) {
+ if (this.selected.length === 1) {
+ // 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
+ if (this.dateSame(date, this.selected[0])) return this.startText
+ else return bottomInfo
+ // 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
+ if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
+ len === 1) {
+ // 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
+ return `${this.startText}/${this.endText}`
+ } else if (this.dateSame(date, this.selected[0])) {
+ return this.startText
+ } else if (this.dateSame(date, this.selected[len])) {
+ return this.endText
+ return bottomInfo
+ // 初始化默认选中
+ this.$emit('monthSelected', this.selected)
+ // 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
+ // 因为nvue下,$nextTick并不是100%可靠的
+ uni.$u.sleep(10).then(() => {
+ this.getWrapperWidth()
+ this.getMonthRect()
+ // 判断两个日期是否相等
+ dateSame(date1, date2) {
+ return dayjs(date1).isSame(dayjs(date2))
+ // 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
+ getWrapperWidth() {
+ dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
+ this.width = res.size.width
+ this.$uGetRect('.u-calendar-month-wrapper').then(size => {
+ getMonthRect() {
+ // 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
+ const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
+ `u-calendar-month-${index}`))
+ // 一次性返回
+ Promise.all(promiseAllArr).then(
+ sizes => {
+ let height = 1
+ const topArr = []
+ for (let i = 0; i < this.months.length; i++) {
+ // 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
+ topArr[i] = height
+ height += sizes[i].height
+ // 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
+ this.$emit('updateMonthTop', topArr)
+ // 获取每个月份区域的尺寸
+ getMonthRectByPromise(el) {
+ // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
+ // 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
+ this.$uGetRect(`.${el}`).then(size => {
+ resolve(size)
+ // nvue下,使用dom模块查询元素高度
+ // 返回一个promise,让调用此方法的主体能使用then回调
+ dom.getComponentRect(this.$refs[el][0], res => {
+ resolve(res.size)
+ // 点击某一个日期
+ clickHandler(index1, index2, item) {
+ if (this.readonly) {
+ this.item = item
+ if (item.disabled) return
+ // 对上一次选择的日期数组进行深度克隆
+ let selected = uni.$u.deepClone(this.selected)
+ // 单选情况下,让数组中的元素为当前点击的日期
+ selected = [date]
+ } else if (this.mode === 'multiple') {
+ if (selected.some(item => this.dateSame(item, date))) {
+ // 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
+ const itemIndex = selected.findIndex(item => item === date)
+ selected.splice(itemIndex, 1)
+ // 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
+ if (selected.length < this.maxCount) selected.push(date)
+ // 选择区间形式
+ if (selected.length === 0 || selected.length >= 2) {
+ // 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
+ } else if (selected.length === 1) {
+ // 如果已经选择了开始日期
+ const existsDate = selected[0]
+ // 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
+ if (dayjs(date).isBefore(existsDate)) {
+ } else if (dayjs(date).isAfter(existsDate)) {
+ // 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
+ if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
+ if(this.rangePrompt) {
+ uni.$u.toast(this.rangePrompt)
+ uni.$u.toast(`选择天数不能超过 ${this.maxRange} 天`)
+ // 如果当前日期大于已有日期,将当前的添加到数组尾部
+ selected.push(date)
+ const startDate = selected[0]
+ const endDate = selected[1]
+ let i = 0
+ do {
+ // 将开始和结束日期之间的日期添加到数组中
+ arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
+ i++
+ // 累加的日期小于结束日期时,继续下一次的循环
+ } while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
+ // 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
+ arr.push(endDate)
+ selected = arr
+ // 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
+ if (selected[0] === date && !this.allowSameDay) return
+ this.setSelected(selected)
+ // 设置默认日期
+ setDefaultDate() {
+ if (!this.defaultDate) {
+ // 如果没有设置默认日期,则将当天日期设置为默认选中的日期
+ const selected = [dayjs().format("YYYY-MM-DD")]
+ return this.setSelected(selected, false)
+ let defaultDate = []
+ const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
+ const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
+ // 单选模式,可以是字符串或数组,Date对象等
+ if (!uni.$u.test.array(this.defaultDate)) {
+ defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
+ defaultDate = [this.defaultDate[0]]
+ // 如果为非数组,则不执行
+ if (!uni.$u.test.array(this.defaultDate)) return
+ defaultDate = this.defaultDate
+ // 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
+ defaultDate = defaultDate.filter(item => {
+ return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
+ maxDate).add(1, 'day'))
+ this.setSelected(defaultDate, false)
+ setSelected(selected, event = true) {
+ this.selected = selected
+ event && this.$emit('monthSelected', this.selected)
+ .u-calendar-month-wrapper {
+ margin-top: 4px;
+ .u-calendar-month {
+ &__days {
+ &__month-mark-wrapper {
+ font-size: 155px;
+ color: rgba(231, 232, 234, 0.83);
+ &__day {
+ padding: 2px;
+ // vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
+ width: calc(100% / 7);
+ &__select {
+ &__dot {
+ width: 7px;
+ height: 7px;
+ top: 12px;
+ right: 7px;
+ &__buttom-info {
+ bottom: 5px;
+ font-size: 10px;
+ &--selected {
+ color: #cacbcd;
+ &__info {
+ border-radius: 3px;
+ &--range-selected {
+ opacity: 0.3;
+ border-radius: 0;
+ &--range-start-selected {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ &--range-end-selected {
+ border-top-left-radius: 0;
@@ -0,0 +1,144 @@
+ // 日历顶部标题
+ default: uni.$u.props.calendar.title
+ default: uni.$u.props.calendar.showTitle
+ default: uni.$u.props.calendar.showSubtitle
+ // 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
+ default: uni.$u.props.calendar.mode
+ default: uni.$u.props.calendar.startText
+ default: uni.$u.props.calendar.endText
+ // 自定义列表
+ customList: {
+ default: uni.$u.props.calendar.customList
+ default: uni.$u.props.calendar.color
+ default: uni.$u.props.calendar.minDate
+ default: uni.$u.props.calendar.maxDate
+ type: [Array, String, Date, null],
+ default: uni.$u.props.calendar.defaultDate
+ default: uni.$u.props.calendar.maxCount
+ default: uni.$u.props.calendar.rowHeight
+ // 日期格式化函数
+ formatter: {
+ type: [Function, null],
+ default: uni.$u.props.calendar.formatter
+ // 是否显示农历
+ showLunar: {
+ default: uni.$u.props.calendar.showLunar
+ default: uni.$u.props.calendar.showMark
+ // 确定按钮的文字
+ confirmText: {
+ default: uni.$u.props.calendar.confirmText
+ // 确认按钮处于禁用状态时的文字
+ confirmDisabledText: {
+ default: uni.$u.props.calendar.confirmDisabledText
+ // 是否显示日历弹窗
+ default: uni.$u.props.calendar.show
+ // 是否允许点击遮罩关闭日历
+ default: uni.$u.props.calendar.closeOnClickOverlay
+ // 是否展示确认按钮
+ showConfirm: {
+ default: uni.$u.props.calendar.showConfirm
+ default: uni.$u.props.calendar.maxRange
+ default: uni.$u.props.calendar.rangePrompt
+ default: uni.$u.props.calendar.showRangePrompt
+ default: uni.$u.props.calendar.allowSameDay
+ type: [Boolean, String, Number],
+ default: uni.$u.props.calendar.round
+ // 最多展示月份数量
+ monthNum: {
+ default: 3
@@ -0,0 +1,384 @@
+ closeable
+ :closeOnClickOverlay="closeOnClickOverlay"
+ <view class="u-calendar">
+ <uHeader
+ :title="title"
+ :subtitle="subtitle"
+ :showSubtitle="showSubtitle"
+ :showTitle="showTitle"
+ ></uHeader>
+ height: $u.addUnit(listHeight)
+ scroll-y
+ @scroll="onScroll"
+ :scroll-top="scrollTop"
+ :scrollIntoView="scrollIntoView"
+ <uMonth
+ :rowHeight="rowHeight"
+ :showMark="showMark"
+ :months="months"
+ :maxCount="maxCount"
+ :startText="startText"
+ :endText="endText"
+ :defaultDate="defaultDate"
+ :minDate="innerMinDate"
+ :maxDate="innerMaxDate"
+ :maxMonth="monthNum"
+ :maxRange="maxRange"
+ :rangePrompt="rangePrompt"
+ :showRangePrompt="showRangePrompt"
+ :allowSameDay="allowSameDay"
+ ref="month"
+ @monthSelected="monthSelected"
+ @updateMonthTop="updateMonthTop"
+ ></uMonth>
+ <slot name="footer" v-if="showConfirm">
+ <view class="u-calendar__confirm">
+ :text="
+ buttonDisabled ? confirmDisabledText : confirmText
+ @click="confirm"
+ :disabled="buttonDisabled"
+import uHeader from './header.vue'
+import uMonth from './month.vue'
+import util from './util.js'
+import dayjs from '../../libs/util/dayjs.js'
+import Calendar from '../../libs/util/calendar.js'
+ * Calendar 日历
+ * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
+ * @tutorial https://www.uviewui.com/components/calendar.html
+ * @property {String} title 标题内容 (默认 日期选择 )
+ * @property {Boolean} showTitle 是否显示标题 (默认 true )
+ * @property {Boolean} showSubtitle 是否显示副标题 (默认 true )
+ * @property {String} mode 日期类型选择 single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' )
+ * @property {String} startText mode=range时,第一个日期底部的提示文字 (默认 '开始' )
+ * @property {String} endText mode=range时,最后一个日期底部的提示文字 (默认 '结束' )
+ * @property {Array} customList 自定义列表
+ * @property {String} color 主题色,对底部按钮和选中日期有效 (默认 ‘#3c9cff' )
+ * @property {String | Number} minDate 最小的可选日期 (默认 0 )
+ * @property {String | Number} maxDate 最大可选日期 (默认 0 )
+ * @property {Array | String| Date} defaultDate 默认选中的日期,mode为multiple或range是必须为数组格式
+ * @property {String | Number} maxCount mode=multiple时,最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER )
+ * @property {String | Number} rowHeight 日期行高 (默认 56 )
+ * @property {Function} formatter 日期格式化函数
+ * @property {Boolean} showLunar 是否显示农历 (默认 false )
+ * @property {Boolean} showMark 是否显示月份背景色 (默认 true )
+ * @property {String} confirmText 确定按钮的文字 (默认 '确定' )
+ * @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' )
+ * @property {Boolean} show 是否显示日历弹窗 (默认 false )
+ * @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false )
+ * @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false )
+ * @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效
+ * @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效
+ * @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
+ * @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
+ * @property {Number|String} monthNum 最多展示的月份数量 (默认 3 )
+ * @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数
+ * @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件
+ * @example <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm">
+ </u-calendar>
+ * */
+ name: 'u-calendar',
+ uHeader,
+ uMonth
+ // 需要显示的月份的数组
+ months: [],
+ // 在月份滚动区域中,当前视图中月份的index索引
+ monthIndex: 0,
+ // 月份滚动区域的高度
+ listHeight: 0,
+ // month组件中选择的日期数组
+ selected: [],
+ scrollIntoView: '',
+ scrollTop:0,
+ // 过滤处理方法
+ innerFormatter: (value) => value
+ this.setMonth()
+ // 打开弹窗时,设置月份数据
+ // 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理
+ innerMaxDate() {
+ return uni.$u.test.number(this.maxDate)
+ ? Number(this.maxDate)
+ : this.maxDate
+ innerMinDate() {
+ return uni.$u.test.number(this.minDate)
+ ? Number(this.minDate)
+ : this.minDate
+ return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
+ subtitle() {
+ // 初始化时,this.months为空数组,所以需要特别判断处理
+ if (this.months.length) {
+ return `${this.months[this.monthIndex].year}年${
+ this.months[this.monthIndex].month
+ }月`
+ return ''
+ buttonDisabled() {
+ // 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
+ if (this.selected.length <= 1) {
+ this.start = Date.now()
+ // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+ setFormatter(e) {
+ this.innerFormatter = e
+ // month组件内部选择日期后,通过事件通知给父组件
+ monthSelected(e) {
+ this.selected = e
+ if (!this.showConfirm) {
+ // 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还
+ this.mode === 'multiple' ||
+ this.mode === 'single' ||
+ (this.mode === 'range' && this.selected.length >= 2)
+ ) {
+ this.$emit('confirm', this.selected)
+ // 校验maxDate,不能小于minDate
+ this.innerMaxDate &&
+ this.innerMinDate &&
+ new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()
+ return uni.$u.error('maxDate不能小于minDate')
+ // 滚动区域的高度
+ this.listHeight = this.rowHeight * 5 + 30
+ // 点击确定按钮
+ confirm() {
+ if (!this.buttonDisabled) {
+ // 获得两个日期之间的月份数
+ getMonths(minDate, maxDate) {
+ const minYear = dayjs(minDate).year()
+ const minMonth = dayjs(minDate).month() + 1
+ const maxYear = dayjs(maxDate).year()
+ const maxMonth = dayjs(maxDate).month() + 1
+ return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1
+ // 设置月份数据
+ setMonth() {
+ // 最小日期的毫秒数
+ const minDate = this.innerMinDate || dayjs().valueOf()
+ // 如果没有指定最大日期,则往后推3个月
+ const maxDate =
+ this.innerMaxDate ||
+ dayjs(minDate)
+ .add(this.monthNum - 1, 'month')
+ .valueOf()
+ // 最大最小月份之间的共有多少个月份,
+ const months = uni.$u.range(
+ 1,
+ this.monthNum,
+ this.getMonths(minDate, maxDate)
+ // 先清空数组
+ this.months = []
+ for (let i = 0; i < months; i++) {
+ this.months.push({
+ date: new Array(
+ dayjs(minDate).add(i, 'month').daysInMonth()
+ .fill(1)
+ .map((item, index) => {
+ // 日期,取值1-31
+ let day = index + 1
+ // 星期,0-6,0为周日
+ const week = dayjs(minDate)
+ .add(i, 'month')
+ .date(day)
+ .day()
+ const date = dayjs(minDate)
+ .format('YYYY-MM-DD')
+ let bottomInfo = ''
+ if (this.showLunar) {
+ // 将日期转为农历格式
+ const lunar = Calendar.solar2lunar(
+ dayjs(date).year(),
+ dayjs(date).month() + 1,
+ dayjs(date).date()
+ bottomInfo = lunar.IDayCn
+ let config = {
+ day,
+ week,
+ // 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态
+ disabled:
+ dayjs(date).isBefore(
+ dayjs(minDate).format('YYYY-MM-DD')
+ ) ||
+ dayjs(date).isAfter(
+ dayjs(maxDate).format('YYYY-MM-DD')
+ ),
+ // 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理
+ date: new Date(date),
+ bottomInfo,
+ dot: false,
+ month:
+ dayjs(minDate).add(i, 'month').month() + 1
+ const formatter =
+ this.formatter || this.innerFormatter
+ return formatter(config)
+ }),
+ // 当前所属的月份
+ month: dayjs(minDate).add(i, 'month').month() + 1,
+ // 当前年份
+ year: dayjs(minDate).add(i, 'month').year()
+ // 滚动到默认设置的月份
+ scrollIntoDefaultMonth(selected) {
+ // 查询默认日期在可选列表的下标
+ const _index = this.months.findIndex(({
+ year,
+ month
+ }) => {
+ month = uni.$u.padZero(month)
+ return `${year}-${month}` === selected
+ if (_index !== -1) {
+ this.scrollIntoView = `month-${_index}`
+ this.scrollTop = this.months[_index].top || 0;
+ // scroll-view滚动监听
+ onScroll(event) {
+ // 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值
+ const scrollTop = Math.max(0, event.detail.scrollTop)
+ // 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引
+ if (scrollTop >= (this.months[i].top || this.listHeight)) {
+ this.monthIndex = i
+ // 更新月份的top值
+ updateMonthTop(topArr = []) {
+ // 设置对应月份的top值,用于onScroll方法更新月份
+ topArr.map((item, index) => {
+ this.months[index].top = item
+ // 获取默认日期的下标
+ const selected = dayjs().format("YYYY-MM")
+ this.scrollIntoDefaultMonth(selected)
+ let selected = dayjs().format("YYYY-MM");
+ selected = dayjs(this.defaultDate).format("YYYY-MM")
+ selected = dayjs(this.defaultDate[0]).format("YYYY-MM");
+.u-calendar {
+ &__confirm {
+ padding: 7px 18px;
+ // 月初是周几
+ const day = dayjs(this.date).date(1).day()
+ const start = day == 0 ? 6 : day - 1
+ // 本月天数
+ const days = dayjs(this.date).endOf('month').format('D')
+ // 上个月天数
+ const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D')
+ // 日期数据
+ // 清空表格
+ this.month = []
+ // 添加上月数据
+ arr.push(
+ ...new Array(start).fill(1).map((e, i) => {
+ const day = prevDays - start + i + 1
+ value: day,
+ disabled: true,
+ date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD')
+ // 添加本月数据
+ ...new Array(days - 0).fill(1).map((e, i) => {
+ const day = i + 1
+ date: dayjs(this.date).date(day).format('YYYY-MM-DD')
+ // 添加下个月
+ ...new Array(42 - days - start).fill(1).map((e, i) => {
+ date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD')
+ // 分割数组
+ for (let n = 0; n < arr.length; n += 7) {
+ this.month.push(
+ arr.slice(n, n + 7).map((e, i) => {
+ e.index = i + n
+ // 自定义信息
+ const custom = this.customList.find((c) => c.date == e.date)
+ // 农历
+ if (this.lunar) {
+ IDayCn,
+ IMonthCn
+ } = this.getLunar(e.date)
+ e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn
+ ...e,
+ ...custom
@@ -0,0 +1,14 @@
+ // 是否打乱键盘按键的顺序
+ random: {
+ // 输入一个中文后,是否自动切换到英文
+ autoChange: {
@@ -0,0 +1,311 @@
+ class="u-keyboard"
+ @touchmove.stop.prevent="noop"
+ v-for="(group, i) in abc ? engKeyBoardList : areaList"
+ :key="i"
+ class="u-keyboard__button"
+ :index="i"
+ :class="[i + 1 === 4 && 'u-keyboard__button--center']"
+ v-if="i === 3"
+ class="u-keyboard__button__inner-wrapper"
+ class="u-keyboard__button__inner-wrapper__left"
+ hover-class="u-hover-class"
+ :hover-stay-time="200"
+ @tap="changeCarInputMode"
+ class="u-keyboard__button__inner-wrapper__left__lang"
+ :class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
+ >中</text>
+ <text class="u-keyboard__button__inner-wrapper__left__line">/</text>
+ :class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
+ >英</text>
+ v-for="(item, j) in group"
+ :key="j"
+ class="u-keyboard__button__inner-wrapper__inner"
+ @tap="carInputClick(i, j)"
+ <text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text>
+ @touchstart="backspaceClick"
+ @touchend="clearTimer"
+ class="u-keyboard__button__inner-wrapper__right"
+ name="backspace"
+ color="#303133"
+ * keyboard 键盘组件
+ * @description 此为uView自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。
+ * @tutorial https://uviewui.com/components/keyboard.html
+ * @property {Boolean} random 是否打乱键盘的顺序
+ * @event {Function} change 点击键盘触发
+ * @event {Function} backspace 点击退格键触发
+ * @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard>
+ name: "u-keyboard",
+ // 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
+ abc: false
+ areaList() {
+ let data = [
+ '京',
+ '沪',
+ '粤',
+ '津',
+ '冀',
+ '豫',
+ '云',
+ '辽',
+ '黑',
+ '湘',
+ '皖',
+ '鲁',
+ '苏',
+ '浙',
+ '赣',
+ '鄂',
+ '桂',
+ '甘',
+ '晋',
+ '陕',
+ '蒙',
+ '吉',
+ '闽',
+ '贵',
+ '渝',
+ '川',
+ '青',
+ '琼',
+ '宁',
+ '挂',
+ '藏',
+ '港',
+ '澳',
+ '新',
+ '使',
+ '学'
+ let tmp = [];
+ // 打乱顺序
+ if (this.random) data = uni.$u.randomArray(data);
+ // 切割成二维数组
+ tmp[0] = data.slice(0, 10);
+ tmp[1] = data.slice(10, 20);
+ tmp[2] = data.slice(20, 30);
+ tmp[3] = data.slice(30, 36);
+ return tmp;
+ engKeyBoardList() {
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 0,
+ 'Q',
+ 'W',
+ 'E',
+ 'R',
+ 'T',
+ 'Y',
+ 'U',
+ 'I',
+ 'O',
+ 'P',
+ 'A',
+ 'S',
+ 'D',
+ 'F',
+ 'G',
+ 'H',
+ 'J',
+ 'K',
+ 'L',
+ 'Z',
+ 'X',
+ 'C',
+ 'V',
+ 'B',
+ 'N',
+ 'M'
+ // 点击键盘按钮
+ carInputClick(i, j) {
+ let value = '';
+ // 不同模式,获取不同数组的值
+ if (this.abc) value = this.engKeyBoardList[i][j];
+ else value = this.areaList[i][j];
+ // 如果允许自动切换,则将中文状态切换为英文
+ if (!this.abc && this.autoChange) uni.$u.sleep(200).then(() => this.abc = true)
+ this.$emit('change', value);
+ // 修改汽车牌键盘的输入模式,中文|英文
+ changeCarInputMode() {
+ this.abc = !this.abc;
+ // 点击退格键
+ backspaceClick() {
+ this.$emit('backspace');
+ clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
+ this.timer = null;
+ this.timer = setInterval(() => {
+ }, 250);
+ clearTimer() {
+ clearInterval(this.timer);
+ $u-car-keyboard-background-color: rgb(224, 228, 230) !default;
+ $u-car-keyboard-padding:6px 0 6px !default;
+ $u-car-keyboard-button-inner-width:64rpx !default;
+ $u-car-keyboard-button-inner-background-color:#FFFFFF !default;
+ $u-car-keyboard-button-height:80rpx !default;
+ $u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;
+ $u-car-keyboard-button-border-radius:4px !default;
+ $u-car-keyboard-button-inner-margin:8rpx 5rpx !default;
+ $u-car-keyboard-button-text-font-size:16px !default;
+ $u-car-keyboard-button-text-color:$u-main-color !default;
+ $u-car-keyboard-center-inner-margin: 0 4rpx !default;
+ $u-car-keyboard-special-button-width:134rpx !default;
+ $u-car-keyboard-lang-font-size:16px !default;
+ $u-car-keyboard-lang-color:$u-main-color !default;
+ $u-car-keyboard-active-color:$u-primary !default;
+ $u-car-keyboard-line-font-size:15px !default;
+ $u-car-keyboard-line-color:$u-main-color !default;
+ $u-car-keyboard-line-margin:0 1px !default;
+ $u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default;
+ .u-keyboard {
+ background-color: $u-car-keyboard-background-color;
+ align-items: stretch;
+ padding: $u-car-keyboard-padding;
+ &__button {
+ &__inner-wrapper {
+ box-shadow: $u-car-keyboard-button-inner-box-shadow;
+ margin: $u-car-keyboard-button-inner-margin;
+ border-radius: $u-car-keyboard-button-border-radius;
+ &__inner {
+ width: $u-car-keyboard-button-inner-width;
+ background-color: $u-car-keyboard-button-inner-background-color;
+ height: $u-car-keyboard-button-height;
+ font-size: $u-car-keyboard-button-text-font-size;
+ color: $u-car-keyboard-button-text-color;
+ &__left,
+ &__right {
+ width: $u-car-keyboard-special-button-width;
+ background-color: $u-car-keyboard-u-hover-class-background-color;
+ &__left {
+ &__line {
+ font-size: $u-car-keyboard-line-font-size;
+ color: $u-car-keyboard-line-color;
+ margin: $u-car-keyboard-line-margin;
+ &__lang {
+ font-size: $u-car-keyboard-lang-font-size;
+ color: $u-car-keyboard-lang-color;
+ color: $u-car-keyboard-active-color;
+ .u-hover-class {
+ // 分组标题
+ default: uni.$u.props.cellGroup.title
+ // 是否显示外边框
+ border: {
+ default: uni.$u.props.cellGroup.border
@@ -0,0 +1,61 @@
+ <view :style="[$u.addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
+ <view v-if="title" class="u-cell-group__title">
+ <slot name="title">
+ <text class="u-cell-group__title__text">{{ title }}</text>
+ <view class="u-cell-group__wrapper">
+ <u-line v-if="border"></u-line>
+ * cellGroup 单元格
+ * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+ * @tutorial https://uviewui.com/components/cell.html
+ * @property {String} title 分组标题
+ * @property {Boolean} border 是否显示外边框 (默认 true )
+ * @event {Function} click 点击cell列表时触发
+ * @example <u-cell-group title="设置喜好">
+ name: 'u-cell-group',
+ $u-cell-group-title-padding: 16px 16px 8px !default;
+ $u-cell-group-title-font-size: 15px !default;
+ $u-cell-group-title-line-height: 16px !default;
+ $u-cell-group-title-color: $u-main-color !default;
+ .u-cell-group {
+ padding: $u-cell-group-title-padding;
+ font-size: $u-cell-group-title-font-size;
+ line-height: $u-cell-group-title-line-height;
+ color: $u-cell-group-title-color;
@@ -0,0 +1,110 @@
+ default: uni.$u.props.cell.title
+ // 标题下方的描述信息
+ default: uni.$u.props.cell.label
+ // 右侧的内容
+ default: uni.$u.props.cell.value
+ // 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+ default: uni.$u.props.cell.icon
+ // 是否禁用cell
+ default: uni.$u.props.cell.disabled
+ // 是否显示下边框
+ default: uni.$u.props.cell.border
+ // 内容是否垂直居中(主要是针对右侧的value部分)
+ default: uni.$u.props.cell.center
+ // 点击后跳转的URL地址
+ url: {
+ default: uni.$u.props.cell.url
+ // 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作
+ linkType: {
+ default: uni.$u.props.cell.linkType
+ // 是否开启点击反馈(表现为点击时加上灰色背景)
+ clickable: {
+ default: uni.$u.props.cell.clickable
+ // 是否展示右侧箭头并开启点击反馈
+ isLink: {
+ default: uni.$u.props.cell.isLink
+ // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
+ required: {
+ default: uni.$u.props.cell.required
+ // 右侧的图标箭头
+ rightIcon: {
+ default: uni.$u.props.cell.rightIcon
+ // 右侧箭头的方向,可选值为:left,up,down
+ arrowDirection: {
+ default: uni.$u.props.cell.arrowDirection
+ // 左侧图标样式
+ type: [Object, String],
+ default: () => {
+ return uni.$u.props.cell.iconStyle
+ // 右侧箭头图标的样式
+ rightIconStyle: {
+ return uni.$u.props.cell.rightIconStyle
+ // 标题的样式
+ titleStyle: {
+ return uni.$u.props.cell.titleStyle
+ // 单位元的大小,可选值为large
+ default: uni.$u.props.cell.size
+ // 点击cell是否阻止事件传播
+ stop: {
+ default: uni.$u.props.cell.stop
+ // 标识符,cell被点击时返回
+ default: uni.$u.props.cell.name