Kaynağa Gözat

制作海报插件

xiaohaizhao 1 yıl önce
ebeveyn
işleme
64a9c68276
24 değiştirilmiş dosya ile 5162 ekleme ve 0 silme
  1. 221 0
      uni_modules/lime-painter/changelog.md
  2. 150 0
      uni_modules/lime-painter/components/common/relation.js
  3. 28 0
      uni_modules/lime-painter/components/l-painter-image/l-painter-image.vue
  4. 27 0
      uni_modules/lime-painter/components/l-painter-qrcode/l-painter-qrcode.vue
  5. 33 0
      uni_modules/lime-painter/components/l-painter-text/l-painter-text.vue
  6. 34 0
      uni_modules/lime-painter/components/l-painter-view/l-painter-view.vue
  7. 461 0
      uni_modules/lime-painter/components/l-painter/l-painter.vue
  8. 191 0
      uni_modules/lime-painter/components/l-painter/nvue.js
  9. 0 0
      uni_modules/lime-painter/components/l-painter/painter.js
  10. 56 0
      uni_modules/lime-painter/components/l-painter/props.js
  11. 0 0
      uni_modules/lime-painter/components/l-painter/single.js
  12. 368 0
      uni_modules/lime-painter/components/l-painter/utils.js
  13. 166 0
      uni_modules/lime-painter/components/lime-painter/lime-painter.vue
  14. 119 0
      uni_modules/lime-painter/hybrid/html/index.html
  15. 0 0
      uni_modules/lime-painter/hybrid/html/painter.js
  16. 0 0
      uni_modules/lime-painter/hybrid/html/uni.webview.1.5.3.js
  17. 92 0
      uni_modules/lime-painter/package.json
  18. 388 0
      uni_modules/lime-painter/parser.js
  19. 963 0
      uni_modules/lime-painter/readme.md
  20. 29 0
      uni_modules/lunc-calendar/changelog.md
  21. 759 0
      uni_modules/lunc-calendar/components/lunc-calendar/calendar.js
  22. 873 0
      uni_modules/lunc-calendar/components/lunc-calendar/lunc-calendar.vue
  23. 78 0
      uni_modules/lunc-calendar/package.json
  24. 126 0
      uni_modules/lunc-calendar/readme.md

+ 221 - 0
uni_modules/lime-painter/changelog.md

@@ -0,0 +1,221 @@
+## 1.9.6.4(2024-03-10)
+- fix: 修复代理ctx导致H5不能使用ctx.save
+## 1.9.6.3(2024-03-08)
+- fix: 修复支付宝真机无法使用的问题
+## 1.9.6.2(2024-02-22)
+- fix: 修复使用render函数报错的问题
+## 1.9.6.1(2023-12-22)
+- fix: 修复字节小程序非2d字体偏移
+- fix: 修复`canvasToTempFilePathSync`会触发两次的问题
+- fix: 修复`parser`图片没有宽度的问题
+## 1.9.6(2023-12-06)
+- fix: 修复背景图受padding影响
+- fix: 修复因字节报错改了代理实现导致微信报错
+- 1.9.5.8(2023-11-16)
+- fix: 修复margin问题
+- fix: 修复borderWidth问题
+- fix: 修复textBox问题
+- fix: 修复字节开发工具报`could not be cloned.`问题
+## 1.9.5.7(2023-07-27)
+- fix: 去掉多余的方法
+- chore: 更新文档,增加自定义字体说明
+## 1.9.5.6(2023-07-21)
+- feat: 有限的支持富文本
+- feat: H5和APP 增加 `hidpi` prop,主要用于大尺寸无法生成图片时用
+- fix: 修复 钉钉小程序 缺少 `measureText` 方法
+- chore: 由于微信小程序 pc 端的 canvas 2d 时不时抽风,故不使用canvas 2d
+## 1.9.5.5(2023-06-27)
+- fix: 修复把`emoji`表情字符拆分成多个字符的情况
+## 1.9.5.4(2023-06-05)
+- fix: 修复因`canvasToTempFilePathSync`监听导致重复调用
+## 1.9.5.3(2023-05-23)
+- fix: 因isPc错写成了isPC导致小程序PC不能生成图片
+## 1.9.5.2(2023-05-22)
+- feat: 删除多余文件
+## 1.9.5.1(2023-05-22)
+- fix: 修复 文字行数与`line-clamp`相同但不满一行时也加了省略号的问题
+## 1.9.5(2023-05-14)
+- feat: 增加 `text-indent` 和 `calc` 方法
+- feat: 优化 布局时间
+## 1.9.4.4(2023-04-15)
+- fix: 修复无法匹配负值
+- fix: 修复 Nvue IOS getImageInfo `useCORS` 为 undefined
+## 1.9.4.3(2023-04-01)
+- feat: 增加支持文字描边 `text-stroke: '5rpx #fff'`
+## 1.9.4.2(2023-03-30)
+- fix: 修复 支付宝小程序 isPC 在手机也为true的问题
+- feat: 由 微信开发工具 3060 版 无法获取图片尺寸,现 微信开发工具 3220 版 修复该问题,故还原上一版的获取图片方式。
+## 1.9.4.1(2023-03-28)
+- fix: 修复固定高度不正确问题
+## 1.9.4(2023-03-17)
+- fix: nvue ios getImageInfo缺少this报错
+- fix: pathType 非2d无效问题
+- fix: 修复 小米9se 可能会存在多次init 导致画面多次放大
+- fix: 修复 border 分开写 width style无效问题
+- fix: 修复 支付宝小程序IOS 再次进入不渲染的问题
+- fix: 修复 支付宝小程序安卓Zindex排序错乱问题
+- fix: 修复 微信开发工具 3060 版 无法获取图片的问题
+- feat: 把 for in 改为 forEach
+- feat: 增加 hidden
+- feat: 根节点 box-sizing 默认 `border-box`
+- feat: 增加支持 `vw` `wh`
+- chore: pathType 取消 默认值,因为字节开发工具不能显示
+- chore: 支付宝小程序开发工具不支持 生成图片 请以真机调试为准
+- bug: 企业微信 2.20.3无法使用
+## 1.9.3.5(2022-06-29)
+- feat: justifyContent 增加 `space-around`、`space-between`
+- feat: canvas 2d 也使用`getImageInfo`
+- fix: 修复 `text`的 `text-decoration`错位
+## 1.9.3.4(2022-06-20)
+- fix: 修复 因创建节点速度问题导致顺序出错。 
+- fix: 修复 微信小程序 PC 无法显示本地图片 
+- fix: 修复 flex-box 对齐问题 
+- feat: 增加 `text-shadow`
+- feat: 重写 `text` 对齐方式
+- chore: 更新文档
+## 1.9.3.3(2022-06-17)
+- fix: 修复 支付宝小程序 canvas 2d 存在ctx.draw问题导致报错
+- fix: 修复 支付宝小程序 toDataURL 存在权限问题改用 `toTempFilePath`
+- fix: 修复 支付宝小程序 image size 问题导致 `objectFit` 无效
+## 1.9.3.2(2022-06-14)
+- fix: 修复 image 设置背景色不生效问题
+- fix: 修复 nvue 环境判断缺少参数问题
+## 1.9.3.1(2022-06-14)
+- fix: 修复 bottom 定位不对问题
+- fix: 修复 因小数导致计算出错换行问题
+- feat: 增加 `useCORS` h5端图片跨域 在设置请求头无效果后试一下设置这个值
+- chore: 更新文档
+## 1.9.3(2022-06-13)
+- feat: 增加 `zIndex`
+- feat: 增加 `flex-box` 该功能处于原始阶段,非常简陋。
+- tips: QQ小程序 vue3 不支持, 为 uni 官方BUG
+## 1.9.2.9(2022-06-10)
+- fix: 修复`text-align`及`margin`居中问题
+## 1.9.2.8(2022-06-10)
+- fix: 修复 Nvue `canvasToTempFilePathSync` 不生效问题
+## 1.9.2.7(2022-06-10)
+- fix: 修复 margin及padding的bug
+- fix: 修复 Nvue `isCanvasToTempFilePath` 不生效问题
+## 1.9.2.6(2022-06-09)
+- fix: 修复 Nvue 不显示
+- feat: 增加支持字体渐变
+```html
+<l-painter-text 
+	text="水调歌头\n明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。"
+	css="background: linear-gradient(,#ff971b 0%, #1989fa 100%); background-clip: text" />
+```
+## 1.9.2.5(2022-06-09)
+- chore: 更变获取父级宽度的设定
+- chore: `pathType` 在canvas 2d 默认为 `url`
+## 1.9.2.4(2022-06-08)
+- fix: 修复 `pathType` 不生效问题
+## 1.9.2.3(2022-06-08)
+- fix: 修复 `canvasToTempFilePath` 漏写 `success` 参数
+## 1.9.2.2(2022-06-07)
+- chore: 更新文档
+## 1.9.2.1(2022-06-07)
+- fix: 修复 vue3 赋值给this再传入导致image无法绘制
+- fix: 修复 `canvasToTempFilePathSync` 时机问题
+- feat: canvas 2d 更改图片生成方式 `toDataURL` 
+## 1.9.2(2022-05-30)
+- fix: 修复 `canvasToTempFilePathSync` 在 vue3 下只生成一次
+## 1.9.1.7(2022-05-28)
+- fix: 修复 `qrcode`显示不全问题
+## 1.9.1.6(2022-05-28)
+- fix: 修复 `canvasToTempFilePathSync` 会重复多次问题
+- fix: 修复 `view` css `backgroundImage` 图片下载失败导致 子节点不渲染
+## 1.9.1.5(2022-05-27)
+- fix: 修正支付宝小程序 canvas 2d版本号 2.7.15
+## 1.9.1.4(2022-05-22)
+- fix: 修复字节小程序无法使用xml方式
+- fix: 修复字节小程序无法使用base64(非2D情况下工具上无法显示)
+- fix: 修复支付宝小程序 `canvasToTempFilePath` 报错
+## 1.9.1.3(2022-04-29)
+- fix: 修复vue3打包后uni对象为空后的报错
+## 1.9.1.2(2022-04-25)
+- fix: 删除多余文件
+## 1.9.1.1(2022-04-25)
+- fix: 修复图片不显示问题
+## 1.9.1(2022-04-12)
+- fix: 因四舍五入导致有些机型错位
+- fix: 修复无views报错 
+- chore: nvue下因ios无法读取插件内static文件,改由下载方式
+## 1.9.0(2022-03-20)
+- fix: 因无法固定尺寸导致生成图片不全
+- fix: 特定情况下text判断无效
+- chore: 本地化APP Nvue webview
+## 1.8.9(2022-02-20)
+- fix: 修复 小程序下载最多10次并发的问题
+- fix: 修复 APP端无法获取本地图片
+- fix: 修复 APP Nvue端不执行问题
+- chore: 增加图片缓存机制
+## 1.8.8.8(2022-01-27)
+- fix: 修复 主动调用尺寸问题
+## 1.8.8.6(2022-01-26)
+- fix: 修复 nvue 下无宽度时获取父级宽度 
+- fix: 修复 ios app 无法渲染问题
+## 1.8.8(2022-01-23)
+- fix: 修复 主动调用时无节点问题
+- fix: 修复 `box-shadow` 颜色问题
+- fix: 修复 `transform:rotate` 角度位置问题
+- feat: 增加 `overflow:hidden`
+## 1.8.7(2022-01-07)
+- fix: 修复 image 方向为 `right` 时原始宽高问题
+- feat: 支持 view 设置背景图 `background-image: url(xxx)`
+- chore: 去掉可选链
+## 1.8.6(2021-11-28)
+- feat: 支持`view`对`inline-block`的子集使用`text-align`
+## 1.8.5.5(2021-08-17)
+- chore: 更新文档,删除 replace
+- fix: 修复 text 值为 number时报错
+## 1.8.5.4(2021-08-16)
+- fix: 字节小程序兼容
+## 1.8.5.3(2021-08-15)
+- fix: 修复线性渐变与css现实效果不一致的问题
+- chore: 更新文档
+## 1.8.5.2(2021-08-13)
+- chore: 增加`background-image`、`background-repeat` 能力,主要用于背景纹理的绘制,并不是代替`image`。例如:大面积的重复平铺的水印
+- 注意:这个功能H5暂时无法使用,因为[官方的API有BUG](https://ask.dcloud.net.cn/question/128793),待官方修复!!!
+## 1.8.5.1(2021-08-10)
+- fix: 修复因`margin`报错问题
+## 1.8.5(2021-08-09)
+- chore: 增加margin支持`auto`,以达到居中效果
+## 1.8.4(2021-08-06)
+- chore: 增加判断缓存文件条件
+- fix: 修复css 多余空格报错问题
+## 1.8.3(2021-08-04)
+- tips: 1.6.x 以下的版本升级到1.8.x后要为每个元素都加上定位:position: 'absolute'
+- fix: 修复只有一个view子元素时不计算高度的问题
+## 1.8.2(2021-08-03)
+- fix: 修复 path-type 为 `url` 无效问题
+- fix: 修复 qrcode `text` 为空时报错问题
+- fix: 修复 image `src` 动态设置时不生效问题
+- feat: 增加 css 属性 `min-width` `max-width`
+## 1.8.1(2021-08-02)
+- fix: 修复无法加载本地图片
+## 1.8.0(2021-08-02)
+- chore 文档更新
+- 使用旧版的同学不要升级!
+## 1.8.0-beta(2021-07-30)
+- ## 全新布局方式 不兼容旧版!
+- chore: 布局方式变更
+- tips: 微信canvas 2d 不支持真机调试
+## 1.6.6(2021-07-09)
+- chore: 统一命名规范,无须主动引入组件
+## 1.6.5(2021-06-08)
+- chore: 去掉console
+## 1.6.4(2021-06-07)
+- fix: 修复 数字 为纯字符串时不转换的BUG
+## 1.6.3(2021-06-06)
+- fix: 修复 PC 端放大的BUG
+## 1.6.2(2021-05-31)
+- fix: 修复 报`adaptor is not a function`错误
+- fix: 修复 text 多行高度
+- fix: 优化 默认文字的基准线
+- feat: `@progress`事件,监听绘制进度
+## 1.6.1(2021-02-28)
+- 删除多余节点
+## 1.6.0(2021-02-26)
+- 调整为uni_modules目录规范
+- 修复:transform的rotate不能为负数问题
+- 新增:`pathType` 指定生成图片返回的路径类型,可选值有 `base64`、`url`

+ 150 - 0
uni_modules/lime-painter/components/common/relation.js

@@ -0,0 +1,150 @@
+const styles = (v ='') =>  v.split(';').filter(v => v && !/^[\n\s]+$/.test(v)).map(v => {
+						const key = v.slice(0, v.indexOf(':'))
+						const value = v.slice(v.indexOf(':')+1)
+						return {
+							[key
+								.replace(/-([a-z])/g, function() { return arguments[1].toUpperCase()})
+								.replace(/\s+/g, '')
+							]: value.replace(/^\s+/, '').replace(/\s+$/, '') || ''
+						}
+					})
+export function parent(parent) {
+	return {
+		provide() {
+			return {
+				[parent]: this
+			}
+		},
+		data() {
+			return {
+				el: {
+					id: null,
+					css: {},
+					views: []
+				},
+			}
+		},
+		watch: {
+			css: { 
+				handler(v) {
+					if(this.canvasId) {
+						this.el.css = (typeof v == 'object' ? v : v && Object.assign(...styles(v))) || {}
+						this.canvasWidth = this.el.css && this.el.css.width || this.canvasWidth
+						this.canvasHeight = this.el.css && this.el.css.height || this.canvasHeight
+					}
+				},
+				immediate: true
+			}
+		}
+	}
+}
+export function children(parent, options = {}) {
+	const indexKey = options.indexKey || 'index'
+	return {
+		inject: {
+			[parent]: {
+				default: null
+			}
+		},
+		watch: {
+			el: {
+				handler(v, o) {
+					if(JSON.stringify(v) != JSON.stringify(o))
+						this.bindRelation()
+				},
+				deep: true,
+				immediate: true
+			},
+			src: {
+				handler(v, o) {
+					if(v != o)
+						this.bindRelation()
+				},
+				immediate: true
+			},
+			text: {
+				handler(v, o) {
+					if(v != o) this.bindRelation()
+				},
+				immediate: true
+			},
+			css: {
+				handler(v, o) {
+					if(v != o)
+						this.el.css = (typeof v == 'object' ? v : v && Object.assign(...styles(v))) || {}
+				},
+				immediate: true
+			},
+			replace: {
+				handler(v, o) {
+					if(JSON.stringify(v) != JSON.stringify(o))
+						this.bindRelation()
+				},
+				deep: true,
+				immediate: true
+			}
+		},
+		created() {
+			if(!this._uid) {
+				this._uid = this._.uid
+			}
+			Object.defineProperty(this, 'parent', {
+				get: () => this[parent] || [],
+			})
+			Object.defineProperty(this, 'index', {
+				get: () =>  {
+					this.bindRelation();
+					const {parent: {el: {views=[]}={}}={}} = this
+					return views.indexOf(this.el)
+				},
+			});
+			this.el.type = this.type
+			if(this.uid) {
+				this.el.uid = this.uid
+			}
+			this.bindRelation()
+		},
+		// #ifdef VUE3
+		beforeUnmount() {
+			this.removeEl()
+		},
+		// #endif
+		// #ifdef VUE2
+		beforeDestroy() {
+			this.removeEl()
+		},
+		// #endif
+		methods: {
+			removeEl() {
+				if (this.parent) {
+					this.parent.el.views = this.parent.el.views.filter(
+						(item) => item._uid !== this._uid
+					);
+				}
+			},
+			bindRelation() {
+				if(!this.el._uid) {
+					this.el._uid = this._uid 
+				}
+				if(['text','qrcode'].includes(this.type)) {
+					this.el.text = this.$slots && this.$slots.default && this.$slots.default[0].text || `${this.text || ''}`.replace(/\\n/g, '\n')
+				}
+				if(this.type == 'image') {
+					this.el.src = this.src
+				}
+				if (!this.parent) {
+					return;
+				}
+				let views = this.parent.el.views || [];
+				if(views.indexOf(this.el) !== -1) {
+					this.parent.el.views = views.map(v => v._uid == this._uid ? this.el : v)
+				} else {
+					this.parent.el.views = [...views, this.el];
+				}
+			}
+		},
+		mounted() {
+			// this.bindRelation()
+		},
+	}
+}

+ 28 - 0
uni_modules/lime-painter/components/l-painter-image/l-painter-image.vue

@@ -0,0 +1,28 @@
+<template>
+	
+</template>
+
+<script>
+	import {parent, children} from '../common/relation';
+	export default {
+		name: 'lime-painter-image',
+		mixins:[children('painter')],
+		props: {
+			id: String,
+			css: [String, Object],
+			src: String
+		},
+		data() {
+			return {
+				type: 'image',
+				el: {
+					css: {},
+					src: null
+				},
+			}
+		}
+	}
+</script>
+
+<style>
+</style>

+ 27 - 0
uni_modules/lime-painter/components/l-painter-qrcode/l-painter-qrcode.vue

@@ -0,0 +1,27 @@
+<template>
+</template>
+
+<script>
+	import {parent, children} from '../common/relation';
+	export default {
+		name: 'lime-painter-qrcode',
+		mixins:[children('painter')],
+		props: {
+			id: String,
+			css: [String, Object],
+			text: String
+		},
+		data() {
+			return {
+				type: 'qrcode',
+				el: {
+					css: {},
+					text: null
+				},
+			}
+		}
+	}
+</script>
+
+<style>
+</style>

+ 33 - 0
uni_modules/lime-painter/components/l-painter-text/l-painter-text.vue

@@ -0,0 +1,33 @@
+<template>
+	<text style="opacity: 0;height: 0;"><slot/></text>
+</template>
+
+<script>
+	import {parent, children} from '../common/relation';
+	export default {
+		name: 'lime-painter-text',
+		mixins:[children('painter')],
+		props: {
+			type: {
+				type: String,
+				default: 'text'
+			},
+			uid: String,
+			css: [String, Object],
+			text: [String, Number],
+			replace: Object,
+		},
+		data() {
+			return {
+				// type: 'text',
+				el: {
+					css: {},
+					text: null
+				},
+			}
+		}
+	}
+</script>
+
+<style>
+</style>

+ 34 - 0
uni_modules/lime-painter/components/l-painter-view/l-painter-view.vue

@@ -0,0 +1,34 @@
+<template>
+	<view><slot/></view>
+</template>
+
+<script>
+	import {parent, children} from '../common/relation';
+	export default {
+		name: 'lime-painter-view',
+		mixins:[children('painter'), parent('painter')],
+		props: {
+			id: String,
+			type: {
+				type: String,
+				default: 'view'
+			},
+			css: [String, Object],
+		},
+		data() {
+			return {
+				// type: 'view',
+				el: {
+					css: {},
+					views:[]
+				},
+			}
+		},
+		mounted() {
+			
+		}
+	}
+</script>
+
+<style>
+</style>

+ 461 - 0
uni_modules/lime-painter/components/l-painter/l-painter.vue

@@ -0,0 +1,461 @@
+<template>
+	<view class="lime-painter" ref="limepainter">
+		<view v-if="canvasId && size" :style="styles">
+			<!-- #ifndef APP-NVUE -->
+			<canvas class="lime-painter__canvas" v-if="use2dCanvas" :id="canvasId" type="2d" :style="size"></canvas>
+			<canvas class="lime-painter__canvas" v-else :id="canvasId" :canvas-id="canvasId" :style="size"
+				:width="boardWidth * dpr" :height="boardHeight * dpr" :hidpi="hidpi"></canvas>
+
+			<!-- #endif -->
+			<!-- #ifdef APP-NVUE -->
+			<web-view :style="size" ref="webview"
+				src="/uni_modules/lime-painter/hybrid/html/index.html"
+				class="lime-painter__canvas" @pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage">
+			</web-view>
+			<!-- #endif -->
+		</view>
+		<slot />
+	</view>
+</template>
+
+<script>
+	import { parent } from '../common/relation'
+	import props from './props'
+	import {toPx, base64ToPath, pathToBase64, isBase64, sleep, getImageInfo }from './utils';
+	//  #ifndef APP-NVUE
+	import { canIUseCanvas2d, isPC} from './utils';
+	import Painter from './painter';
+	// import Painter from '@painter'
+	const nvue = {}
+	//  #endif
+	//  #ifdef APP-NVUE
+	import nvue from './nvue'
+	//  #endif
+	export default {
+		name: 'lime-painter',
+		mixins: [props, parent('painter'), nvue],
+		data() {
+			return {
+				use2dCanvas: false,
+				canvasHeight: 150,
+				canvasWidth: null,
+				parentWidth: 0,
+				inited: false,
+				progress: 0,
+				firstRender: 0,
+				done: false,
+				tasks: []
+			};
+		},
+		computed: {
+			styles() {
+				return `${this.size}${this.customStyle||''};` + (this.hidden && 'position: fixed; left: 1500rpx;')
+			},
+			canvasId() {
+				return `l-painter${this._ && this._.uid || this._uid}`
+			},
+			size() {
+				if (this.boardWidth && this.boardHeight) {
+					return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;
+				}
+			},
+			dpr() {
+				return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
+			},
+			boardWidth() {
+				const {width = 0} = (this.elements && this.elements.css) || this.elements || this
+				const w = toPx(width||this.width)
+				return w || Math.max(w, toPx(this.canvasWidth));
+			},
+			boardHeight() {
+				const {height = 0} = (this.elements && this.elements.css) || this.elements || this
+				const h = toPx(height||this.height)
+				return h || Math.max(h, toPx(this.canvasHeight));
+			},
+			hasBoard() {
+				return this.board && Object.keys(this.board).length
+			},
+			elements() {
+				return this.hasBoard ? this.board : JSON.parse(JSON.stringify(this.el))
+			}
+		},
+		created() {
+			this.use2dCanvas = this.type === '2d' && canIUseCanvas2d() && !isPC
+		},
+		async mounted() {
+			await sleep(30)
+			await this.getParentWeith()
+			this.$nextTick(() => {
+				setTimeout(() => {
+					this.$watch('elements', this.watchRender, {
+						deep: true,
+						immediate: true
+					});
+				}, 30)
+			})
+		},
+		// #ifdef VUE3
+		unmounted() {
+			this.done = false
+			this.inited = false
+			this.firstRender = 0
+			this.progress = 0
+			this.painter = null
+			clearTimeout(this.rendertimer)
+		},
+		// #endif
+		// #ifdef VUE2
+		destroyed() {
+			this.done = false
+			this.inited = false
+			this.firstRender = 0
+			this.progress = 0
+			this.painter = null
+			clearTimeout(this.rendertimer)
+		},
+		// #endif
+		methods: {
+			async watchRender(val, old) {
+				if (!val || !val.views || (!this.firstRender ? !val.views.length : !this.firstRender) || !Object.keys(val).length || JSON.stringify(val) == JSON.stringify(old)) return;
+				this.firstRender = 1
+				this.progress = 0
+				this.done = false
+				clearTimeout(this.rendertimer)
+				this.rendertimer = setTimeout(() => {
+					this.render(val);
+				}, this.beforeDelay)
+			},
+			async setFilePath(path, param) {
+				let filePath = path
+				const {pathType = this.pathType} =  param || this
+				if (pathType == 'base64' && !isBase64(path)) {
+					filePath = await pathToBase64(path)
+				} else if (pathType == 'url' && isBase64(path)) {
+					filePath = await base64ToPath(path)
+				}
+				if (param && param.isEmit) {
+					this.$emit('success', filePath);
+				}
+				return filePath
+			},
+			async getSize(args) {
+				const {width} = args.css || args
+				const {height} = args.css || args
+				if (!this.size) {
+					if (width || height) {
+						this.canvasWidth = width || this.canvasWidth
+						this.canvasHeight = height || this.canvasHeight
+						await sleep(30);
+					} else {
+						await this.getParentWeith()
+					}
+				}
+			},
+			canvasToTempFilePathSync(args) {
+				// this.stopWatch && this.stopWatch()
+				// this.stopWatch = this.$watch('done', (v) => {
+				// 	if (v) {
+				// 		this.canvasToTempFilePath(args)
+				// 		this.stopWatch && this.stopWatch()
+				// 	}
+				// }, {
+				// 	immediate: true
+				// })
+				this.tasks.push(args)
+				if(this.done){
+					this.runTask()
+				}
+			},
+			runTask(){
+				while(this.tasks.length){
+					const task = this.tasks.shift()	
+					 this.canvasToTempFilePath(task)
+				}
+			},
+			// #ifndef APP-NVUE
+			getParentWeith() {
+				return new Promise(resolve => {
+					uni.createSelectorQuery()
+						.in(this)
+						.select(`.lime-painter`)
+						.boundingClientRect()
+						.exec(res => {
+							const {width, height} = res[0]||{}
+							this.parentWidth = Math.ceil(width||0)
+							this.canvasWidth = this.parentWidth || 300
+							this.canvasHeight = height || this.canvasHeight||150
+							resolve(res[0])
+						})
+				})
+			},
+			async render(args = {}) {
+				if(!Object.keys(args).length) {
+					return console.error('空对象')
+				}
+				this.progress = 0
+				this.done = false
+				// #ifdef APP-NVUE
+				this.tempFilePath.length = 0
+				// #endif
+				await this.getSize(args)
+				const ctx = await this.getContext();
+				
+				let {
+					use2dCanvas,
+					boardWidth,
+					boardHeight,
+					canvas,
+					afterDelay
+				} = this;
+				if (use2dCanvas && !canvas) {
+					return Promise.reject(new Error('canvas 没创建'));
+				}
+				this.boundary = {
+					top: 0,
+					left: 0,
+					width: boardWidth,
+					height: boardHeight
+				};
+				this.painter = null
+				if (!this.painter) {
+					const {width} = args.css || args
+					const {height} = args.css || args
+					if(!width && this.parentWidth) {
+						Object.assign(args, {width: this.parentWidth})
+					}
+					const param = {
+						context: ctx,
+						canvas,
+						width: boardWidth,
+						height: boardHeight,
+						pixelRatio: this.dpr,
+						useCORS: this.useCORS,
+						createImage: getImageInfo.bind(this),
+						performance: this.performance,
+						listen: {
+							onProgress: (v) => {
+								this.progress = v
+								this.$emit('progress', v)
+							},
+							onEffectFail: (err) => {
+								this.$emit('faill', err)
+							}
+						}
+					}
+					this.painter = new Painter(param)
+				} 
+				try{
+					// vue3 赋值给data会引起图片无法绘制
+					const { width, height } = await this.painter.source(JSON.parse(JSON.stringify(args)))
+					this.boundary.height = this.canvasHeight = height
+					this.boundary.width = this.canvasWidth = width
+					await sleep(this.sleep);
+					await this.painter.render()
+					await new Promise(resolve => this.$nextTick(resolve));
+					if (!use2dCanvas) {
+						await this.canvasDraw();
+					}
+					if (afterDelay && use2dCanvas) {
+						await sleep(afterDelay);
+					}
+					this.$emit('done');
+					this.done = true
+					if (this.isCanvasToTempFilePath) {
+						this.canvasToTempFilePath()
+							.then(res => {
+								this.$emit('success', res.tempFilePath)
+							})
+							.catch(err => {
+								this.$emit('fail', new Error(JSON.stringify(err)));
+							});
+					}
+					this.runTask()
+					return Promise.resolve({
+						ctx,
+						draw: this.painter,
+						node: this.node
+					});
+				}catch(e){
+					//TODO handle the exception
+				}
+				
+			},
+			canvasDraw(flag = false) {
+				return new Promise((resolve, reject) => this.ctx.draw(flag, () => setTimeout(() => resolve(), this
+					.afterDelay)));
+			},
+			async getContext() {
+				if (!this.canvasWidth) {
+					this.$emit('fail', 'painter no size')
+					console.error('[lime-painter]: 给画板或父级设置尺寸')
+					return Promise.reject();
+				}
+				if (this.ctx && this.inited) {
+					return Promise.resolve(this.ctx);
+				}
+				const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
+				const _getContext = () => {
+					return new Promise(resolve => {
+						uni.createSelectorQuery()
+							.in(this)
+							.select(`#${this.canvasId}`)
+							.boundingClientRect()
+							.exec(res => {
+								if (res) {
+									const ctx = uni.createCanvasContext(this.canvasId, this);
+									if (!this.inited) {
+										this.inited = true;
+										this.use2dCanvas = false;
+										this.canvas = res;
+									}
+									
+									// 钉钉小程序框架不支持 measureText 方法,用此方法 mock
+									if (!ctx.measureText) {
+										function strLen(str) {
+											let len = 0;
+											for (let i = 0; i < str.length; i++) {
+												if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
+													len++;
+												} else {
+													len += 2;
+												}
+											}
+											return len;
+										}
+										ctx.measureText = text => {
+											let fontSize = ctx.state && ctx.state.fontSize || 12;
+											const font = ctx.__font
+											if (font && fontSize == 12) {
+												fontSize = parseInt(font.split(' ')[3], 10);
+											}
+											fontSize /= 2;
+											return {
+												width: strLen(text) * fontSize
+											};
+										}
+									}
+									
+									// #ifdef MP-ALIPAY
+									ctx.scale(dpr, dpr);
+									// #endif
+									this.ctx = ctx
+									resolve(this.ctx);
+								} else {
+									console.error('[lime-painter] no node')
+								}
+							});
+					});
+				};
+				if (!use2dCanvas) {
+					return _getContext();
+				}
+				return new Promise(resolve => {
+					uni.createSelectorQuery()
+						.in(this)
+						.select(`#${this.canvasId}`)
+						.node()
+						.exec(res => {
+							let {node: canvas} = res && res[0]||{};
+							if(canvas) {
+								const ctx = canvas.getContext(type);
+								if (!this.inited) {
+									this.inited = true;
+									this.use2dCanvas = true;
+									this.canvas = canvas;
+								}
+								this.ctx = ctx
+								resolve(this.ctx);
+							} else {
+								console.error('[lime-painter]: no size')
+							}
+						});
+				});
+			},
+			canvasToTempFilePath(args = {}) {
+				return new Promise(async (resolve, reject) => {
+					const { use2dCanvas, canvasId, dpr, fileType, quality } = this;
+					const success = async (res) => {
+						try {
+							const tempFilePath = await this.setFilePath(res.tempFilePath || res, args)
+							const result = Object.assign(res, {tempFilePath})
+							args.success && args.success(result)
+							resolve(result)
+						} catch (e) {
+							this.$emit('fail', e)
+						}
+					}
+					
+					let { top: y = 0, left: x = 0, width, height } = this.boundary || this;
+					// let destWidth = width * dpr;
+					// let destHeight = height * dpr;
+					// #ifdef MP-ALIPAY
+					// width = destWidth;
+					// height = destHeight;
+					// #endif
+					
+					const copyArgs = Object.assign({
+						// x,
+						// y,
+						// width,
+						// height,
+						// destWidth,
+						// destHeight,
+						canvasId,
+						id: canvasId,
+						fileType,
+						quality,
+					}, args, {success});
+					// if(this.isPC || use2dCanvas) {
+					// 	copyArgs.canvas = this.canvas
+					// }
+					if (use2dCanvas) {
+						copyArgs.canvas = this.canvas
+						try{
+							// #ifndef MP-ALIPAY
+							const oFilePath = this.canvas.toDataURL(`image/${args.fileType||fileType}`.replace(/pg/, 'peg'), args.quality||quality)
+							if(/data:,/.test(oFilePath)) {
+								uni.canvasToTempFilePath(copyArgs, this);
+							} else {
+								const tempFilePath = await this.setFilePath(oFilePath, args)
+								args.success && args.success({tempFilePath})
+								resolve({tempFilePath})
+							}
+							// #endif
+							// #ifdef MP-ALIPAY
+							this.canvas.toTempFilePath(copyArgs)
+							// #endif
+						}catch(e){
+							args.fail && args.fail(e)
+							reject(e)
+						}
+					} else {
+						// #ifdef MP-ALIPAY
+						if(this.ctx.toTempFilePath) {
+							// 钉钉
+							const ctx = uni.createCanvasContext(canvasId);
+							ctx.toTempFilePath(copyArgs);
+						} else {
+							my.canvasToTempFilePath(copyArgs);
+						}
+						// #endif
+						// #ifndef MP-ALIPAY
+						uni.canvasToTempFilePath(copyArgs, this);
+						// #endif
+					}
+				})
+			}
+			// #endif
+		}
+	};
+</script>
+<style>
+	.lime-painter,
+	.lime-painter__canvas {
+		// #ifndef APP-NVUE
+		width: 100%;
+		// #endif
+		// #ifdef APP-NVUE
+		flex: 1;
+		// #endif
+	}
+</style>

+ 191 - 0
uni_modules/lime-painter/components/l-painter/nvue.js

@@ -0,0 +1,191 @@
+// #ifdef APP-NVUE
+import { sleep, getImageInfo, isBase64, useNvue, networkReg } from './utils';
+const dom = weex.requireModule('dom')
+import { version } from '../../package.json'
+
+export default {
+	data() {
+		return {
+			tempFilePath: [],
+			isInitFile: false,
+			osName: uni.getSystemInfoSync().osName
+		}
+	},
+	methods: {
+		getParentWeith() {
+			return new Promise(resolve => {
+				dom.getComponentRect(this.$refs.limepainter, (res) => {
+					this.parentWidth = Math.ceil(res.size.width)
+					this.canvasWidth = this.canvasWidth || this.parentWidth ||300
+					this.canvasHeight = res.size.height || this.canvasHeight||150
+					resolve(res.size)
+				})
+			})
+		},
+		onPageFinish() {
+			this.webview = this.$refs.webview
+			this.webview.evalJS(`init(${this.dpr})`)
+		},
+		onMessage(e) {
+			const res = e.detail.data[0] || null;
+			if (res.event) {
+				if (res.event == 'inited') {
+					this.inited = true
+				}
+				if(res.event == 'fail'){
+					this.$emit('fail', res)
+				}
+				if (res.event == 'layoutChange') {
+					const data = typeof res.data == 'string' ? JSON.parse(res.data) : res.data
+					this.canvasWidth = Math.ceil(data.width);
+					this.canvasHeight = Math.ceil(data.height);
+				}
+				if (res.event == 'progressChange') {
+					this.progress = res.data * 1
+				}
+				if (res.event == 'file') {
+					this.tempFilePath.push(res.data)
+					if (this.tempFilePath.length > 7) {
+						this.tempFilePath.shift()
+					}
+					return
+				}
+				if (res.event == 'success') {
+					if (res.data) {
+						this.tempFilePath.push(res.data)
+						if (this.tempFilePath.length > 8) {
+							this.tempFilePath.shift()
+						}
+						if (this.isCanvasToTempFilePath) {
+							this.setFilePath(this.tempFilePath.join(''), {isEmit:true})
+						}
+					} else {
+						this.$emit('fail', 'canvas no data')
+					}
+					return
+				}
+				this.$emit(res.event, JSON.parse(res.data));
+			} else if (res.file) {
+				this.file = res.data;
+			} else{
+				console.info(res[0])
+			}
+		},
+		getWebViewInited() {
+			if (this.inited) return Promise.resolve(this.inited);
+			return new Promise((resolve) => {
+				this.$watch(
+					'inited',
+					async val => {
+						if (val) {
+							resolve(val)
+						}
+					}, {
+						immediate: true
+					}
+				);
+			})
+		},
+		getTempFilePath() {
+			if (this.tempFilePath.length == 8) return Promise.resolve(this.tempFilePath)
+			return new Promise((resolve) => {
+				this.$watch(
+					'tempFilePath',
+					async val => {
+						if (val.length == 8) {
+							resolve(val.join(''))
+						}
+					}
+				);
+			})
+		},
+		getWebViewDone() {
+			if (this.progress == 1) return Promise.resolve(this.progress);
+			return new Promise((resolve) => {
+				this.$watch(
+					'progress',
+					async val => {
+						if (val == 1) {
+							this.$emit('done')
+							this.done = true
+							this.runTask()
+							resolve(val)
+						}
+					}, {
+						immediate: true
+					}
+				);
+			})
+		},
+		async render(args) {
+			try {
+				await this.getSize(args)
+				const {width} = args.css || args
+				if(!width && this.parentWidth) {
+					Object.assign(args, {width: this.parentWidth})
+				}
+				const newNode = await this.calcImage(args);
+				await this.getWebViewInited()
+				this.webview.evalJS(`source(${JSON.stringify(newNode)})`)
+				await this.getWebViewDone()
+				await sleep(this.afterDelay)
+				if (this.isCanvasToTempFilePath) {
+					const params = {
+						fileType: this.fileType,
+						quality: this.quality
+					}
+					this.webview.evalJS(`save(${JSON.stringify(params)})`)
+				}
+				return Promise.resolve()
+			} catch (e) {
+				this.$emit('fail', e)
+			}
+		},
+		async calcImage(args) {
+			let node = JSON.parse(JSON.stringify(args))
+			const urlReg = /url\((.+)\)/
+			const {backgroundImage} = node.css||{}
+			const isBG = backgroundImage && urlReg.exec(backgroundImage)[1]
+			const url = node.url || node.src || isBG
+			if(['text', 'qrcode'].includes(node.type)) {
+				return node
+			}
+			if ((node.type === "image" || isBG) && url && !isBase64(url) && (this.osName == 'ios' || !networkReg.test(url))) {
+				let {path} = await getImageInfo(url, true)
+				if (isBG) {
+					node.css.backgroundImage = `url(${path})`
+				} else {
+					node.src = path
+				}
+			} else if (node.views && node.views.length) {
+				for (let i = 0; i < node.views.length; i++) {
+					node.views[i] = await this.calcImage(node.views[i])
+				}
+			}
+			return node
+		},
+		async canvasToTempFilePath(args = {}) {
+			if (!this.inited) {
+				return this.$emit('fail', 'no init')
+			}
+			this.tempFilePath = []
+			if (args.fileType == 'jpg') {
+				args.fileType = 'jpeg'
+			}
+			this.webview.evalJS(`save(${JSON.stringify(args)})`)
+			try {
+				let tempFilePath = await this.getTempFilePath()
+				tempFilePath = await this.setFilePath(tempFilePath, args)
+				args.success({
+					errMsg: "canvasToTempFilePath:ok",
+					tempFilePath
+				})
+			} catch (e) {
+				args.fail({
+					error: e
+				})
+			}
+		}
+	}
+}
+// #endif

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
uni_modules/lime-painter/components/l-painter/painter.js


+ 56 - 0
uni_modules/lime-painter/components/l-painter/props.js

@@ -0,0 +1,56 @@
+export default {
+	props: {
+		board: Object,
+		pathType: String, // 'base64'、'url'
+		fileType: {
+			type: String,
+			default: 'png'
+		},
+		hidden: Boolean,
+		quality: {
+			type: Number,
+			default: 1
+		},
+		css: [String, Object],
+		// styles: [String, Object],
+		width: [Number, String],
+		height: [Number, String],
+		pixelRatio: Number,
+		customStyle: String,
+		isCanvasToTempFilePath: Boolean,
+		// useCanvasToTempFilePath: Boolean,
+		sleep: {
+			type: Number,
+			default: 1000 / 30
+		},
+		beforeDelay: {
+			type: Number,
+			default: 100
+		},
+		afterDelay: {
+			type: Number,
+			default: 100
+		},
+		performance: Boolean,
+		// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
+		type: {
+			type: String,
+			default: '2d'
+		},
+		// #endif
+		// #ifdef APP-NVUE
+		hybrid: Boolean,
+		timeout: {
+			type: Number,
+			default: 2000
+		},
+		// #endif
+		// #ifdef H5 || APP-PLUS
+		useCORS: Boolean,
+		hidpi: {
+			type: Boolean,
+			default: true
+		}
+		// #endif
+	}
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
uni_modules/lime-painter/components/l-painter/single.js


+ 368 - 0
uni_modules/lime-painter/components/l-painter/utils.js

@@ -0,0 +1,368 @@
+export const networkReg = /^(http|\/\/)/;
+export const isBase64 = (path) => /^data:image\/(\w+);base64/.test(path);
+export function sleep(delay) {
+	return new Promise(resolve => setTimeout(resolve, delay))
+}
+let {platform, SDKVersion} = uni.getSystemInfoSync() 
+export const isPC = /windows|mac/.test(platform)
+// 缓存图片
+let cache = {}
+export function isNumber(value) {
+	return /^-?\d+(\.\d+)?$/.test(value);
+}
+export function toPx(value, baseSize, isDecimal = false) {
+	// 如果是数字
+	if (typeof value === 'number') {
+		return value
+	}
+	// 如果是字符串数字
+	if (isNumber(value)) {
+		return value * 1
+	}
+	// 如果有单位
+	if (typeof value === 'string') {
+		const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g
+		const results = reg.exec(value);
+		if (!value || !results) {
+			return 0;
+		}
+		const unit = results[3];
+		value = parseFloat(value);
+		let res = 0;
+		if (unit === 'rpx') {
+			res = uni.upx2px(value);
+		} else if (unit === 'px') {
+			res = value * 1;
+		} else if (unit === '%') {
+			res = value * toPx(baseSize) / 100;
+		} else if (unit === 'em') {
+			res = value * toPx(baseSize || 14);
+		}
+		return isDecimal ? res.toFixed(2) * 1 : Math.round(res);
+	}
+	return 0
+}
+
+// 计算版本
+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
+}
+
+function gte(version) {
+  // #ifdef MP-ALIPAY
+  SDKVersion = my.SDKVersion
+  // #endif
+  return compareVersion(SDKVersion, version) >= 0;
+}
+export function canIUseCanvas2d() {
+	// #ifdef MP-WEIXIN
+	return gte('2.9.2');
+	// #endif
+	// #ifdef MP-ALIPAY
+	return gte('2.7.15');
+	// #endif
+	// #ifdef MP-TOUTIAO
+	return gte('1.78.0');
+	// #endif
+	return false
+}
+
+// #ifdef MP
+export const prefix = () => {
+	// #ifdef MP-TOUTIAO
+	return tt
+	// #endif
+	// #ifdef MP-WEIXIN
+	return wx
+	// #endif
+	// #ifdef MP-BAIDU
+	return swan
+	// #endif
+	// #ifdef MP-ALIPAY
+	return my
+	// #endif
+	// #ifdef MP-QQ
+	return qq
+	// #endif
+	// #ifdef MP-360
+	return qh
+	// #endif
+}
+// #endif
+
+
+
+/**
+ * base64转路径
+ * @param {Object} base64
+ */
+export function base64ToPath(base64) {
+	const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || [];
+
+	return new Promise((resolve, reject) => {
+		// #ifdef MP
+		const fs = uni.getFileSystemManager()
+		//自定义文件名
+		if (!format) {
+			reject(new Error('ERROR_BASE64SRC_PARSE'))
+		}
+		const time = new Date().getTime();
+		let pre = prefix()
+		// #ifdef MP-TOUTIAO
+		const filePath = `${pre.getEnvInfoSync().common.USER_DATA_PATH}/${time}.${format}`
+		// #endif
+		// #ifndef MP-TOUTIAO
+		const filePath = `${pre.env.USER_DATA_PATH}/${time}.${format}`
+		// #endif
+		fs.writeFile({
+			filePath,
+			data: base64.split(',')[1],
+			encoding: 'base64',
+			success() {
+				resolve(filePath)
+			},
+			fail(err) {
+				console.error(err)
+				reject(err)
+			}
+		})
+		// #endif
+
+		// #ifdef H5
+		// mime类型
+		let mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
+		//base64 解码
+		let byteString = atob(base64.split(',')[1]);
+		//创建缓冲数组
+		let arrayBuffer = new ArrayBuffer(byteString.length);
+		//创建视图
+		let intArray = new Uint8Array(arrayBuffer);
+		for (let i = 0; i < byteString.length; i++) {
+			intArray[i] = byteString.charCodeAt(i);
+		}
+		resolve(URL.createObjectURL(new Blob([intArray], {
+			type: mimeString
+		})))
+		// #endif
+
+		// #ifdef APP-PLUS
+		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) => {
+					bitmap.clear()
+					reject(error)
+				})
+		}, (error) => {
+			bitmap.clear()
+			reject(error)
+		})
+		// #endif
+	})
+}
+
+/**
+ * 路径转base64
+ * @param {Object} string
+ */
+export function pathToBase64(path) {
+	if (/^data:/.test(path)) return path
+	return new Promise((resolve, reject) => {
+		// #ifdef H5
+		let image = new Image();
+		image.setAttribute("crossOrigin", 'Anonymous');
+		image.onload = function() {
+			let canvas = document.createElement('canvas');
+			canvas.width = this.naturalWidth;
+			canvas.height = this.naturalHeight;
+			canvas.getContext('2d').drawImage(image, 0, 0);
+			let result = canvas.toDataURL('image/png')
+			resolve(result);
+			canvas.height = canvas.width = 0
+		}
+		image.src = path + '?v=' + Math.random()
+		image.onerror = (error) => {
+			reject(error);
+		};
+		// #endif
+
+		// #ifdef MP
+		if (uni.canIUse('getFileSystemManager')) {
+			uni.getFileSystemManager().readFile({
+				filePath: path,
+				encoding: 'base64',
+				success: (res) => {
+					resolve('data:image/png;base64,' + res.data)
+				},
+				fail: (error) => {
+					console.error({error, path})
+					reject(error)
+				}
+			})
+		}
+		// #endif
+
+		// #ifdef APP-PLUS
+		plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), (entry) => {
+			entry.file((file) => {
+				const fileReader = new plus.io.FileReader()
+				fileReader.onload = (data) => {
+					resolve(data.target.result)
+				}
+				fileReader.onerror = (error) => {
+					reject(error)
+				}
+				fileReader.readAsDataURL(file)
+			}, reject)
+		}, reject)
+		// #endif
+	})
+}
+
+
+
+export function getImageInfo(path, useCORS) {
+	const isCanvas2D = this && this.canvas && this.canvas.createImage
+	return new Promise(async (resolve, reject) => {
+		// let time = +new Date()
+		let src = path.replace(/^@\//,'/')
+		if (cache[path] && cache[path].errMsg) {
+			resolve(cache[path])
+		} else {
+			try {
+				// #ifdef MP || APP-PLUS
+				if (isBase64(path) && (isCanvas2D ? isPC : true)) {
+					src = await base64ToPath(path)
+				}
+				// #endif
+				// #ifdef H5
+				if(useCORS) {
+					src = await pathToBase64(path)
+				}
+				// #endif
+			} catch (error) {
+				reject({
+					...error,
+					src
+				})
+			}
+			// #ifndef APP-NVUE
+			if(isCanvas2D && !isPC) {
+				const img = this.canvas.createImage()
+				img.onload = function() {
+					const image = {
+						path: img,
+						width:  img.width,
+						height:  img.height
+					}
+					cache[path] = image
+					resolve(cache[path])
+				}
+				img.onerror = function(err) {
+					reject({err,path})
+				}
+				img.src = src
+				return
+			}
+			// #endif
+			uni.getImageInfo({
+				src,
+				success: (image) => {
+					const localReg = /^\.|^\/(?=[^\/])/;
+					// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
+					image.path = localReg.test(src) ?  `/${image.path}` : image.path;
+					// #endif
+					if(isCanvas2D) {
+						const img = this.canvas.createImage()
+						img.onload = function() {
+							image.path = img
+							cache[path] = image
+							resolve(cache[path])
+						}
+						img.onerror = function(err) {
+							reject({err,path})
+						}
+						img.src = src
+						return
+					}
+					// #ifdef APP-PLUS
+					// console.log('getImageInfo', +new Date() - time)
+					// ios 比较严格 可能需要设置跨域
+					if(uni.getSystemInfoSync().osName == 'ios' && useCORS) {
+						pathToBase64(image.path).then(base64 => {
+							image.path = base64
+							cache[path] = image
+							resolve(cache[path])
+						}).catch(err => {
+							console.error({err, path})
+							reject({err,path})
+						})
+						return
+					}
+					// #endif
+					cache[path] = image
+					resolve(cache[path])
+				},
+				fail(err) {
+					console.error({err, path})
+					reject({err,path})
+				}
+			})
+		}
+	})
+}
+
+
+// #ifdef APP-PLUS
+const getLocalFilePath = (path) => {
+	if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path
+		.indexOf('_downloads') === 0) {
+		return path
+	}
+	if (path.indexOf('file://') === 0) {
+		return path
+	}
+	if (path.indexOf('/storage/emulated/0/') === 0) {
+		return path
+	}
+	if (path.indexOf('/') === 0) {
+		const localFilePath = plus.io.convertAbsoluteFileSystem(path)
+		if (localFilePath !== path) {
+			return localFilePath
+		} else {
+			path = path.substr(1)
+		}
+	}
+	return '_www/' + path
+}
+// #endif
+
+

Dosya farkı çok büyük olduğundan ihmal edildi
+ 166 - 0
uni_modules/lime-painter/components/lime-painter/lime-painter.vue


+ 119 - 0
uni_modules/lime-painter/hybrid/html/index.html

@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html lang="zh">
+
+<head>
+	<meta charset="UTF-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<meta http-equiv="X-UA-Compatible" content="ie=edge">
+	<title></title>
+	<style type="text/css">
+		html,
+		body,
+		canvas {
+			padding: 0;
+			margin: 0;
+			width: 100%;
+			height: 100%;
+			overflow-y: hidden;
+			background-color: transparent;
+		}
+	</style>
+</head>
+
+<body>
+	<canvas id="lime-painter"></canvas>
+	<script type="text/javascript" src="./uni.webview.1.5.3.js"></script>
+	<script type="text/javascript" src="./painter.js"></script>
+	<script> 
+		var cache = [];
+		var painter = null;
+		var canvas = null;
+		var context = null;
+		var timer = null;
+		var pixelRatio = 1;
+		console.log = function (...args) {
+			postMessage(args);
+		};
+		// function stringify(key, value) {
+		// 	if (typeof value === 'object' && value !== null) {
+		// 		if (cache.indexOf(value) !== -1) {
+		// 			return;
+		// 		}
+		// 		cache.push(value);
+		// 	}
+		// 	return value;
+		// };
+
+		function emit(event, data) {
+			postMessage({
+				event,
+				data: (typeof data !== 'object' && data !== null ? data : JSON.stringify(data)) 
+			});
+			cache = [];
+		};
+		function postMessage(data) {
+			uni.postMessage({
+				data
+			});
+		};
+		
+		function init(dpr) {
+			canvas = document.querySelector('#lime-painter');
+			context = canvas.getContext('2d');
+			pixelRatio = dpr || window.devicePixelRatio;
+			painter = new Painter({
+				id: 'lime-painter',
+				context,
+				canvas,
+				pixelRatio,
+				width: canvas.offsetWidth,
+				height: canvas.offsetHeight,
+				listen: {
+					onProgress(v) {
+						emit('progressChange', v);
+					},
+					onEffectFail(err) {
+						//console.error(err)
+						emit('fail', err);
+					}
+				}
+			});
+			emit('inited', true);
+		};
+		function save(args) {
+			delete args.success;
+			delete args.fail;
+			clearTimeout(timer);
+			timer = setTimeout(() => {
+				const path = painter.save(args);
+				if (typeof path == 'string') {
+					const index = Math.ceil(path.length / 8);
+					for (var i = 0; i < 8; i++) {
+						if (i == 7) {
+							emit('success', path.substr(i * index, index));
+						} else {
+							emit('file', path.substr(i * index, index));
+						}
+					};
+				} else {
+					// console.log('canvas no data')
+					emit('fail', 'canvas no data');
+				};
+			}, 30);
+		};
+		async function source(args) {
+			let size = await painter.source(args);
+			emit('layoutChange', size);
+			if(!canvas.height) {
+				console.log('canvas no size')
+				emit('fail', 'canvas no size');
+			}
+			painter.render().catch(err => {
+				// console.error(err)
+				emit('fail', err);
+			});
+		};
+	</script>
+</body>
+
+</html>

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
uni_modules/lime-painter/hybrid/html/painter.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
uni_modules/lime-painter/hybrid/html/uni.webview.1.5.3.js


+ 92 - 0
uni_modules/lime-painter/package.json

@@ -0,0 +1,92 @@
+{
+  "id": "lime-painter",
+  "displayName": "海报画板",
+  "version": "1.9.6.4",
+  "description": "一款canvas海报组件,更优雅的海报生成方案,有限的支持富文本",
+  "keywords": [
+    "海报",
+    "富文本",
+    "生成海报",
+    "生成二维码",
+    "JSON"
+],
+  "repository": "https://gitee.com/liangei/lime-painter", 
+  "engines": {
+    "HBuilderX": "^3.4.14"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": "305716444"
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "u",
+          "Edge": "u",
+          "Firefox": "u",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+        "QQ": "y",
+        "钉钉": "u",
+        "快手": "u",
+        "飞书": "u",
+        "京东": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  },
+  "name": "lime-painter",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC"
+}

+ 388 - 0
uni_modules/lime-painter/parser.js

@@ -0,0 +1,388 @@
+/*
+ * HTML5 Parser By Sam Blowes
+ *
+ * Designed for HTML5 documents
+ *
+ * Original code by John Resig (ejohn.org)
+ * http://ejohn.org/blog/pure-javascript-html-parser/
+ * Original code by Erik Arvidsson, Mozilla Public License
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
+ *
+ * ----------------------------------------------------------------------------
+ * License
+ * ----------------------------------------------------------------------------
+ *
+ * This code is triple licensed using Apache Software License 2.0,
+ * Mozilla Public License or GNU Public License
+ *
+ * ////////////////////////////////////////////////////////////////////////////
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.  You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ////////////////////////////////////////////////////////////////////////////
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * The Original Code is Simple HTML Parser.
+ *
+ * The Initial Developer of the Original Code is Erik Arvidsson.
+ * Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
+ * Reserved.
+ *
+ * ////////////////////////////////////////////////////////////////////////////
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * ----------------------------------------------------------------------------
+ * Usage
+ * ----------------------------------------------------------------------------
+ *
+ * // Use like so:
+ * HTMLParser(htmlString, {
+ *     start: function(tag, attrs, unary) {},
+ *     end: function(tag) {},
+ *     chars: function(text) {},
+ *     comment: function(text) {}
+ * });
+ *
+ * // or to get an XML string:
+ * HTMLtoXML(htmlString);
+ *
+ * // or to get an XML DOM Document
+ * HTMLtoDOM(htmlString);
+ *
+ * // or to inject into an existing document/DOM node
+ * HTMLtoDOM(htmlString, document);
+ * HTMLtoDOM(htmlString, document.body);
+ *
+ */
+// Regular Expressions for parsing tags and attributes
+var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
+var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
+var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
+
+var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
+// fixed by xxx 将 ins 标签从块级名单中移除
+
+var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
+
+var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
+// (and which close themselves)
+
+var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
+
+var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
+
+var special = makeMap('script,style');
+function HTMLParser(html, handler) {
+  var index;
+  var chars;
+  var match;
+  var stack = [];
+  var last = html;
+
+  stack.last = function () {
+    return this[this.length - 1];
+  };
+
+  while (html) {
+    chars = true; // Make sure we're not in a script or style element
+
+    if (!stack.last() || !special[stack.last()]) {
+      // Comment
+      if (html.indexOf('<!--') == 0) {
+        index = html.indexOf('-->');
+
+        if (index >= 0) {
+          if (handler.comment) {
+            handler.comment(html.substring(4, index));
+          }
+
+          html = html.substring(index + 3);
+          chars = false;
+        } // end tag
+
+      } else if (html.indexOf('</') == 0) {
+        match = html.match(endTag);
+
+        if (match) {
+          html = html.substring(match[0].length);
+          match[0].replace(endTag, parseEndTag);
+          chars = false;
+        } // start tag
+
+      } else if (html.indexOf('<') == 0) {
+        match = html.match(startTag);
+
+        if (match) {
+          html = html.substring(match[0].length);
+          match[0].replace(startTag, parseStartTag);
+          chars = false;
+        }
+      }
+
+      if (chars) {
+        index = html.indexOf('<');
+        var text = index < 0 ? html : html.substring(0, index);
+        html = index < 0 ? '' : html.substring(index);
+
+        if (handler.chars) {
+          handler.chars(text);
+        }
+      }
+    } else {
+      html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
+        text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
+
+        if (handler.chars) {
+          handler.chars(text);
+        }
+
+        return '';
+      });
+      parseEndTag('', stack.last());
+    }
+
+    if (html == last) {
+      throw 'Parse Error: ' + html;
+    }
+
+    last = html;
+  } // Clean up any remaining tags
+
+
+  parseEndTag();
+
+  function parseStartTag(tag, tagName, rest, unary) {
+    tagName = tagName.toLowerCase();
+    if (block[tagName]) {
+      while (stack.last() && inline[stack.last()]) {
+        parseEndTag('', stack.last());
+      }
+    }
+
+    if (closeSelf[tagName] && stack.last() == tagName) {
+      parseEndTag('', tagName);
+    }
+
+    unary = empty[tagName] || !!unary;
+
+    if (!unary) {
+      stack.push(tagName);
+    }
+
+    if (handler.start) {
+      var attrs = [];
+      rest.replace(attr, function (match, name) {
+        var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
+        attrs.push({
+          name: name,
+          value: value,
+          escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
+
+        });
+      });
+
+      if (handler.start) {
+        handler.start(tagName, attrs, unary);
+      }
+    }
+  }
+
+  function parseEndTag(tag, tagName) {
+    // If no tag name is provided, clean shop
+    if (!tagName) {
+      var pos = 0;
+    } // Find the closest opened tag of the same type
+    else {
+        for (var pos = stack.length - 1; pos >= 0; pos--) {
+          if (stack[pos] == tagName) {
+            break;
+          }
+        }
+      }
+
+    if (pos >= 0) {
+      // Close all the open elements, up the stack
+      for (var i = stack.length - 1; i >= pos; i--) {
+        if (handler.end) {
+          handler.end(stack[i]);
+        }
+      } // Remove the open elements from the stack
+
+
+      stack.length = pos;
+    }
+  }
+}
+
+function makeMap(str) {
+  var obj = {};
+  var items = str.split(',');
+
+  for (var i = 0; i < items.length; i++) {
+    obj[items[i]] = true;
+  }
+
+  return obj;
+}
+
+function removeDOCTYPE(html) {
+  return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
+}
+
+function parseAttrs(attrs) {
+  return attrs.reduce(function (pre, attr) {
+    var value = attr.value;
+    var name = attr.name;
+    if (pre[name]) {
+			pre[name] = pre[name] + " " + value;
+    } else {
+			pre[name] = value;
+    }
+
+    return pre;
+  }, {});
+}
+function convertStyleStringToJSON(styleString) {
+  var styles = styleString.split(";"); // 通过分号将样式字符串分割为多个样式声明
+  var result = {};
+
+  styles.forEach(function(style) {
+    var styleParts = style.split(":"); // 通过冒号将样式声明分割为属性和值
+    var property = styleParts[0].trim();
+    var value = styleParts[1] && styleParts[1].trim();
+
+    if (property && value) {
+      result[property] = value; // 将属性和值添加到结果对象中
+    }
+  });
+
+  return result;
+}
+function parseHtml(html) {
+  html = removeDOCTYPE(html);
+  var stacks = [];
+  var results = {
+    node: 'root',
+    children: []
+  };
+  HTMLParser(html, {
+    start: function start(tag, attrs, unary) {
+      var node = {
+        name: tag
+      };
+
+      if (attrs.length !== 0) {
+        node.attrs = parseAttrs(attrs);
+		node.styles = node.attrs.style ? convertStyleStringToJSON(node.attrs.style) : {}
+      }
+	
+	if(!node.type) {
+	  if(inline[node.name] && node.name !== 'img' ) {
+		node.type = 'text';
+		if(node.name == 'br') {
+			node.text = '\n'
+		} else if(node.name == 'strong'){
+			node.styles.fontWeight = 'bold'
+		}
+	  } else if(node.name == 'img'){
+		 node.type = 'image' 
+		 node.src =  node.attrs.src
+	  } else {
+		   node.type = 'view' 
+		   if(['h1','h2','h3','h4','h5','h6'].includes(node.name)) {
+			   node.styles.fontWeight = 'bold'
+		   }
+	  }
+	}		
+      if (unary) {
+        var parent = stacks[0] || results;
+
+        if (!parent.children) {
+          parent.children = [];
+        }
+
+        parent.children.push(node);
+      } else {
+        stacks.unshift(node);
+      }
+    },
+    end: function end(tag) {
+      var node = stacks.shift();
+      if (node.name !== tag) console.error('invalid state: mismatch end tag');
+      if (stacks.length === 0) {
+        results.children.push(node);
+      } else {
+        var parent = stacks[0];
+
+        if (!parent.children) {
+          parent.children = [];
+        }
+        parent.children.push(node);
+      }
+	  const isTextBox = node.children && node.children.length > 1 && node.children.every(child => {
+		  return ['text','image'].includes(child.type)
+	  })
+	  if(isTextBox) {
+		  node.type = 'textBox'
+	  }
+    },
+    chars: function chars(text) {
+      var node = {
+        type: 'text',
+        text: text
+      };
+
+      if (stacks.length === 0) {
+        results.children.push(node);
+      } else {
+        var parent = stacks[0];
+
+        if (!parent.children) {
+          parent.children = [];
+        }
+
+        parent.children.push(node);
+      }
+    },
+    comment: function comment(text) {
+      var node = {
+        node: 'comment',
+        text: text
+      };
+      var parent = stacks[0];
+
+      if (!parent.children) {
+        parent.children = [];
+      }
+
+      parent.children.push(node);
+    }
+  });
+  return results.children;
+}
+
+export default parseHtml;

+ 963 - 0
uni_modules/lime-painter/readme.md

@@ -0,0 +1,963 @@
+# Painter 画板 测试版
+
+> uniapp 海报画板,更优雅的海报生成方案  
+> [查看更多 站点 1](https://limeui.qcoon.cn/#/painter)  
+> [查看更多 站点 2](http://liangei.gitee.io/limeui/#/painter)  
+> Q 群:1169785031
+
+## 平台兼容
+
+| H5  | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
+| --- | ---------- | ------------ | ---------- | ---------- | --------- | --- |
+| √   | √          | √            | 未测       | √          | √         | √   |
+
+## 安装
+在市场导入**[海报画板](https://ext.dcloud.net.cn/plugin?id=2389)uni_modules**版本的即可,无需`import`
+
+## 代码演示
+
+### 插件demo
+- lime-painter 为 demo
+- 位于 uni_modules/lime-painter/components/lime-painter
+- 导入插件后直接使用可查看demo
+```vue
+<lime-painter />
+```
+
+
+### 基本用法
+
+- 插件提供 JSON 及 Template 的方式绘制海报
+- 参考 css 块状流布局模拟 css schema。
+- 另外flex布局还不是成完善,请谨慎使用,普通的流布局我觉得已经够用了。
+
+#### 方式一 Template
+
+- 提供`l-painter-view`、`l-painter-text`、`l-painter-image`、`l-painter-qrcode`四种类型组件
+- 通过 `css` 属性绘制样式,与 style 使用方式保持一致。
+```html
+<l-painter>
+	//如果使用Template出现顺序错乱,可使用`template` 等所有变量完成再显示
+	<template v-if="show">
+		<l-painter-view
+			css="background: #07c160; height: 120rpx; width: 120rpx; display: inline-block"
+		></l-painter-view>
+		<l-painter-view
+			css="background: #1989fa; height: 120rpx; width: 120rpx; border-top-right-radius: 60rpx; border-bottom-left-radius: 60rpx; display: inline-block; margin: 0 30rpx;"
+		></l-painter-view>
+		<l-painter-view
+			css="background: #ff9d00; height: 120rpx; width: 120rpx; border-radius: 50%; display: inline-block"
+		></l-painter-view>
+	<template>
+</l-painter>
+```
+
+#### 方式二 JSON
+
+- 在 json 里四种类型组件的`type`为`view`、`text`、`image`、`qrcode`
+- 通过 `board` 设置海报所需的 JSON 数据进行绘制或`ref`获取组件实例调用组件内的`render(json)`
+- 所有类型的 schema 都具有`css`字段,css 的 key 值使用**驼峰**如:`lineHeight`
+
+```html
+<l-painter :board="poster"/>
+```
+
+```js
+data() {
+	return {
+		poster: {
+			css: {
+				// 根节点若无尺寸,自动获取父级节点
+				width: '750rpx'
+			},
+			views: [
+				{
+					css: {
+						background: "#07c160",
+						height: "120rpx",
+						width: "120rpx",
+						display: "inline-block"
+					},
+					type: "view"
+				},
+				{
+					css: {
+						background: "#1989fa",
+						height: "120rpx",
+						width: "120rpx",
+						borderTopRightRadius: "60rpx",
+						borderBottomLeftRadius: "60rpx",
+						display: "inline-block",
+						margin: "0 30rpx"
+					},
+					views: [],
+					type: "view"
+				},
+				{
+					css: {
+						background: "#ff9d00",
+						height: "120rpx",
+						width: "120rpx",
+						borderRadius: "50%",
+						display: "inline-block"
+					},
+					views: [],
+					type: "view"
+				},
+			]
+		}
+	}
+}
+```
+
+### View 容器
+
+- 类似于 `div` 可以嵌套承载更多的 view、text、image,qrcode 共同构建一颗完整的节点树
+- 在 JSON 里具有 `views` 的数组字段,用于嵌套承载节点。
+
+#### 方式一 Template
+
+```html
+<l-painter>
+  <l-painter-view css="background: #f0f0f0; padding-top: 100rpx;">
+    <l-painter-view
+      css="background: #d9d9d9; width: 33.33%; height: 100rpx; display: inline-block"
+    ></l-painter-view>
+    <l-painter-view
+      css="background: #bfbfbf; width: 66.66%; height: 100rpx; display: inline-block"
+    ></l-painter-view>
+  </l-painter-view>
+</l-painter>
+```
+
+#### 方式二 JSON
+
+```js
+{
+	css: {},
+	views: [
+		{
+			type: 'view',
+			css: {
+				background: '#f0f0f0',
+				paddingTop: '100rpx'
+			},
+			views: [
+				{
+					type: 'view',
+					css: {
+						background: '#d9d9d9',
+						width: '33.33%',
+						height: '100rpx',
+						display: 'inline-block'
+					}
+				},
+				{
+					type: 'view',
+					css: {
+						background: '#bfbfbf',
+						width: '66.66%',
+						height: '100rpx',
+						display: 'inline-block'
+					}
+				}
+			],
+
+		}
+	]
+}
+```
+
+### Text 文本
+
+- 通过 `text` 属性填写文本内容。
+- 支持`\n`换行符
+- 支持省略号,使用 css 的`line-clamp`设置行数,当文字内容超过会显示省略号。
+- 支持`text-decoration`
+
+#### 方式一 Template
+
+```html
+<l-painter>
+  <l-painter-view css="background: #e0e2db; padding: 30rpx; color: #222a29">
+    <l-painter-text
+      text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
+    />
+    <l-painter-text
+      text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
+      css="text-align:center; padding-top: 20rpx; text-decoration: line-through "
+    />
+    <l-painter-text
+      text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
+      css="text-align:right; padding-top: 20rpx"
+    />
+    <l-painter-text
+      text="水调歌头\n明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。"
+      css="line-clamp: 3; padding-top: 20rpx; background: linear-gradient(,#ff971b 0%, #ff5000 100%); background-clip: text"
+    />
+  </l-painter-view>
+</l-painter>
+```
+
+#### 方式二 JSON
+
+```js
+// 基础用法
+{
+	type: 'text',
+	text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
+},
+{
+	type: 'text',
+	text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
+	css: {
+		// 设置居中对齐
+		textAlign: 'center',
+		// 设置中划线
+		textDecoration: 'line-through'
+	}
+},
+{
+	type: 'text',
+	text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
+	css: {
+		// 设置右对齐
+		textAlign: 'right',
+	}
+},
+{
+	type: 'text',
+	text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
+	css: {
+		// 设置行数,超出显示省略号
+		lineClamp: 3,
+		// 渐变文字
+		background: 'linear-gradient(,#ff971b 0%, #1989fa 100%)',
+		backgroundClip: 'text'
+	}
+}
+```
+
+### Image 图片
+
+- 通过 `src` 属性填写图片路径。
+- 图片路径支持:网络图片,本地 static 里的图片路径,缓存路径,**字节的static目录是写相对路径**
+- 通过 `css` 的 `object-fit`属性可以设置图片的填充方式,可选值见下方 CSS 表格。
+- 通过 `css` 的 `object-position`配合 `object-fit` 可以设置图片的对齐方式,类似于`background-position`,详情见下方 CSS 表格。
+- 使用网络图片时:小程序需要去公众平台配置 [downloadFile](https://mp.weixin.qq.com/) 域名
+- 使用网络图片时:**H5 和 Nvue 需要决跨域问题**
+
+#### 方式一 Template
+
+```html
+<l-painter>
+  <!-- 基础用法 -->
+  <l-painter-image
+    src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
+    css="width: 200rpx; height: 200rpx"
+  />
+  <!-- 填充方式 -->
+  <!-- css object-fit 设置 填充方式 见下方表格-->
+  <l-painter-image
+    src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
+    css="width: 200rpx; height: 200rpx; object-fit: contain; background: #eee"
+  />
+  <!-- css object-position 设置 图片的对齐方式-->
+  <l-painter-image
+    src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
+    css="width: 200rpx; height: 200rpx; object-fit: contain; object-position: 50% 50%; background: #eee"
+  />
+</l-painter>
+```
+
+#### 方式二 JSON
+
+```js
+// 基础用法
+{
+	type: 'image',
+	src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
+	css: {
+		width: '200rpx',
+		height: '200rpx'
+	}
+},
+// 填充方式
+// css objectFit 设置 填充方式 见下方表格
+{
+	type: 'image',
+	src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
+	css: {
+		width: '200rpx',
+		height: '200rpx',
+		objectFit: 'contain'
+	}
+},
+// css objectPosition 设置 图片的对齐方式
+{
+	type: 'image',
+	src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
+	css: {
+		width: '200rpx',
+		height: '200rpx',
+		objectFit: 'contain',
+		objectPosition: '50% 50%'
+	}
+}
+```
+
+### Qrcode 二维码
+
+- 通过`text`属性填写需要生成二维码的文本。
+- 通过 `css` 里的 `color` 可设置生成码点的颜色。
+- 通过 `css` 里的 `background`可设置背景色。
+- 通过 `css `里的 `width`、`height`设置尺寸。
+
+#### 方式一 Template
+
+```html
+<l-painter>
+  <l-painter-qrcode
+    text="limeui.qcoon.cn"
+    css="width: 200rpx; height: 200rpx"
+  />
+</l-painter>
+```
+
+#### 方式二 JSON
+
+```js
+{
+	type: 'qrcode',
+	text: 'limeui.qcoon.cn',
+	css: {
+		width: '200rpx',
+		height: '200rpx',
+	}
+}
+```
+
+### 富文本
+- 这是一个有限支持的测试能力,只能通过JSON方式,不要抱太大希望!
+- 首先需要把富文本转成JSON,这需要引入`parser`这个包,如果你不使用是不会进入主包
+
+```html
+<l-painter ref="painter"/>
+```
+```js
+import parseHtml from '@/uni_modules/lime-painter/parser'
+const json = parseHtml(`<p><span>测试测试</span><img src="/static/logo.png"/></p>`)
+this.$refs.painter.render(json)
+```
+
+### 生成图片
+
+- 方式1、通过设置`isCanvasToTempFilePath`自动生成图片并在 `@success` 事件里接收海报临时路径
+- 方式2、通过调用内部方法生成图片:
+
+```html
+<l-painter ref="painter">...code</l-painter>
+```
+
+```js
+this.$refs.painter.canvasToTempFilePathSync({
+  fileType: "jpg",
+  // 如果返回的是base64是无法使用 saveImageToPhotosAlbum,需要设置 pathType为url
+  pathType: 'url',
+  quality: 1,
+  success: (res) => {
+    console.log(res.tempFilePath);
+	// 非H5 保存到相册
+	// H5 提示用户长按图另存
+	uni.saveImageToPhotosAlbum({
+		filePath: res.tempFilePath,
+		success: function () {
+			console.log('save success');
+		}
+	});
+  },
+});
+```
+
+### 主动调用方式
+
+- 通过获取组件实例内部的`render`函数 传递`JSON`即可
+
+```html
+<l-painter ref="painter" />
+```
+
+```js
+// 渲染
+this.$refs.painter.render(jsonSchema);
+// 生成图片
+this.$refs.painter.canvasToTempFilePathSync({
+  fileType: "jpg",
+  // 如果返回的是base64是无法使用 saveImageToPhotosAlbum,需要设置 pathType为url
+  pathType: 'url',
+  quality: 1,
+  success: (res) => {
+    console.log(res.tempFilePath);
+	// 非H5 保存到相册
+	uni.saveImageToPhotosAlbum({
+		filePath: res.tempFilePath,
+		success: function () {
+			console.log('save success');
+		}
+	});
+  },
+});
+```
+
+
+### H5跨域
+- 一般是需要后端或管理OSS资源的大佬处理
+- 一般OSS的处理方式:
+
+1、设置来源
+```cmd
+*
+```
+
+2、允许Methods
+```html
+GET
+```
+
+3、允许Headers
+```html
+access-control-allow-origin:*
+```
+
+4、最后如果还是不行,可试下给插件设置`useCORS`
+```html
+<l-painter useCORS>
+```
+
+
+
+### 海报示例
+
+- 提供一份示例,只把插件当成生成图片的工具,非必要不要在弹窗里使用。
+- 通过设置`isCanvasToTempFilePath`主动生成图片,再由 `@success` 事件接收海报临时路径
+- 设置`hidden`隐藏画板。
+请注意,示例用到了图片,海报的渲染是包括下载图片的时间,也许在某天图片会失效或访问超级慢,请更换为你的图片再查看,另外如果你是小程序请在使用示例时把**不校验合法域名**勾上!!!!!不然不显示还以为是插件的锅,求求了大佬们!
+#### 方式一 Template
+
+```html
+<image :src="path" mode="widthFix"></image>
+<l-painter
+  isCanvasToTempFilePath
+  @success="path = $event"
+  hidden
+  css="width: 750rpx; padding-bottom: 40rpx; background: linear-gradient(,#ff971b 0%, #ff5000 100%)"
+>
+  <l-painter-image
+    src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
+    css="margin-left: 40rpx; margin-top: 40rpx; width: 84rpx;  height: 84rpx; border-radius: 50%;"
+  />
+  <l-painter-view
+    css="margin-top: 40rpx; padding-left: 20rpx; display: inline-block"
+  >
+    <l-painter-text
+      text="隔壁老王"
+      css="display: block; padding-bottom: 10rpx; color: #fff; font-size: 32rpx; fontWeight: bold"
+    />
+    <l-painter-text
+      text="为您挑选了一个好物"
+      css="color: rgba(255,255,255,.7); font-size: 24rpx"
+    />
+  </l-painter-view>
+  <l-painter-view
+    css="margin-left: 40rpx; margin-top: 30rpx; padding: 32rpx; box-sizing: border-box; background: #fff; border-radius: 16rpx; width: 670rpx; box-shadow: 0 20rpx 58rpx rgba(0,0,0,.15)"
+  >
+    <l-painter-image
+      src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
+      css="object-fit: cover; object-position: 50% 50%; width: 606rpx; height: 606rpx; border-radius: 12rpx;"
+    />
+    <l-painter-view
+      css="margin-top: 32rpx; color: #FF0000; font-weight: bold; font-size: 28rpx; line-height: 1em;"
+    >
+      <l-painter-text text="¥" css="vertical-align: bottom" />
+      <l-painter-text
+        text="39"
+        css="vertical-align: bottom; font-size: 58rpx"
+      />
+      <l-painter-text text=".39" css="vertical-align: bottom" />
+      <l-painter-text
+        text="¥59.99"
+        css="vertical-align: bottom; padding-left: 10rpx; font-weight: normal; text-decoration: line-through; color: #999999"
+      />
+    </l-painter-view>
+    <l-painter-view css="margin-top: 32rpx; font-size: 26rpx; color: #8c5400">
+      <l-painter-text text="自营" css="color: #212121; background: #ffb400;" />
+      <l-painter-text
+        text="30天最低价"
+        css="margin-left: 16rpx; background: #fff4d9; text-decoration: line-through;"
+      />
+      <l-painter-text
+        text="满减优惠"
+        css="margin-left: 16rpx; background: #fff4d9"
+      />
+      <l-painter-text
+        text="超高好评"
+        css="margin-left: 16rpx; background: #fff4d9"
+      />
+    </l-painter-view>
+    <l-painter-view css="margin-top: 30rpx">
+      <l-painter-text
+        css="line-clamp: 2; color: #333333; line-height: 1.8em; font-size: 36rpx; width: 478rpx; padding-right:32rpx; box-sizing: border-box"
+        text="360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝"
+      ></l-painter-text>
+      <l-painter-qrcode
+        css="width: 128rpx; height: 128rpx;"
+        text="limeui.qcoon.cn"
+      ></l-painter-qrcode>
+    </l-painter-view>
+  </l-painter-view>
+</l-painter>
+```
+
+```js
+data() {
+	return {
+		path: ''
+	}
+}
+```
+
+#### 方式二 JSON
+
+```html
+<image :src="path" mode="widthFix"></image>
+<l-painter
+  :board="poster"
+  isCanvasToTempFilePath
+  @success="path = $event"
+  hidden
+/>
+```
+
+```js
+data() {
+	return {
+		path: '',
+		poster: {
+		    css: {
+		        width: "750rpx",
+		        paddingBottom: "40rpx",
+		        background: "linear-gradient(,#000 0%, #ff5000 100%)"
+		    },
+		    views: [
+		        {
+		            src: "https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg",
+		            type: "image",
+		            css: {
+		                background: "#fff",
+		                objectFit: "cover",
+		                marginLeft: "40rpx",
+		                marginTop: "40rpx",
+		                width: "84rpx",
+		                border: "2rpx solid #fff",
+		                boxSizing: "border-box",
+		                height: "84rpx",
+		                borderRadius: "50%"
+		            }
+		        },
+		        {
+		            type: "view",
+		            css: {
+		                marginTop: "40rpx",
+		                paddingLeft: "20rpx",
+		                display: "inline-block"
+		            },
+		            views: [
+		                {
+		                    text: "隔壁老王",
+		                    type: "text",
+		                    css: {
+		                        display: "block",
+		                        paddingBottom: "10rpx",
+		                        color: "#fff",
+		                        fontSize: "32rpx",
+		                        fontWeight: "bold"
+		                    }
+		                },
+		                {
+		                    text: "为您挑选了一个好物",
+		                    type: "text",
+		                    css: {
+		                        color: "rgba(255,255,255,.7)",
+		                        fontSize: "24rpx"
+		                    },
+		                }
+		            ],
+		        },
+		        {
+		            css: {
+		                marginLeft: "40rpx",
+		                marginTop: "30rpx",
+		                padding: "32rpx",
+		                boxSizing: "border-box",
+		                background: "#fff",
+		                borderRadius: "16rpx",
+		                width: "670rpx",
+		                boxShadow: "0 20rpx 58rpx rgba(0,0,0,.15)"
+		            },
+		            views: [
+		                {
+							src: "https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg",
+							type: "image",
+		                    css: {
+		                        objectFit: "cover",
+		                        objectPosition: "50% 50%",
+		                        width: "606rpx",
+		                        height: "606rpx"
+		                    },
+		                }, {
+		                    css: {
+		                        marginTop: "32rpx",
+		                        color: "#FF0000",
+		                        fontWeight: "bold",
+		                        fontSize: "28rpx",
+		                        lineHeight: "1em"
+		                    },
+		                    views: [{
+								text: "¥",
+								type: "text",
+		                        css: {
+		                            verticalAlign: "bottom"
+		                        },
+		                    }, {
+								text: "39",
+								type: "text",
+		                        css: {
+		                            verticalAlign: "bottom",
+		                            fontSize: "58rpx"
+		                        },
+		                    }, {
+								text: ".39",
+								type: "text",
+		                        css: {
+		                            verticalAlign: "bottom"
+		                        },
+		                    }, {
+								text: "¥59.99",
+								type: "text",
+		                        css: {
+		                            verticalAlign: "bottom",
+		                            paddingLeft: "10rpx",
+		                            fontWeight: "normal",
+		                            textDecoration: "line-through",
+		                            color: "#999999"
+		                        }
+		                    }],
+
+		                    type: "view"
+		                }, {
+		                    css: {
+		                        marginTop: "32rpx",
+		                        fontSize: "26rpx",
+		                        color: "#8c5400"
+		                    },
+		                    views: [{
+								text: "自营",
+								type: "text",
+		                        css: {
+		                            color: "#212121",
+		                            background: "#ffb400"
+		                        },
+		                    }, {
+								text: "30天最低价",
+								type: "text",
+		                        css: {
+		                            marginLeft: "16rpx",
+		                            background: "#fff4d9",
+		                            textDecoration: "line-through"
+		                        },
+		                    }, {
+								text: "满减优惠",
+								type: "text",
+		                        css: {
+		                            marginLeft: "16rpx",
+		                            background: "#fff4d9"
+		                        },
+		                    }, {
+								text: "超高好评",
+								type: "text",
+		                        css: {
+		                            marginLeft: "16rpx",
+		                            background: "#fff4d9"
+		                        },
+
+		                    }],
+
+		                    type: "view"
+		                }, {
+		                    css: {
+		                        marginTop: "30rpx"
+		                    },
+		                    views: [
+								{
+									text: "360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝",
+									type: "text",
+									css: {
+										paddingRight: "32rpx",
+										boxSizing: "border-box",
+										lineClamp: 2,
+										color: "#333333",
+										lineHeight: "1.8em",
+										fontSize: "36rpx",
+										width: "478rpx"
+		                        },
+		                    }, {
+								text: "limeui.qcoon.cn",
+								type: "qrcode",
+		                        css: {
+		                            width: "128rpx",
+		                            height: "128rpx",
+		                        },
+
+		                    }],
+		                    type: "view"
+		                }],
+		            type: "view"
+		        }
+		    ]
+		}
+	}
+}
+```
+
+
+### 自定义字体
+- 需要平台的支持,已知微信小程序支持,其它的没试过,如果可行请告之
+
+```
+// 需要在app.vue中下载字体
+uni.loadFontFace({
+	global:true,
+	scopes: ['native'],
+	family: '自定义字体名称',
+	source: 'url("https://sungd.github.io/Pacifico.ttf")',
+  
+	success() {
+	  console.log('success')
+  }
+})
+
+
+// 然后就可以在插件的css中写font-family: '自定义字体名称'
+```
+
+
+### Nvue
+- 必须为HBX 3.4.11及以上
+
+
+### 原生小程序
+
+- 插件里的`painter.js`支持在原生小程序中使用
+- new Painter 之后在`source`里传入 JSON
+- 再调用`render`绘制海报
+- 如需生成图片,请查看微信小程序 cavnas 的[canvasToTempFilePath](https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html)
+
+```html
+<canvas type="2d" id="painter" style="width: 100%"></canvas>
+```
+
+```js
+import { Painter } from "./painter";
+page({
+  data: {
+    poster: {
+      css: {
+        width: "750rpx",
+      },
+      views: [
+        {
+          type: "view",
+          css: {
+            background: "#d2d4c8",
+            paddingTop: "100rpx",
+          },
+          views: [
+            {
+              type: "view",
+              css: {
+                background: "#5f7470",
+                width: "33.33%",
+                height: "100rpx",
+                display: "inline-block",
+              },
+            },
+            {
+              type: "view",
+              css: {
+                background: "#889696",
+                width: "33.33%",
+                height: "100rpx",
+                display: "inline-block",
+              },
+            },
+            {
+              type: "view",
+              css: {
+                background: "#b8bdb5",
+                width: "33.33%",
+                height: "100rpx",
+                display: "inline-block",
+              },
+            },
+          ],
+        },
+      ],
+    },
+  },
+  async onLoad() {
+    const res = await this.getCentext();
+    const painter = new Painter(res);
+    // 返回计算布局后的整个内容尺寸
+    const { width, height } = await painter.source(this.data.poster);
+    // 得到计算后的尺寸后 可给canvas尺寸赋值,达到动态响应效果
+    // 渲染
+    await painter.render();
+  },
+  // 获取canvas 2d
+  // 非2d 需要传一个 createImage 方法用于获取图片信息 即把 getImageInfo 的 success 通过 promise resolve 返回
+  getCentext() {
+    return new Promise((resolve) => {
+      wx.createSelectorQuery()
+        .select(`#painter`)
+        .node()
+        .exec((res) => {
+          let { node: canvas } = res[0];
+          resolve({
+            canvas,
+            context: canvas.getContext("2d"),
+            width: canvas.width,
+            height: canvas.height,
+			// createImage: getImageInfo()
+            pixelRatio: 2,
+          });
+        });
+    });
+  },
+});
+```
+
+### 旧版(1.6.x)更新
+
+- 由于 1.8.x 版放弃了以定位的方式,所以 1.6.x 版更新之后要每个样式都加上`position: absolute`
+- 旧版的 `image` mode 模式被放弃,使用`object-fit`
+- 旧版的 `isRenderImage` 改成 `is-canvas-to-temp-file-path`
+- 旧版的 `maxLines` 改成 `line-clamp`
+
+## API
+
+### Props
+
+| 参数                       | 说明                                                         | 类型             | 默认值       |
+| -------------------------- | ------------------------------------------------------------ | ---------------- | ------------ |
+| board                      | JSON 方式的海报元素对象集                                    | <em>object</em>  | -            |
+| css                        | 海报内容最外层的样式,可以理解为`body`                           | <em>object</em>  | 参数请向下看 |
+| custom-style               | canvas 元素的样式                                            | <em>string</em>  |              |
+| hidden               		 | 隐藏画板                                                    | <em>boolean</em>  |   `false`    |
+| is-canvas-to-temp-file-path | 是否生成图片,在`@success`事件接收图片地址                   | <em>boolean</em> | `false`      |
+| after-delay                | 生成图片错乱,可延时生成图片                                 | <em>number</em>  | `100`        |
+| type                       | canvas 类型,对微信头条支付宝小程序可有效,可选值:`2d`,`''` | <em>string</em>  | `2d`         |
+| file-type                  | 生成图片的后缀类型, 可选值:`png`、`jpg`                     | <em>string</em>  | `png`        |
+| path-type                  | 生成图片路径类型,可选值`url`、`base64`                      | <em>string</em>  | `-`          |
+| pixel-ratio                | 生成图片的像素密度,默认为对应手机的像素密度,`nvue`无效     | <em>number</em>  | `-`          |
+| hidpi                | H5和APP是否使用高清处理     | <em>boolean</em>  | `true`          |
+| width                      | **废弃** 画板的宽度,一般只用于通过内部方法时加上            | <em>number</em>  | ``           |
+| height                     | **废弃** 画板的高度 ,同上                                   | <em>number</em>  | ``           |
+
+### css
+| 属性名                                                                              | 支持的值或类型                                                                                                                                                                       | 默认值   |
+| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
+| (min\max)width                                                                      | 支持`%`、`rpx`、`px`                                                                                                                                                                 | -        |
+| height                                                                              | 同上                                                                                                                                                                                 | -        |
+| color                                                                               | `string`                                                                                                                                                                             | -        |
+| position                                                                            | 定位,可选值:`absolute`、`fixed`                                                                                                                                                    | -        |
+| ↳ left、top、right、bottom                                                          | 配合`position`才生效,支持`%`、`rpx`、`px`                                                                                                                                           | -        |
+| margin                                                                              | 可简写或各方向分别写,如:`margin-top`,支持`auto`、`rpx`、`px`                                                                                                                      | -        |
+| padding                                                                             | 可简写或各方向分别写,支持`rpx`、`px`                                                                                                                                                | -        |
+| border                                                                              | 可简写或各个值分开写:`border-width`、`border-style` 、`border-color`,简写请按顺序写                                                                                                | -        |
+| line-clamp                                                                          | `number`,超过行数显示省略号                                                                                                                                                         | -        |
+| vertical-align                                                                      | 文字垂直对齐,可选值:`bottom`、`top`、`middle`                                                                                                                                      | `middle` |
+| line-height                                                                         | 文字行高,支持`rpx`、`px`、`em`                                                                                                                                                      | `1.4em`  |
+| font-weight                                                                         | 文字粗细,可选值:`normal`、`bold`                                                                                                                                                   | `normal` |
+| font-size                                                                           | 文字大小,`string`,支持`rpx`、`px`                                                                                                                                                  | `14px`   |
+| text-decoration                                                                     | 文本修饰,可选值:`underline` 、`line-through`、`overline`                                                                                                                           | -        |
+| text-stroke                                                                         | 文字描边,可简写或各个值分开写,如:`text-stroke-color`, `text-stroke-width`                                                                                                              | -        |
+| text-align                                                                          | 文本水平对齐,可选值:`right` 、`center`                                                                                                                                             | `left`   |
+| display                                                                             | 框类型,可选值:`block`、`inline-block`、`flex`、`none`,当为`none`时是不渲染该段, `flex`功能简陋。                                                                                                            | -        |
+| flex                                                                                | 配合 display: flex; 属性定义了在分配多余空间,目前只用为数值如: flex: 1                                                                                                           | -        |
+| align-self                                                                          | 配合 display: flex; 单个项目垂直轴对齐方式: `flex-start` `flex-end` `center`                                                                                                         | `flex-start`        |
+| justify-content                                                                     | 配合 display: flex; 水平轴对齐方式: `flex-start` `flex-end` `center`                                                                                                         | `flex-start`        |
+| align-items                                                                         | 配合 display: flex; 垂直轴对齐方式: `flex-start` `flex-end` `center`                                                                                                  | `flex-start`        |
+| border-radius                                                                       | 圆角边框,支持`%`、`rpx`、`px`                                                                                                                                                       | -        |
+| box-sizing                                                                          | 可选值:`border-box`                                                                                                                                                                 | -        |
+| box-shadow                                                                          | 投影                                                                                                                                                                                 | -        |
+| background(color)                                                                   | 支持渐变,但必须写百分比!如:`linear-gradient(,#ff971b 0%, #ff5000 100%)`、`radial-gradient(#0ff 15%, #f0f 60%)`,目前 radial-gradient 渐变的圆心为元素中点,半径为最长边,不支持设置 | -        |
+| background-clip                                                                	  | 文字渐变,配合`background`背景渐变,设置`background-clip: text` 达到文字渐变效果 | -        |
+| background-image                                                                    | view 元素背景:`url(src)`,若只是设置背景图,请不要设置`background-repeat`                                                                                                                                                           | -        |
+| background-repeat                                                                   | 设置是否及如何重复背景纹理,可选值:`repeat`、`repeat-x`、`repeat-y`、`no-repeat`                                                                                                    | `repeat` |
+| [object-fit](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit/)          | 图片元素适应容器方式,类似于`mode`,可选值:`cover`、 `contain`、 `fill`、 `none`                                                                                                      | -        |
+| [object-position](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-position) | 图片的对齐方式,配合`object-fit`使用                                                                                                                                                 | -        |
+
+### 图片填充模式 object-fit
+
+| 名称    | 含义                                                   |
+| ------- | ------------------------------------------------------ |
+| contain | 保持宽高缩放图片,使图片的长边能完全显示出来           |
+| cover   | 保持宽高缩放图片,使图片的短边能完全显示出来,裁剪长边 |
+| fill    | 拉伸图片,使图片填满元素                               |
+| none    | 保持图片原有尺寸                                       |
+
+### 事件 Events
+
+| 事件名   | 说明                                                             | 返回值 |
+| -------- | ---------------------------------------------------------------- | ------ |
+| success  | 生成图片成功,若使用`is-canvas-to-temp-filePath` 可以接收图片地址 | path   |
+| fail     | 生成图片失败                                                     | error  |
+| done     | 绘制成功                                                         |        |
+| progress | 绘制进度                                                         | number |
+
+### 暴露函数 Expose
+| 事件名   | 说明                                                             | 返回值 |
+| -------- | ---------------------------------------------------------------- | ------ |
+| render(object)   |  渲染器,传入JSON 绘制海报 | promise   |
+| [canvasToTempFilePath](https://uniapp.dcloud.io/api/canvas/canvasToTempFilePath.html#canvastotempfilepath)(object)   | 把当前画布指定区域的内容导出生成指定大小的图片,并返回文件临时路径。    |   |
+| canvasToTempFilePathSync(object)    | 同步接口,同上                                                         |        |
+
+
+## 常见问题
+
+- 1、H5 端使用网络图片需要解决跨域问题。
+- 2、小程序使用网络图片需要去公众平台增加下载白名单!二级域名也需要配!
+- 3、H5 端生成图片是 base64,有时显示只有一半可以使用原生标签`<IMG/>`
+- 4、发生保存图片倾斜变形或提示 native buffer exceed size limit 时,使用 pixel-ratio="2"参数,降分辨率。
+- 5、h5 保存图片不需要调接口,提示用户长按图片保存。
+- 6、画板不能隐藏,包括`v-if`,`v-show`、`display:none`、`opacity:0`,另外也不要把画板放在弹窗里。如果需要隐藏画板请设置 `custom-style="position: fixed; left: 200%"`
+- 7、微信小程序真机调试请使用 **真机调试2.0**,不支持1.0。
+- 8、微信小程序打开调试时可以生但并闭无法生成时,这种情况一般是没有在公众号配置download域名
+- 9、HBX 3.4.5之前的版本不支持vue3
+- 10、在微信开发工具上 canvas 层级最高无法zindex,并不影响真机
+- 11、请不要导入非uni_modules插件
+- 12、关于QQ小程序 报 Propertyor method"toJSON"is not defined 请把基础库调到 1.50.3
+- 13、支付宝小程序 IDE 不支持 生成图片 请以真机调试结果为准
+- 14、返回值为字符串 `data:,` 大概是尺寸超过限制,设置 pixel-ratio="2"
+- 华为手机 APP 上无法生成图片,请使用 HBX2.9.11++(已过时,忽略这条)
+- IOS APP 请勿使用 HBX2.9.3.20201014 的版本!这个版本无法生成图片。(已过时,忽略这条)
+- 苹果微信 7.0.20 存在闪退和图片无法 onload 为微信 bug(已过时,忽略这条)
+- 微信小程序 IOS 旧接口 如父级设置圆角,子级也设会导致子级的失效,为旧接口BUG。
+- 微信小程序 安卓 旧接口 如使用图片必须加背景色,为旧接口BUG。
+- 微信小程序 安卓端 [图片可能在首次可以加载成功,再次加载会不触发任何事件](https://developers.weixin.qq.com/community/develop/doc/000ee2b8dacf4009337f51f4556800?highLine=canvas%25202d%2520createImage),临时解决方法是给图片加个时间戳
+## 打赏
+
+如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
+
+![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png)
+![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png)

+ 29 - 0
uni_modules/lunc-calendar/changelog.md

@@ -0,0 +1,29 @@
+## 1.0.11(2024-03-13)
+1. 更新公历、农历互转js文件(calendar.js);
+2. 新增默认日期属性date;
+## 1.0.10(2023-06-16)
+1. 修复BUG
+2. 解决nvue页面样式问题(仍有少部分问题待解决)
+3. 优化代码
+## 1.0.9(2023-06-02)
+1. 修复BUG;
+2. 修改标记(signList)可动态赋值
+## 1.0.8(2022-12-30)
+修改 readme.md 说明文件
+## 1.0.7(2022-12-30)
+1. 修改 是否显示按钮属性 shouChangeBtn ,改为 showChangeBtn;
+2. 解决在收起状态下,点击“今”不会触发 dayChange 和 monthChange 事件;
+3. 解决 不显示农历的同时也不显示标记问题,修改后可只显示标记;
+## 1.0.6(2022-12-09)
+添加属性 shrinkState ,默认显示周数据(收起)还是月数据(展开)
+## 1.0.5(2022-11-25)
+1. 解决 iOS系统下日期格式只识别"/"问题;
+2. 添加 删除标记和添加标记两个方法;删除之前的设置标记方法setSignList();
+3. 添加收起和展开状态改变事件;
+## 1.0.4(2022-11-24)
+1. 添加可收缩按钮,收缩后显示一个星期的日期,展开显示一个月的日期;
+2. 添加setSignList方法,可动态添加修改删除 标记事件;
+## 1.0.3(2022-10-25)
+添加signList的监听,可动态修改signList值
+## 1.0.0(2021-09-23)
+初次提交

+ 759 - 0
uni_modules/lunc-calendar/components/lunc-calendar/calendar.js

@@ -0,0 +1,759 @@
+var calendar = (function() {
+	'use strict';
+
+	var lunarInfo = [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
+		//1900-1909
+		0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
+		//1910-1919
+		0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
+		//1920-1929
+		0x06566, 0x0d4a0, 0x0ea50, 0x16a95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
+		//1930-1939
+		0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
+		//1940-1949
+		0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,
+		//1950-1959
+		0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
+		//1960-1969
+		0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,
+		//1970-1979
+		0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
+		//1980-1989
+		0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0,
+		//1990-1999
+		0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
+		//2000-2009
+		0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
+		//2010-2019
+		0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
+		//2020-2029
+		0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
+		//2030-2039
+		0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
+		//2040-2049
+		/**Add By JJonline@JJonline.Cn**/
+		0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,
+		//2050-2059
+		0x092e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
+		//2060-2069
+		0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
+		//2070-2079
+		0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
+		//2080-2089
+		0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
+		//2090-2099
+		0x0d520
+	]; //2100
+
+	var solarMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+
+	var Gan = ["\u7532", "\u4E59", "\u4E19", "\u4E01", "\u620A", "\u5DF1", "\u5E9A", "\u8F9B", "\u58EC", "\u7678"];
+	var Zhi = ["\u5B50", "\u4E11", "\u5BC5", "\u536F", "\u8FB0", "\u5DF3", "\u5348", "\u672A", "\u7533", "\u9149",
+		"\u620C", "\u4EA5"
+	];
+
+	var ChineseZodiac = ["\u9F20", "\u725B", "\u864E", "\u5154", "\u9F99", "\u86C7", "\u9A6C", "\u7F8A", "\u7334",
+		"\u9E21", "\u72D7", "\u732A"
+	];
+
+	var festival = {
+		'1-1': {
+			title: '元旦节'
+		},
+		'2-14': {
+			title: '情人节'
+		},
+		'5-1': {
+			title: '劳动节'
+		},
+		'5-4': {
+			title: '青年节'
+		},
+		'6-1': {
+			title: '儿童节'
+		},
+		'9-10': {
+			title: '教师节'
+		},
+		'10-1': {
+			title: '国庆节'
+		},
+		'12-25': {
+			title: '圣诞节'
+		},
+		'3-8': {
+			title: '妇女节'
+		},
+		'3-12': {
+			title: '植树节'
+		},
+		'4-1': {
+			title: '愚人节'
+		},
+		'5-12': {
+			title: '护士节'
+		},
+		'7-1': {
+			title: '建党节'
+		},
+		'8-1': {
+			title: '建军节'
+		},
+		'12-24': {
+			title: '平安夜'
+		}
+	};
+	var lFestival = {
+		'12-30': {
+			title: '除夕'
+		},
+		'1-1': {
+			title: '春节'
+		},
+		'1-15': {
+			title: '元宵节'
+		},
+		'2-2': {
+			title: '龙抬头'
+		},
+		'5-5': {
+			title: '端午节'
+		},
+		'7-7': {
+			title: '七夕节'
+		},
+		'7-15': {
+			title: '中元节'
+		},
+		'8-15': {
+			title: '中秋节'
+		},
+		'9-9': {
+			title: '重阳节'
+		},
+		'10-1': {
+			title: '寒衣节'
+		},
+		'10-15': {
+			title: '下元节'
+		},
+		'12-8': {
+			title: '腊八节'
+		},
+		'12-23': {
+			title: '北方小年'
+		},
+		'12-24': {
+			title: '南方小年'
+		}
+	};
+
+	var solarTerm = ["\u5C0F\u5BD2", "\u5927\u5BD2", "\u7ACB\u6625", "\u96E8\u6C34", "\u60CA\u86F0", "\u6625\u5206",
+		"\u6E05\u660E", "\u8C37\u96E8", "\u7ACB\u590F", "\u5C0F\u6EE1", "\u8292\u79CD", "\u590F\u81F3",
+		"\u5C0F\u6691", "\u5927\u6691", "\u7ACB\u79CB", "\u5904\u6691", "\u767D\u9732", "\u79CB\u5206",
+		"\u5BD2\u9732", "\u971C\u964D", "\u7ACB\u51AC", "\u5C0F\u96EA", "\u5927\u96EA", "\u51AC\u81F3"
+	];
+	var sTermInfo = ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+		'97bcf97c3598082c95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
+		'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
+		'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
+		'97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e',
+		'97b6b97bd19801ec95f8c965cc920f', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
+		'9778397bd197c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f',
+		'97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e',
+		'97bcf97c3598082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa',
+		'97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
+		'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
+		'97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+		'97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+		'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
+		'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
+		'97bd097bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e',
+		'97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+		'9778397bd097c36c9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722',
+		'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+		'97bd07f1487f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa',
+		'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+		'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722',
+		'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+		'97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+		'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722',
+		'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
+		'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e',
+		'97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2',
+		'9778397bd097c36c9210c9274c920e', '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722',
+		'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+		'7f0e37f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa',
+		'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+		'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+		'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+		'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+		'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+		'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722',
+		'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721',
+		'7f0e27f0e47f531b0723b0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2',
+		'977837f0e37f149b0723b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722',
+		'7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721',
+		'7f0e37f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd',
+		'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+		'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+		'7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+		'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
+		'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+		'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722',
+		'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721',
+		'7f0e27f0e47f531b0723b0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5',
+		'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722',
+		'7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722',
+		'7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd',
+		'7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
+		'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+		'7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+		'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd',
+		'7f07e7f0e47f149b0723b0787b0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
+		'7ec967f0e37f14998082b0723b06bd', '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+		'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721',
+		'7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5',
+		'7f07e7f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35',
+		'665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722',
+		'7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd',
+		'7f07e7f0e47f531b0723b0b6fb0721', '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35',
+		'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35',
+		'665f67f0e37f1489801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+		'7f0e27f1487f531b0b0bb0b6fb0722'
+	];
+
+	var nStr1 = ["\u65E5", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D",
+		"\u5341"
+	];
+	var nStr2 = ["\u521D", "\u5341", "\u5EFF", "\u5345"];
+	var nStr3 = ["\u6B63", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D", "\u5341",
+		"\u51AC", "\u814A"
+	];
+
+	/**
+	 * @1900-2100区间内的公历、农历互转
+	 * @charset UTF-8
+	 * @Author  Jea杨(JJonline@JJonline.Cn)
+	 * @Time    2014-7-21
+	 * @Time    2016-8-13 Fixed 2033hex、Attribution Annals
+	 * @Time    2016-9-25 Fixed lunar LeapMonth Param Bug
+	 * @Time    2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
+	 * @Version 1.0.3
+	 * @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
+	 * @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
+	 */
+	var calendar = {
+		/**
+		 * 农历1900-2100的润大小信息表
+		 * @Array Of Property
+		 * @return Hex
+		 */
+		lunarInfo: lunarInfo,
+		/**
+		 * 公历每个月份的天数普通表
+		 * @Array Of Property
+		 * @return Number
+		 */
+		solarMonth: solarMonth,
+		/**
+		 * 天干地支之天干速查表
+		 * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
+		 * @return Cn string
+		 */
+		Gan: Gan,
+		/**
+		 * 天干地支之地支速查表
+		 * @Array Of Property
+		 * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
+		 * @return Cn string
+		 */
+		Zhi: Zhi,
+		/**
+		 * 天干地支之地支速查表<=>生肖
+		 * @Array Of Property
+		 * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
+		 * @return Cn string
+		 */
+		Animals: ChineseZodiac,
+		/**
+		 * 阳历节日
+		 */
+		festival: festival,
+		/**
+		 * 农历节日
+		 */
+		lFestival: lFestival,
+		/**
+		 * 24节气速查表
+		 * @Array Of Property
+		 * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
+		 * @return Cn string
+		 */
+		solarTerm: solarTerm,
+		/**
+		 * 1900-2100各年的24节气日期速查表
+		 * @Array Of Property
+		 * @return 0x string For splice
+		 */
+		sTermInfo: sTermInfo,
+		/**
+		 * 数字转中文速查表
+		 * @Array Of Property
+		 * @trans ['日','一','二','三','四','五','六','七','八','九','十']
+		 * @return Cn string
+		 */
+		nStr1: nStr1,
+		/**
+		 * 日期转农历称呼速查表
+		 * @Array Of Property
+		 * @trans ['初','十','廿','卅']
+		 * @return Cn string
+		 */
+		nStr2: nStr2,
+		/**
+		 * 月份转农历称呼速查表
+		 * @Array Of Property
+		 * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
+		 * @return Cn string
+		 */
+		nStr3: nStr3,
+		/**
+		 * 返回默认定义的阳历节日
+		 */
+		getFestival: function getFestival() {
+			return this.festival;
+		},
+		/**
+		 * 返回默认定义的内容里节日
+		 */
+		getLunarFestival: function getLunarFestival() {
+			return this.lFestival;
+		},
+		/**
+		 *
+		 * @param param {Object} 按照festival的格式输入数据,设置阳历节日
+		 */
+		setFestival: function setFestival() {
+			var param = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+			this.festival = param;
+		},
+		/**
+		 *
+		 * @param param {Object} 按照lFestival的格式输入数据,设置农历节日
+		 */
+		setLunarFestival: function setLunarFestival() {
+			var param = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+			this.lFestival = param;
+		},
+		/**
+		 * 返回农历y年一整年的总天数
+		 * @param y lunar Year
+		 * @return Number
+		 * @eg:var count = calendar.lYearDays(1987) ;//count=387
+		 */
+		lYearDays: function lYearDays(y) {
+			var i,
+				sum = 348;
+			for (i = 0x8000; i > 0x8; i >>= 1) {
+				sum += this.lunarInfo[y - 1900] & i ? 1 : 0;
+			}
+			return sum + this.leapDays(y);
+		},
+		/**
+		 * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
+		 * @param y lunar Year
+		 * @return Number (0-12)
+		 * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
+		 */
+		leapMonth: function leapMonth(y) {
+			//闰字编码 \u95f0
+			return this.lunarInfo[y - 1900] & 0xf;
+		},
+		/**
+		 * 返回农历y年闰月的天数 若该年没有闰月则返回0
+		 * @param y lunar Year
+		 * @return Number (0、29、30)
+		 * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
+		 */
+		leapDays: function leapDays(y) {
+			if (this.leapMonth(y)) {
+				return this.lunarInfo[y - 1900] & 0x10000 ? 30 : 29;
+			}
+			return 0;
+		},
+		/**
+		 * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
+		 * @param y lunar Year
+		 * @param m lunar Month
+		 * @return Number (-1、29、30)
+		 * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
+		 */
+		monthDays: function monthDays(y, m) {
+			if (m > 12 || m < 1) {
+				return -1;
+			} //月份参数从1至12,参数错误返回-1
+			return this.lunarInfo[y - 1900] & 0x10000 >> m ? 30 : 29;
+		},
+		/**
+		 * 返回公历(!)y年m月的天数
+		 * @param y solar Year
+		 * @param m solar Month
+		 * @return Number (-1、28、29、30、31)
+		 * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
+		 */
+		solarDays: function solarDays(y, m) {
+			if (m > 12 || m < 1) {
+				return -1;
+			} //若参数错误 返回-1
+			var ms = m - 1;
+			if (ms === 1) {
+				//2月份的闰平规律测算后确认返回28或29
+				return y % 4 === 0 && y % 100 !== 0 || y % 400 === 0 ? 29 : 28;
+			} else {
+				return this.solarMonth[ms];
+			}
+		},
+		/**
+		 * 农历年份转换为干支纪年
+		 * @param  lYear 农历年的年份数
+		 * @return Cn string
+		 */
+		toGanZhiYear: function toGanZhiYear(lYear) {
+			var ganKey = (lYear - 3) % 10;
+			var zhiKey = (lYear - 3) % 12;
+			if (ganKey === 0) ganKey = 10; //如果余数为0则为最后一个天干
+			if (zhiKey === 0) zhiKey = 12; //如果余数为0则为最后一个地支
+			return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1];
+		},
+		/**
+		 * 公历月、日判断所属星座
+		 * @param  cMonth [description]
+		 * @param  cDay [description]
+		 * @return Cn string
+		 */
+		toAstro: function toAstro(cMonth, cDay) {
+			var s =
+				"\u6469\u7FAF\u6C34\u74F6\u53CC\u9C7C\u767D\u7F8A\u91D1\u725B\u53CC\u5B50\u5DE8\u87F9\u72EE\u5B50\u5904\u5973\u5929\u79E4\u5929\u874E\u5C04\u624B\u6469\u7FAF";
+			var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22];
+			return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + "\u5EA7"; //座
+		},
+
+		/**
+		 * 传入offset偏移量返回干支
+		 * @param offset 相对甲子的偏移量
+		 * @return Cn string
+		 */
+		toGanZhi: function toGanZhi(offset) {
+			return this.Gan[offset % 10] + this.Zhi[offset % 12];
+		},
+		/**
+		 * 传入公历(!)y年获得该年第n个节气的公历日期
+		 * @param y y公历年(1900-2100)
+		 * @param n n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
+		 * @return day Number
+		 * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
+		 */
+		getTerm: function getTerm(y, n) {
+			if (y < 1900 || y > 2100 || n < 1 || n > 24) {
+				return -1;
+			}
+			var _table = this.sTermInfo[y - 1900];
+			var _calcDay = [];
+			for (var index = 0; index < _table.length; index += 5) {
+				var chunk = parseInt('0x' + _table.substr(index, 5)).toString();
+				_calcDay.push(chunk[0], chunk.substr(1, 2), chunk[3], chunk.substr(4, 2));
+			}
+			return parseInt(_calcDay[n - 1]);
+		},
+		/**
+		 * 传入农历数字月份返回汉语通俗表示法
+		 * @param m lunar month
+		 * @return Cn string
+		 * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
+		 */
+		toChinaMonth: function toChinaMonth(m) {
+			// 月 => \u6708
+			if (m > 12 || m < 1) {
+				return -1;
+			} //若参数错误 返回-1
+			var s = this.nStr3[m - 1];
+			s += "\u6708"; //加上月字
+			return s;
+		},
+		/**
+		 * 传入农历日期数字返回汉字表示法
+		 * @param d lunar day
+		 * @return Cn string
+		 * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
+		 */
+		toChinaDay: function toChinaDay(d) {
+			//日 => \u65e5
+			var s;
+			switch (d) {
+				case 10:
+					s = "\u521D\u5341";
+					break;
+				case 20:
+					s = "\u4E8C\u5341";
+					break;
+				case 30:
+					s = "\u4E09\u5341";
+					break;
+				default:
+					s = this.nStr2[Math.floor(d / 10)];
+					s += this.nStr1[d % 10];
+			}
+			return s;
+		},
+		/**
+		 * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
+		 * @param y year
+		 * @return Cn string
+		 * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
+		 */
+		getAnimal: function getAnimal(y) {
+			return this.Animals[(y - 4) % 12];
+		},
+		/**
+		 * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
+		 * !important! 公历参数区间1900.1.31~2100.12.31
+		 * @param yPara  solar year
+		 * @param mPara  solar month
+		 * @param dPara  solar day
+		 * @return JSON object
+		 * @eg:console.log(calendar.solar2lunar(1987,11,01));
+		 */
+		solar2lunar: function solar2lunar(yPara, mPara, dPara) {
+			var y = parseInt(yPara);
+			var m = parseInt(mPara);
+			var d = parseInt(dPara);
+			//年份限定、上限
+			if (y < 1900 || y > 2100) {
+				return -1; // undefined转换为数字变为NaN
+			}
+			//公历传参最下限
+			if (y === 1900 && m === 1 && d < 31) {
+				return -1;
+			}
+
+			//未传参  获得当天
+			var objDate;
+			if (!y) {
+				objDate = new Date();
+			} else {
+				objDate = new Date(y, parseInt(m) - 1, d);
+			}
+			var i,
+				leap = 0,
+				temp = 0;
+			//修正ymd参数
+			y = objDate.getFullYear();
+			m = objDate.getMonth() + 1;
+			d = objDate.getDate();
+			var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0,
+				31)) / 86400000;
+			for (i = 1900; i < 2101 && offset > 0; i++) {
+				temp = this.lYearDays(i);
+				offset -= temp;
+			}
+			if (offset < 0) {
+				offset += temp;
+				i--;
+			}
+
+			//是否今天
+			var isTodayObj = new Date(),
+				isToday = false;
+			if (isTodayObj.getFullYear() === y && isTodayObj.getMonth() + 1 === m && isTodayObj.getDate() === d) {
+				isToday = true;
+			}
+			//星期几
+			var nWeek = objDate.getDay(),
+				cWeek = this.nStr1[nWeek];
+			//数字表示周几顺应天朝周一开始的惯例
+			if (nWeek === 0) {
+				nWeek = 7;
+			}
+			//农历年
+			var year = i;
+			leap = this.leapMonth(i); //闰哪个月
+			var isLeap = false;
+
+			//效验闰月
+			for (i = 1; i < 13 && offset > 0; i++) {
+				//闰月
+				if (leap > 0 && i === leap + 1 && isLeap === false) {
+					--i;
+					isLeap = true;
+					temp = this.leapDays(year); //计算农历闰月天数
+				} else {
+					temp = this.monthDays(year, i); //计算农历普通月天数
+				}
+				//解除闰月
+				if (isLeap === true && i === leap + 1) {
+					isLeap = false;
+				}
+				offset -= temp;
+			}
+			// 闰月导致数组下标重叠取反
+			if (offset === 0 && leap > 0 && i === leap + 1) {
+				if (isLeap) {
+					isLeap = false;
+				} else {
+					isLeap = true;
+					--i;
+				}
+			}
+			if (offset < 0) {
+				offset += temp;
+				--i;
+			}
+			//农历月
+			var month = i;
+			//农历日
+			var day = offset + 1;
+			//天干地支处理
+			var sm = m - 1;
+			var gzY = this.toGanZhiYear(year);
+
+			// 当月的两个节气
+			// bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
+			var firstNode = this.getTerm(y, m * 2 - 1); //返回当月「节」为几日开始
+			var secondNode = this.getTerm(y, m * 2); //返回当月「节」为几日开始
+
+			// 依据12节气修正干支月
+			var gzM = this.toGanZhi((y - 1900) * 12 + m + 11);
+			if (d >= firstNode) {
+				gzM = this.toGanZhi((y - 1900) * 12 + m + 12);
+			}
+
+			//传入的日期的节气与否
+			var isTerm = false;
+			var Term = null;
+			if (firstNode === d) {
+				isTerm = true;
+				Term = this.solarTerm[m * 2 - 2];
+			}
+			if (secondNode === d) {
+				isTerm = true;
+				Term = this.solarTerm[m * 2 - 1];
+			}
+			//日柱 当月一日与 1900/1/1 相差天数
+			var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10;
+			var gzD = this.toGanZhi(dayCyclical + d - 1);
+			//该日期所属的星座
+			var astro = this.toAstro(m, d);
+			var solarDate = y + '-' + m + '-' + d;
+			var lunarDate = year + '-' + month + '-' + day;
+			var festival = this.festival;
+			var lFestival = this.lFestival;
+			var festivalDate = m + '-' + d;
+			var lunarFestivalDate = month + '-' + day;
+
+			// bugfix https://github.com/jjonline/calendar.js/issues/29
+			// 农历节日修正:农历12月小月则29号除夕,大月则30号除夕
+			// 此处取巧修正:当前为农历12月29号时增加一次判断并且把lunarFestivalDate设置为12-30以正确取得除夕
+			// 天朝农历节日遇闰月过前不过后的原则,此处取农历12月天数不考虑闰月
+			// 农历润12月在本工具支持的200年区间内仅1574年出现
+			if (month === 12 && day === 29 && this.monthDays(year, month) === 29) {
+				lunarFestivalDate = '12-30';
+			}
+			return {
+				date: solarDate,
+				lunarDate: lunarDate,
+				festival: festival[festivalDate] ? festival[festivalDate].title : null,
+				lunarFestival: lFestival[lunarFestivalDate] ? lFestival[lunarFestivalDate].title : null,
+				'lYear': year,
+				'lMonth': month,
+				'lDay': day,
+				'Animal': this.getAnimal(year),
+				'IMonthCn': (isLeap ? "\u95F0" : '') + this.toChinaMonth(month),
+				'IDayCn': this.toChinaDay(day),
+				'cYear': y,
+				'cMonth': m,
+				'cDay': d,
+				'gzYear': gzY,
+				'gzMonth': gzM,
+				'gzDay': gzD,
+				'isToday': isToday,
+				'isLeap': isLeap,
+				'nWeek': nWeek,
+				'ncWeek': "\u661F\u671F" + cWeek,
+				'isTerm': isTerm,
+				'Term': Term,
+				'astro': astro
+			};
+		},
+		/**
+		 * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
+		 * !important! 参数区间1900.1.31~2100.12.1
+		 * @param y  lunar year
+		 * @param m  lunar month
+		 * @param d  lunar day
+		 * @param isLeapMonth  lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
+		 * @return JSON object
+		 * @eg:console.log(calendar.lunar2solar(1987,9,10));
+		 */
+		lunar2solar: function lunar2solar(y, m, d, isLeapMonth) {
+			y = parseInt(y);
+			m = parseInt(m);
+			d = parseInt(d);
+			isLeapMonth = !!isLeapMonth;
+			var leapMonth = this.leapMonth(y);
+			this.leapDays(y);
+			if (isLeapMonth && leapMonth !== m) {
+				return -1;
+			} //传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
+			if (y === 2100 && m === 12 && d > 1 || y === 1900 && m === 1 && d < 31) {
+				return -1;
+			} //超出了最大极限值
+			var day = this.monthDays(y, m);
+			var _day = day;
+			//bugFix 2016-9-25
+			//if month is leap, _day use leapDays method
+			if (isLeapMonth) {
+				_day = this.leapDays(y, m);
+			}
+			if (y < 1900 || y > 2100 || d > _day) {
+				return -1;
+			} //参数合法性效验
+
+			//计算农历的时间差
+			var offset = 0;
+			var i;
+			for (i = 1900; i < y; i++) {
+				offset += this.lYearDays(i);
+			}
+			var leap = 0,
+				isAdd = false;
+			for (i = 1; i < m; i++) {
+				leap = this.leapMonth(y);
+				if (!isAdd) {
+					//处理闰月
+					if (leap <= i && leap > 0) {
+						offset += this.leapDays(y);
+						isAdd = true;
+					}
+				}
+				offset += this.monthDays(y, i);
+			}
+			//转换闰月农历 需补充该年闰月的前一个月的时差
+			if (isLeapMonth) {
+				offset += day;
+			}
+			//1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
+			var strap = Date.UTC(1900, 1, 30, 0, 0, 0);
+			var calObj = new Date((offset + d - 31) * 86400000 + strap);
+			var cY = calObj.getUTCFullYear();
+			var cM = calObj.getUTCMonth() + 1;
+			var cD = calObj.getUTCDate();
+			return this.solar2lunar(cY, cM, cD);
+		}
+	};
+
+	return calendar;
+
+})();
+//# sourceMappingURL=js-calendar-converter.js.map
+export default calendar;

+ 873 - 0
uni_modules/lunc-calendar/components/lunc-calendar/lunc-calendar.vue

@@ -0,0 +1,873 @@
+<template>
+	<view class="lunc-calendar">
+		<!-- 头部上下月按钮及月份 -->
+		<view class="header">
+			<text class="head-icon head-pre-month" v-if="showChangeBtn" @click="changeMonthOrWeek('prev')"></text>
+			<text class="head-month">{{ selDate.year + '年' + (selDate.month < 10 ? '0' + selDate.month : selDate.month)
+				+ '月' }}</text>
+					<text class="head-icon head-next-month" v-if="showChangeBtn" @click="changeMonthOrWeek('next')"></text>
+					<text class="go-to-today" v-if="showToday" @click="goToday">今</text>
+		</view>
+		<!-- 星期 -->
+		<view class="week-area">
+			<text class="week-font" v-for="(item, index) in weekArr" :key="index">{{ getWeekType + '' + item }}</text>
+		</view>
+		<!-- 日历 -->
+		<swiper class="calendar-data" :current="shrinkType ? tranCurrent : tranIndex" circular :duration="tranDuration"
+			@change="swiperChange" @animationfinish="swiperEndChange" :style="{ height: shrinkType ? '56px' : '266px' }">
+			<swiper-item class="swiper-item swiper-prev-item" v-for="(a, i) in getAllData" :key="i">
+				<text class="month-bg" v-if="showMonthBg">{{ getMontBg }}</text>
+				<view class="month-days" :class="[shrinkType ? 'item-week' : '']">
+					<view class="week-days" v-for="(b, j) in a" :key="j">
+						<view class="day" v-for="(c, k) in b" :key="k" @click="clickDay(c)">
+							<view class="day-info"
+								:class="[c.dayClass, getIsSelDay(c) && 'is-sel', c.dayType != 'normal' && 'un-month']">
+								<text class="day-solar">{{ c.day }}</text>
+								<template v-if="showLunar || c.sign && c.sign.length > 0">
+									<text class="day-tag" v-if="c.sign && c.sign.length > 0"></text>
+									<text class="day-sign" v-if="c.sign && c.sign.length > 0">{{ c.sign[0].title }}</text>
+									<text class="day-lunar" v-else>{{ c.dayLunar }}</text>
+								</template>
+							</view>
+						</view>
+					</view>
+				</view>
+			</swiper-item>
+		</swiper>
+		<!-- 收缩按钮 -->
+		<view class="shrink" v-if="showShrink" @click="changeShrink">
+			<text class="shrink-btn" :class="[shrinkType ? 'shrink-open' : 'shrink-close']"></text>
+		</view>
+	</view>
+</template>
+<script>
+import calendar from './calendar.js';
+/**
+ * @property {String, Date} date 默认日期,默认new Date()
+ * @property {Boolean} showLunar = [true|false] 是否显示农历,默认false
+ * @property {Boolean} showMonthBg = [true|false] 是否显示月份背景,默认true
+ * @property {Boolean} showChangeBtn = [true|false] 是否显示上月下月箭头按钮,默认true
+ * @property {String} firstDayOfWeek = [monday|sunday] 周几为每周的第一天,默认monday
+ *  @value monday 每周从周一开始(默认)
+ *  @value sunday 每周从周日开始
+ * @property {String} weekType = [''|周|星期] 星期的前缀;如周一周二或星期一星期二,为空则只显示一、二等;不用预设值时可自定义前缀,填的值即为星期前缀;默认周
+ *  @value '' 星期显示:只显示一、二等
+ *  @value 周 星期显示:周一、周二等(默认)
+ *  @value 星期 星期显示:星期一、星期二等
+ * @property {Boolean} weekend = [true|false] 周末标红(周六周日日期用红色字体),默认true
+ * @property {Boolean} showShrink = [true|false] 是否显示收缩按钮,可显示一周的日期,默认false
+ * @property {String} shrinkState = [week|month] 收缩状态,默认month
+ *  @value week 默认打开显示周数据(收起状态)
+ *  @value month 默认打开显示月数据(展开状态)
+ * @property {Array} signList 标记数组,若当前有多个标记,则显示最后一个,期待格式[{date: '2021-09-10', title: '生日', info: '八月初四张三生日'}]
+ * @event {Function()} dayChange 点击日期触发事件,返回参数e={year,month,day,week,date,lunar,daySign},详情参数见文档
+ * @event {Function()} monthChange 切换月份触发事件,返回参数e={year,month,type},详情参数见文档
+ * @event {Function()} shrinkClick 收缩和展开时触发事件,返回参数e=week|month
+ */
+export default {
+	name: 'LuncCalendar',
+	props: {
+		//默认日期
+		date: {
+			type: [String, Date],
+			default: () => {
+				return new Date()
+			}
+		},
+		//是否显示农历
+		showLunar: {
+			type: Boolean,
+			default: false
+		},
+		//是否显示月份背景
+		showMonthBg: {
+			type: Boolean,
+			default: true
+		},
+		//是否显示上月下月按钮
+		showChangeBtn: {
+			type: Boolean,
+			default: true
+		},
+		//每周的周几为第一天
+		firstDayOfWeek: {
+			type: String,
+			default: 'monday'
+		},
+		//每周的周几为第一天
+		weekType: {
+			type: String,
+			default: '周'
+		},
+		//周末标红
+		weekend: {
+			type: Boolean,
+			default: true
+		},
+		//是否可收缩,收起来后以周显示
+		showShrink: {
+			type: Boolean,
+			default: false
+		},
+		// 默认打开状态(收起或展开)
+		shrinkState: {
+			type: String,
+			default: 'month'
+		},
+		//标记
+		signList: {
+			type: Array,
+			default() {
+				return []
+			}
+		}
+	},
+	data() {
+		return {
+			weekArr: ['一', '二', '三', '四', '五', '六', '日'], //星期数组
+			defaultDate: {},
+			today: {}, //今天日期 -> year, month, day
+			selDate: {}, //选中日期信息 -> year, month, day
+
+			allMonthList: [], // 月份数据 -> [[[周],[周]],[月],[月]]
+			tranIndex: 1, // 月份轮播所在位置
+			allWeekList: [], // 周月份数据 -> [[[周]],[月],[月]]
+			tranCurrent: 1, // 周轮播所在位置
+			tranDuration: 300, //轮播时间(单位毫秒)
+
+			signArr: this.signList, // 标记列表
+			showToday: false, //显示回到今天(非当月才显示)
+			shrinkType: false, // 收缩状态,true:收起(显示周),false展开(显示月)
+			deterChange: true, // 防止切换月份过快
+		}
+	},
+	created() {
+		let dnd = new Date(this.date);
+		this.defaultDate = {
+			year: dnd.getFullYear(),
+			month: dnd.getMonth() + 1,
+			day: dnd.getDate()
+		}
+		let nd = new Date();
+		this.today = {
+			year: nd.getFullYear(),
+			month: nd.getMonth() + 1,
+			day: nd.getDate()
+		}
+		if (this.firstDayOfWeek == "sunday") this.weekArr = ['日', '一', '二', '三', '四', '五', '六'];
+		this.initDate();
+	},
+	watch: {
+		signList(nList, oList) {
+			this.signArr = nList;
+			this.setSignList();
+		}
+	},
+	computed: {
+		getAllData() { // 切换周或月时,展示的数据不同
+			return this.shrinkType ? this.allWeekList : this.allMonthList;
+		},
+		getMontBg() { // 获取当前月背景
+			let monthBg = this.selDate.month;
+			return !this.shrinkType ? (monthBg < 10 ? '0' + monthBg : monthBg) : '';
+		},
+		getIsSelDay() { // 判断是否是选中日期
+			return (d) => {
+				let { year, month, day } = this.selDate;
+				return year == d.year && month == d.month && day == d.day
+			}
+		},
+		getWeekType() {
+			let weekType = this.weekType;
+			if (weekType && weekType != "true" && weekType != "''" && weekType != '""') {
+				return weekType
+			} else {
+				return ''
+			}
+		}
+	},
+	methods: {
+		initDate() { // 初始化日期
+			this.selDate = JSON.parse(JSON.stringify(this.defaultDate));
+			let monthList = this.getMonthData(this.selDate); // 获取当月数据
+			let prevMonthList = this.getMonthData(this.getMonthDate(this.selDate, -1)); // 上月数据
+			let nextMonthList = this.getMonthData(this.getMonthDate(this.selDate)); // 下月数据
+			this.allMonthList = [prevMonthList, monthList, nextMonthList]
+			this.tranIndex = 1;
+			if (this.shrinkState == "week" && !this.shrinkType) this.changeShrink();
+		},
+		/**
+		 * 根据指定日期获取当月的数据
+		 * @param {Object} date = { year, month, day } 指定的日期
+		 */
+		getMonthData(date) {
+			const { year, month, day } = date; //指定的日期
+			let maxDay = new Date(year, month, 0).getDate(); //当前月最大日期
+			let firstWeek = new Date(year + "/" + month + "/1").getDay(); //月份1号的星期数
+			if (this.firstDayOfWeek == "monday") firstWeek = firstWeek - 1 < 0 ? 6 : firstWeek - 1;
+			let list = [];
+			//每月显示42天,6周,每周7天
+			for (var i = 0; i < 42; i++) {
+				let dayInfo = {}; // 每天的详细信息
+				if (i < firstWeek) { //指定月份上月的最后几天
+					let { year, month } = this.getMonthDate(date, -1);
+					let preMaxDay = new Date(year, month, 0).getDate(); //上月最大日期
+					let day = preMaxDay - firstWeek + i + 1;
+					dayInfo = this.getDayInfo({ year, month, day }, 'prev');
+				} else if (i > maxDay + firstWeek - 1) { //指定月份下月的前几天
+					let { year, month } = this.getMonthDate(date);
+					let day = i - maxDay - firstWeek + 1;
+					dayInfo = this.getDayInfo({ year, month, day }, 'next');
+				} else {
+					let day = i - firstWeek + 1;
+					dayInfo = this.getDayInfo({ year, month, day }, 'normal');
+				}
+				if (i % 7 == 0) list.push(new Array());
+				list[list.length - 1].push(dayInfo);
+			}
+			return list;
+		},
+		/**
+		 * 获取指定日期的详细信息,包括农历节假日等
+		 * @param {Object} date = { year, month, day } 指定的日期
+		 * @param {String} dayType = [prev|next|normal] 日期类型,上月|下月|当前月
+		 */
+		getDayInfo(date, dayType) {
+			const { year, month, day } = date;
+			let isToday = false; //是否今天
+			if (year == this.today.year && month == this.today.month && day == this.today.day) isToday = true;
+			let week = new Date(year + "/" + month + "/" + day).getDay(); //星期数
+			let lunar = calendar.solar2lunar(year, month, day); //农历
+			let dayLunar = lunar.IDayCn == '初一' ? lunar.IMonthCn + lunar.IDayCn : lunar.IDayCn;
+			if (lunar.festival) dayLunar = lunar.festival; // 阳历节日
+			else if (lunar.lunarFestival) dayLunar = lunar.lunarFestival; // 农历节日
+			else if (lunar.Term) dayLunar = lunar.Term; // 节气
+			let holidayArr = ["元旦", "春节", "清明节", "劳动节", "端午节", "中秋节", "国庆节"];
+			let isHoliday = false;
+			if (holidayArr.indexOf(dayLunar) != -1) isHoliday = true;
+			let dayInfo = {
+				date: year + "-" + month + "-" + day,
+				year, month, day, week,
+				lunar, // 农历
+				dayLunar, // 显示的农历
+				isToday, // 是否是今日
+				isHoliday, // 是否是节假日
+				dayType, // 日期类型,上月、下月或当前月
+				sign: this.getSignByDate(date)
+			}
+			let dayClass = this.getDayClass(dayInfo);
+			dayInfo["dayClass"] = dayClass;
+			return dayInfo;
+		},
+		/**
+		 * 根据日期详细信息添加对应的class
+		 * @param {Object} dayInfo 日期详情
+		 */
+		getDayClass(dayInfo) {
+			let dClass = "";
+			if (dayInfo.isToday) dClass += ' is-today'; // 今天日期
+			if (dayInfo.isHoliday) dClass += ' is-holiday'; // 法定假日
+			if (this.weekend && (dayInfo.week == 0 || dayInfo.week == 6)) dClass += ' week-end'; // 周末标红
+			return dClass;
+		},
+		/**
+		 * 根据日期获取日期对应的事件
+		 * @param {Object} date = { year, month, day } 指定的日期
+		 */
+		getSignByDate(date) {
+			const { year, month, day } = date;
+			let dayDateS = new Date(year + "/" + month + "/" + day + " 00:00:00").getTime();
+			let dayDateE = new Date(year + "/" + month + "/" + day + " 23:59:59").getTime();
+			let daySign = [];
+			this.signArr.map(sign => {
+				let signDate = sign.date.replace(/-/g, '/');
+				let signTimes = new Date(sign.date).getTime();
+				if (signTimes >= dayDateS && signTimes <= dayDateE) daySign.push(sign)
+			})
+			return daySign;
+		},
+
+		/**
+		 * 获取月份数据
+		 * @param {String} type=[pre|next]
+		 */
+		getOtherData(type) {
+			let nowMont = this.getMonthDate(this.selDate, type == 'prev' ? -1 : 1); // 获取当前月份
+			this.selDate = nowMont; // 切换月份后设置选中的日期
+			let monthData = this.getMonthData(this.getMonthDate(nowMont, type == 'prev' ? -1 : 1));
+			// 获取上月或下月轮播所在位置
+			let current = this.getTranIndex().prevNum;
+			if (type == "next") current = this.getTranIndex().nextNum;
+			this.allMonthList.splice(current, 1, monthData);
+			this.judgeShowToday();
+			this.returnMonthChange(type);
+		},
+		/**
+		 * 获取周数据
+		 * @param {String} type=[pre|next]
+		 */
+		getOtherWeekData(type) {
+			let oldSel = this.selDate; // 原选中的日期
+			let newSel = this.getDateByDateAndDay(oldSel, type == 'prev' ? -7 : 7); // 获取7天前或后的日期
+			if (oldSel.month != newSel.month) { // 跨月,先设置跨月后的月历
+				// 设置月轮播位置
+				let current = this.getTranIndex("month").prevNum;
+				if (type == "next") current = this.getTranIndex("month").nextNum;
+				this.tranIndex = current;
+				this.getOtherData(type);
+			}
+			this.selDate = newSel;
+			this.getWeekData(type);
+			this.judgeShowToday();
+		},
+
+		// 从月历中获取周数据,切换周后获取上周或下周数据
+		getWeekData(type) {
+			const { prevNum: prevIndex, nowNum: nowIndex, nextNum: nextIndex } = this.getTranIndex("month");
+			const { prevNum: prevCurrent, nowNum: nowCurrent, nextNum: nextCurrent } = this.getTranIndex("week");
+			const { year: selYear, month: selMonth, day: selDay } = this.selDate;
+			let sDate = selYear + "-" + selMonth + "-" + selDay
+			let prevMonthList = this.allMonthList[prevIndex];
+			let nowMonthList = this.allMonthList[nowIndex];
+			let nextMonthList = this.allMonthList[nextIndex];
+			for (let i = 0; i < nowMonthList.length; i++) {
+				for (let j = 0; j < nowMonthList[i].length; j++) {
+					if (sDate == nowMonthList[i][j].date) {
+						this.returnDayChange(nowMonthList[i][j]); // 返回选中的日期
+						if (type == "next") {
+							this.allWeekList.splice(nextCurrent, 1, [nowMonthList[i + 1]]);
+							if (i == 5) this.allWeekList.splice(nextCurrent, 1, [nextMonthList[1]]);
+						} else {
+							this.allWeekList.splice(prevCurrent, 1, [nowMonthList[i - 1]]);
+							if (i == 0) {
+								for (let k = prevMonthList.length - 1; k >= 0; k--) {
+									if (prevMonthList[k][6].dayType == "normal") {
+										this.allWeekList.splice(prevCurrent, 1, [prevMonthList[k]]);
+										break;
+									}
+								}
+							}
+						}
+						break;
+					}
+				}
+			}
+		},
+		// 根据月份数据获取周数据,相当初始化周数据
+		getAllWeekData() {
+			const { prevNum, nowNum, nextNum } = this.getTranIndex("month");
+			const { year: selYear, month: selMonth, day: selDay } = this.selDate;
+			let sDate = selYear + "-" + selMonth + "-" + selDay; // 选中的日期
+			let allWeekList = [[], [], []];
+			let prevMonthList = this.allMonthList[prevNum];
+			let nowMonthList = this.allMonthList[nowNum];
+			let nextMonthList = this.allMonthList[nextNum];
+			for (let i = 0; i < nowMonthList.length; i++) {
+				for (let j = 0; j < nowMonthList[i].length; j++) {
+					if (sDate == nowMonthList[i][j].date) {
+						allWeekList[0][0] = nowMonthList[i - 1];
+						allWeekList[1][0] = nowMonthList[i];
+						allWeekList[2][0] = nowMonthList[i + 1];
+						if (i == 5) {
+							allWeekList[2][0] = nextMonthList[1];
+						} else if (i == 0) {
+							for (let k = prevMonthList.length - 1; k >= 0; k--) {
+								if (prevMonthList[k][6].dayType == "normal") {
+									allWeekList[0][0] = prevMonthList[k];
+									break;
+								}
+							}
+						}
+						break;
+					}
+				}
+			}
+			this.allWeekList = allWeekList;
+		},
+		// 滑动切换结束
+		swiperEndChange() {
+			this.tranDuration = 300;
+		},
+		// 滑动切换月份或周
+		swiperChange(e) {
+			let current = e.detail.current;
+			let oldIndex = this.shrinkType ? this.tranCurrent : this.tranIndex;
+			let type = (oldIndex - current == -1 || oldIndex - current == 2) ? 'next' : 'prev';
+			if (this.shrinkType) {
+				this.tranCurrent = current;
+				if (current != oldIndex) this.getOtherWeekData(type);
+			} else {
+				this.tranIndex = current;
+				if (current != oldIndex) this.getOtherData(type);
+			}
+		},
+		// 点击切换月份或周(上月下月切换或上周下周切换)type = [prev|next] 切换类型
+		changeMonthOrWeek(type) {
+			if (!this.deterChange) return;
+			this.deterChange = false;
+			setTimeout(_ => {
+				this.deterChange = true;
+			}, 400); // 防止点击过快
+			this.tranDuration = 300;
+			let tranType = this.shrinkType ? 'week' : 'month';
+			let current = this.getTranIndex(tranType).prevNum;
+			if (type == "next") current = this.getTranIndex(tranType).nextNum;
+			if (tranType == "week") {
+				this.tranCurrent = current;
+				this.getOtherWeekData(type);
+			} else {
+				this.tranIndex = current;
+				this.getOtherData(type);
+			}
+		},
+		// 点击收缩按钮,切换显示月份或显示周
+		changeShrink() {
+			this.shrinkType = !this.shrinkType;
+			if (this.tranDuration != 0) this.tranDuration = 0;
+			if (this.shrinkType) {
+				this.tranCurrent = 1;
+				this.getAllWeekData();
+			}
+			this.returnShrinkChange();
+			this.judgeShowToday();
+		},
+		// 点击回到今天
+		goToday() {
+			if (this.tranDuration != 0) this.tranDuration = 0;
+			let oldDate = JSON.parse(JSON.stringify(this.selDate));
+			this.initDate();
+			if (this.shrinkType) {
+				this.tranCurrent = 1;
+				this.getAllWeekData();
+				let today = this.today;
+				// 判断是否需要触发改变月份事件
+				if (oldDate.year != today.year || oldDate.month != today.month) {
+					this.returnMonthChange("today");
+				} else {
+					this.returnDayChange(this.today);
+				}
+			} else {
+				this.returnMonthChange("today"); // 事件
+			}
+			this.judgeShowToday();
+		},
+		// 点击日期
+		clickDay(dayInfo) {
+			let { year, month, day } = this.selDate;
+			if (day == dayInfo.day && month == dayInfo.month && year == dayInfo.year) return;
+			let oldSel = JSON.parse(JSON.stringify(this.selDate));
+			this.selDate.day = dayInfo.day;
+			if (oldSel.month != dayInfo.month) {
+				if (!this.shrinkType) {
+					this.changeMonthOrWeek(dayInfo.dayType);
+					return;
+				} else {
+					this.selDate.year = dayInfo.year;
+					this.selDate.month = dayInfo.month;
+					let nowSel = JSON.parse(JSON.stringify(this.selDate));
+					let type = "nowNum"
+					if (nowSel.year > oldSel.year || (nowSel.year === oldSel.year && nowSel.month > oldSel.month)) {
+						type = "nextNum"
+					} else if (nowSel.year < oldSel.year || (nowSel.year === oldSel.year && nowSel.month < oldSel.month)) {
+						type = "prevNum"
+					}
+					this.tranIndex = this.getTranIndex("month")[type];
+					let monthData = this.getMonthData(this.getMonthDate(this.selDate, type == 'prevNum' ? -1 : 1));
+					let current = this.getTranIndex("month")[type];
+					this.allMonthList.splice(current, 1, monthData); // 设置上月或下月数据
+				}
+				this.returnMonthChange(dayInfo.dayType);
+			} else {
+				this.returnDayChange(dayInfo);
+			}
+		},
+		// 判断是否需要显示回到今天(非本月或本周时显示)
+		judgeShowToday() {
+			const { year, month, day } = this.today;
+			const { year: selYeat, month: selMonth, day: selDay } = this.selDate;
+			if (this.shrinkType) { // 显示的周
+				let selTimes = new Date(selYeat, selMonth - 1, selDay).getTime(); // 选中日期的时间戳
+				let week = new Date(year, month - 1, day).getDay(); // 今天星期
+				let firstWD = this.getDateByDateAndDay(this.today, -week + (this.firstDayOfWeek == "monday" ? 1 : 0));
+				let lastWD = this.getDateByDateAndDay(this.today, 6 - week + (this.firstDayOfWeek == "monday" ? 1 : 0));
+				let firstTimes = new Date(firstWD.year, firstWD.month - 1, firstWD.day).getTime();
+				let lastTimes = new Date(lastWD.year, lastWD.month - 1, lastWD.day).getTime();
+				if (selTimes > lastTimes || selTimes < firstTimes) this.showToday = true;
+				else this.showToday = false;
+			} else {
+				if (year != selYeat || month != selMonth) this.showToday = true;
+				else this.showToday = false;
+			}
+		},
+
+		// 重新设置标记
+		setSignList() {
+			this.allMonthList.map(month => {
+				month.map(week => {
+					week.map(day => {
+						day.sign = this.getSignByDate({ year: day.year, month: day.month, day: day.day })
+					})
+				})
+			})
+		},
+		/**
+		 * 添加标记
+		 * @param {Array} list 需要添加的标记
+		 */
+		addSignList(list) {
+			let signArr = this.signArr.concat(list);
+			this.signArr = signArr;
+			this.setSignList();
+		},
+		/**
+		 * 删除标记
+		 * 根据date和title共同判断是否删除
+		 * @param {Array} list 需要删除的标记
+		 */
+		deleteSignList(list) {
+			let signArr = this.signArr;
+			signArr = signArr.filter(s => {
+				if (list.find(l => l.date == s.date && l.title == s.title)) return false
+				else return true;
+			})
+			this.signArr = signArr;
+			this.setSignList();
+		},
+		/**
+		 * 事件 - 设置返回日期
+		 * @param {Object} dayInfo 日期详情
+		 */
+		returnDayChange(dayInfo) {
+			let { year, month, day } = dayInfo;
+			let dayDate = year + "-" + (month < 10 ? '0' + month : month) + "-" + (day < 10 ? '0' + day : day)
+			let returnData = {
+				date: dayDate,
+				year: year,
+				month: month,
+				day: day,
+				week: dayInfo.week,
+				daySign: dayInfo.sign
+			}
+			if (this.showLunar) returnData["lunar"] = dayInfo.lunar;
+			this.$emit("dayChange", returnData);
+		},
+		/**
+		 * 事件 - 设置返回月份
+		 * @param {String} type 类型
+		 */
+		returnMonthChange(type) {
+			let selDate = this.selDate.year + "-" + this.selDate.month + "-" + this.selDate.day;
+			let monthList = this.allMonthList.flat().flat(); // 二维转一维
+			let dayInfo = monthList.find(day => day.date == selDate);
+			this.returnDayChange(dayInfo)
+			this.$emit("monthChange", {
+				year: dayInfo.year,
+				month: dayInfo.month,
+				type: type
+			});
+		},
+		/**
+		 * 事件 - 返回收缩状态
+		 */
+		returnShrinkChange() {
+			let type = this.shrinkType ? 'week' : 'month'
+			this.$emit("shrinkClick", type);
+		},
+		/**
+		 * 获取上一个或下一个轮播位置
+		 * @param {String} type = [month|week] 轮播类型,月轮播(tranIndex),周轮播(tranCurrent)
+		 * @returns {Object} {prevNum, nowNum, nextNum}
+		 */
+		getTranIndex(type = 'month') {
+			let current = this.tranIndex;
+			if (type == "week") current = this.tranCurrent;
+			let prevNum = current - 1 < 0 ? 2 : current - 1;
+			let nowNum = current;
+			let nextNum = current + 1 > 2 ? 0 : current + 1;
+			return { prevNum, nowNum, nextNum }
+		},
+		/**
+		 * 根据日期获取几天后的日期
+		 * @param {Object} date = {year, month, day} 当前日期
+		 * @param {Number} day 当前日期的几天前或几天后(负数)
+		 * @returns {Object} {year, month, day}
+		 */
+		getDateByDateAndDay(date, num) {
+			let dTime = new Date(date.year + "/" + date.month + "/" + date.day).getTime() + num * 24 * 60 * 60 * 1000;
+			let nd = new Date(dTime);
+			return {
+				year: nd.getFullYear(),
+				month: nd.getMonth() + 1,
+				day: nd.getDate()
+			}
+		},
+		/**
+		 * 获取几个月前或后的日期
+		 * @param {Object} date = {year, month, day} 当前日期
+		 * @param {Number} num 当前日期的num月前或后,默认1月后(下月)
+		 * @returns {Object} {year, month, day}
+		 */
+		getMonthDate(date, num = 1) {
+			let nextMonth = date.month + num;
+			let diffYear = parseInt(Math.abs(nextMonth) / 12);
+			let year = date.year;
+			let month = nextMonth;
+			if (nextMonth > 12) {
+				year = date.year + diffYear;
+				month = nextMonth % 12;
+			} else if (nextMonth < 1) {
+				year = date.year - (diffYear + 1);
+				month = nextMonth + 12 * (diffYear + 1);
+			}
+			let monthMaxDay = new Date(year, month, 0).getDate(); // 月份最大日期
+			let day = date.day > monthMaxDay ? monthMaxDay : date.day;
+			return { year, month, day }
+		},
+	}
+}
+</script>
+<style lang="scss">
+.lunc-calendar {
+	background-color: #FFF;
+
+	// 头部
+	.header {
+		display: flex;
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		position: relative;
+		height: 45px;
+		line-height: 45px;
+		border-bottom: 1px solid #DDD;
+
+		.head-month {
+			font-size: 18px;
+			padding: 0 20px;
+		}
+
+		.go-to-today {
+			position: absolute;
+			right: 0;
+			top: 13px;
+			padding: 4px 6px 4px 11px;
+			background-color: rgba(255, 184, 0, 1);
+			border-radius: 11px 0 0 11px;
+			font-size: 12px;
+			line-height: 12px;
+			color: #FFF;
+		}
+
+		.head-icon {
+			content: '';
+			display: flex;
+			width: 11px;
+			height: 11px;
+			border-top: 1px solid #606266;
+			border-left: 1px solid #606266;
+		}
+
+		.head-icon.head-pre-month {
+			transform: rotate(-45deg);
+		}
+
+		.head-icon.head-next-month {
+			transform: rotate(135deg);
+		}
+	}
+
+	// 星期
+	.week-area {
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		border-bottom: 1px solid #EEE;
+		padding: 8px 0;
+		// margin: 0 5px;
+
+		.week-font {
+			flex: 1;
+			text-align: center;
+			color: #666;
+			font-size: 14px;
+		}
+	}
+
+	// 日历
+	.calendar-data {
+		// transition: all 300ms;
+
+		.swiper-item {
+			position: relative;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			flex-direction: row;
+
+			.month-bg {
+				position: absolute;
+				font-size: 230px;
+				color: #EEE;
+				font-weight: bold;
+				opacity: 0.4;
+				z-index: -1;
+			}
+
+			.month-days {
+				flex: 1;
+				// margin-top: 5px;
+				position: relative;
+
+				.week-days {
+					display: flex;
+					flex-direction: row;
+
+					.day {
+						flex: 1;
+						width: 14.28%;
+						text-align: center;
+						height: 42px;
+						color: #000;
+						/* #ifndef APP-NVUE */
+						padding: 0 3px;
+						/* #endif */
+						// box-sizing: border-box;
+						display: flex;
+
+						.day-info {
+							flex: 1;
+							display: flex;
+							flex-direction: column;
+							justify-content: flex-start;
+							align-items: center;
+							position: relative;
+
+							.day-solar {
+								display: flex;
+								font-size: 17px;
+								line-height: 17px;
+								margin-top: 9px;
+							}
+
+							.day-lunar,
+							.day-sign {
+								color: #909399;
+								font-size: 12px;
+								line-height: 12px;
+								transform: scale(0.8);
+								white-space: nowrap;
+							}
+
+							.day-sign {
+								color: #F75858 !important;
+							}
+
+							.day-tag {
+								content: "";
+								position: absolute;
+								top: 4px;
+								right: 4px;
+								width: 5px;
+								height: 5px;
+								border-radius: 3px;
+								background-color: #C30D23;
+							}
+						}
+
+						// 非当月日期
+						.day-info.un-month {
+							opacity: 0.25;
+							transition: opacity 300ms;
+						}
+
+						// 今天日期
+						.is-today .day-solar,
+						.is-today .day-sign,
+						.is-today .day-lunar {
+							// color: #409eff;
+						}
+
+						// 周末
+						.week-end .day-solar {
+							color: #FF9595;
+						}
+
+						// 法定假日
+						.is-holiday .day-solar,
+						.is-holiday .day-sign,
+						.is-holiday .day-lunar {
+							color: #F75858 !important;
+						}
+
+						// 当前选中的日期
+						.is-sel {
+							background-color: #C40C24;
+							border-radius: 3px;
+							color: #fff !important;
+
+							.day-tag {
+								content: "";
+								position: absolute;
+								top: 4px;
+								right: 4px;
+								width: 5px;
+								height: 5px;
+								border-radius: 3px;
+								background-color: #fff;
+							}
+						}
+					}
+				}
+
+				// .week-days.week-hide {
+				// 	display: none;
+				// }
+			}
+
+			.item-week {
+				.un-month {
+					opacity: 1 !important;
+				}
+			}
+		}
+	}
+
+	// 收缩按钮
+	.shrink {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		height: 30px;
+		border-top: 1px solid #DDD;
+
+		/* #ifndef APP-NVUE */
+		.shrink-btn {
+			width: 16px;
+			height: 16px;
+			background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAMhJREFUOE/dky8LAkEQxd9w0WjV72KxWXfggmAyWWzXxHbNYjIJhoOZarvid9FqNB4jCyes3h9OLghu3fceb36zS+h5qKcf/xYgIisAayJKnHMa8lFVZ2YpgB0z7193FQYisgCwAXA3s8QLicgbhwC2zHwMgxshBkFeXzE2Nvh2rW8NsiybRFE0ZuZTXZCIzIuiuMZxfKltICJTAAciegDwIM9eqKozAKmZDQAsmTlvHSGY/1YKR00cWl9iGYRP8p220BXm7//CE8WyQBHBXZb9AAAAAElFTkSuQmCC);
+		}
+
+		.shrink-close {
+			transform: rotate(180deg);
+		}
+
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		.shrink-btn {
+			content: '';
+			display: flex;
+			width: 9px;
+			height: 9px;
+			border-top: 1px solid #606266;
+			border-left: 1px solid #606266;
+			transform: rotate(-135deg);
+		}
+
+		.shrink-close {
+			transform: rotate(45deg);
+		}
+
+		/* #endif */
+	}
+}
+</style>

+ 78 - 0
uni_modules/lunc-calendar/package.json

@@ -0,0 +1,78 @@
+{
+  "id": "lunc-calendar",
+  "displayName": "calendar日历组件",
+  "version": "1.0.11",
+  "description": "可左右滑动切换月份,自定义显示农历",
+  "keywords": [
+    "calendar",
+    "日历"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "插件不采集任何数据",
+      "permissions": "无"
+    },
+    "npmurl": "",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "Vue": {
+          "vue2": "y",
+          "vue3": "u"
+        },
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "u",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "u"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "n",
+          "百度": "u",
+          "字节跳动": "u",
+          "QQ": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 126 - 0
uni_modules/lunc-calendar/readme.md

@@ -0,0 +1,126 @@
+## 日历组件
+### 简单介绍
+- 组件需要依赖 sass 插件 ,请自行安装;
+- 本组件依赖 uni-app内置组件`swiper`组件,请与`swiper`组件配合使用;
+- nvue页面中的`swiper`组件部分功能不支持,详情见[uni-app内置组件:swiper](https://uniapp.dcloud.net.cn/component/swiper.html)
+- 可设置显示收缩按钮,收缩后只显示一个星期的日期,展开后显示一个月的日期;可以通过触摸屏幕左右滑动切换月份或切换周;
+- 可根据需要选择是否显示农历日期,且会显示节日,如春节、端午、国庆、腊八节等;
+- 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js)
+- 欢迎大家下载使用,项目源码示例:[https://gitee.com/lunc9932/calendar](https://gitee.com/lunc9932/calendar)
+- 若有插件导入失败,重启编辑器;
+
+### API
+#### 属性说明
+| 属性名           | 类型     | 默认值   | 说明              |
+|-----------------|----------|----------|-----------------------|
+| date            | Date     | new Date()    | 默认日期             |
+| showLunar       | Boolean  | false    | 是否显示农历             |
+| firstDayOfWeek  | String   | monday   | 周几为每周的第一天,可选值:monday / sunday<br>monday:每周从周一开始;sunday:每周从周日开始           |
+| showMonthBg     | Boolean  | true     | 是否显示月份背景               |
+| weekend         | Boolean  | true     | 周末标红(周六周日日期用红色字体)  |
+| shouChangeBtn   | Boolean  | true     | 是否显示上月下月箭头按钮      |
+| weekType        | Boolean  | 周       | 星期的前缀,如星期一星期二,或周一周二,也可以只显示一、二等      |
+| showShrink      | Boolean  | false    | 是否显示收缩按钮      |
+| shrinkState     | String   | month    | 默认显示周数据还是月数据(收起或展开),可选值:week / month<br>week:默认打开显示周数据(收起状态);month:默认打开显示月数据(展开状态)      |
+| signList        | Array    | []       | 标记数组,若同日期有多个标记,则显示最后一个<br>期待格式[{date: '2021-09-10', title: '生日', info: '八月初四张三生日'}]  |
+
+注意:
+1. 标记日期会在日期下方显示“title”内容,建议“title”内容不超过4个汉字;
+2. 农历日期、节日、标记日期,只会显示其一,优先级 标记 > 节日 > 农历日期;
+3. `showShrink`表示是否显示收缩按钮,并不是表示收起或展开状态;
+
+#### 方法说明
+| 方法名          | 说明        | 参数 |
+|-----------------|------------|--------|
+| addSignList     | 添加标记    | (Array:list),传入需要添加的标记数组,日期相同不会覆盖,只显示第一个出现的标记         |
+| deleteSignList  | 删除标记    | (Array:list),传入需要删除的标记数组,根据 date 和 title 同时判断进行删除         |
+
+方法使用:在组件上添加 `ref` 属性,在调用对应的方法:`this.$refs.calendar.addSignList([])` 即可,详情见下方的基本用法;<br>
+**原 setSignList() 方法已弃用;**
+
+
+#### 事件说明
+| 事件名        | 说明                | 返回值 |
+|---------------|--------------------|--------|
+| @dayChange    | 选中日期改变时触发,包括点击日期、切换月份和切换周都会改变选中日期 | dayInfo,详情见下方说明 |
+| @monthChange  | 切换月份时触发,包括点击切换月份按钮、选中日期时切换的月份等都会改变触发 | monthInfo,详情见下方说明 |
+| @shrinkClick   | 收缩和展开时触发事件 | 返回当前状态:week / month<br>week:当前显示的一个星期(收起);month:当前显示的一个月(展开) |
+
+#### @dayChange 返回值 dayInfo 说明
+| 值      | 类型    | 说明  |
+| ------- | ------ | ----- |
+| date    | String | 日期,格式“yyyy-MM-dd” |
+| year    | Number | 年    |
+| month   | Number | 月    |
+| day     | Number | 日    |
+| week    | Number | 星期几 |
+| daySign | Array  | 当前日期的标记,若没有则为空数组 |
+| lunar   | Object | 农历信息,包含农历日期、节日、生肖等;<br>只有属性 showLunar 设置为 true 时才会返回此值; |
+
+#### @monthChange 返回值 monthInfo 说明
+| 值    | 类型    | 说明  |
+| ----- | ------ | ----- |
+| year  | Number | 年    |
+| month | Number | 月    |
+| type  | String | 返回类型:today / pre / next<br>today:当前月份,pre:上个月,next:下个月 |
+
+
+### 基本用法
+在`template`中使用组件
+
+```
+<view class="content">
+  <view class="content-item">
+    <lunc-calendar ref="calendar" :showLunar="true" :showMonthBg="true" :showShrink="true" :signList="signList"
+      @dayChange="dayChange" weekType="星期" @monthChange="monthChange" @shrinkClick="shrinkClick">
+    <view class="operation">
+      <view class="button" @click="addSign">新增标记</view>
+      <view class="button" @click="updateSign">修改标记</view>
+    </view>
+  </view>
+  <view class="content-item">
+    <lunc-calendar :showShrink="true" shrinkState="week"></lunc-calendar>
+  </view>
+</view>
+```
+在`script`中使用
+
+```
+export default {
+  data() {
+    return {
+      signList: [{date: "2023-06-25",title: "生日",info: "张三生日"},
+        {date: "2023-06-19",title: "朋友聚会"}, 
+        {date: "2023-07-22",title: "纪念日"}, 
+        {date: "2023-04-11",title: "聚会"}, 
+        {date: "2023-07-23",title: "郊游"}, 
+        {date: "2023-08-19",title: "游玩"}],
+    }
+  },
+  methods: {
+    dayChange(dayInfo) { // 点击日期
+      console.log("点击日期", JSON.parse(JSON.stringify(dayInfo)));
+    },
+    monthChange(monthInfo) { // 切换月份
+      console.log("切换月份", JSON.parse(JSON.stringify(monthInfo)));
+    },
+    shrinkClick(type) {
+      console.log("当前状态", type);
+    },
+
+    addSign() { // 添加标记
+      let addList = [{date: "2023-06-28",title: "休息"}, 
+        {date: "2023-06-01",title: "上班"}];
+      // 调用 addSignList 方法,传入需要添加的标记数组
+      this.$refs.calendar.addSignList(addList);
+    },
+    deleteSign() { // 删除标记
+      let deleteList = [{date: "2023-06-01",title: "上班"}];
+      // 调用 deleteSignList 方法,传入需要删除的标记数组
+      this.$refs.calendar.deleteSignList(deleteList);
+    }
+  }
+}
+```
+
+

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor