Bladeren bron

Merge branch 'master' of http://git.zthymaoyi.com/wangchao/businessCard

ccjgmwz 2 jaren geleden
bovenliggende
commit
6aa053be4d
38 gewijzigde bestanden met toevoegingen van 7780 en 21 verwijderingen
  1. 4 0
      unimall-admin-api/src/main/java/com/iotechn/unimall/admin/api/card/ICircleFriendsDetailService.java
  2. 3 1
      unimall-admin-api/src/main/java/com/iotechn/unimall/admin/api/card/ICircleFriendsInfoService.java
  3. 17 0
      unimall-admin-api/src/main/java/com/iotechn/unimall/admin/api/card/impl/CircleFriendsDetailServiceImpl.java
  4. 44 2
      unimall-admin-api/src/main/java/com/iotechn/unimall/admin/api/card/impl/CircleFriendsInfoServiceImpl.java
  5. 5 0
      unimall-data/src/main/java/com/iotechn/unimall/data/domain/CircleFriendsDetail.java
  6. 24 0
      unimall-data/src/main/java/com/iotechn/unimall/data/domain/CircleFriendsInfo.java
  7. 9 0
      xiaochengxu/README.md
  8. 52 0
      xiaochengxu/common/util.js
  9. 776 0
      xiaochengxu/components/lime-painter/README.md
  10. 78 0
      xiaochengxu/components/lime-painter/canvas.js
  11. 694 0
      xiaochengxu/components/lime-painter/draw.js
  12. 107 0
      xiaochengxu/components/lime-painter/gradient.js
  13. 300 0
      xiaochengxu/components/lime-painter/index.vue
  14. 376 0
      xiaochengxu/components/lime-painter/layout.js
  15. 791 0
      xiaochengxu/components/lime-painter/qrcode.js
  16. 437 0
      xiaochengxu/components/lime-painter/utils.js
  17. 8 5
      xiaochengxu/components/ossutil/uploadFile.js
  18. 26 0
      xiaochengxu/pages.json
  19. 13 2
      xiaochengxu/pages/cardHolder/scanCodeAddCard.vue
  20. 155 0
      xiaochengxu/pages/circle/addFriendCirlce.vue
  21. 515 0
      xiaochengxu/pages/circle/circle-item.vue
  22. 45 9
      xiaochengxu/pages/circle/detail.vue
  23. 414 0
      xiaochengxu/pages/circle/friendSCirlce.vue
  24. 0 1
      xiaochengxu/pages/mySet/help.vue
  25. 5 0
      xiaochengxu/pages/mySet/mySet.vue
  26. 1127 0
      xiaochengxu/pages/mySet/poster.vue
  27. BIN
      xiaochengxu/static/comment.png
  28. BIN
      xiaochengxu/static/imgs/card/share1.png
  29. BIN
      xiaochengxu/static/imgs/mySet/hb.png
  30. BIN
      xiaochengxu/static/imgs/qrcode.jpg
  31. BIN
      xiaochengxu/static/love-fill.png
  32. BIN
      xiaochengxu/static/love.png
  33. BIN
      xiaochengxu/static/play.png
  34. 69 0
      xiaochengxu/uni_modules/cl-upload/changelog.md
  35. 54 0
      xiaochengxu/uni_modules/cl-upload/components/cl-image/cl-image.vue
  36. 981 0
      xiaochengxu/uni_modules/cl-upload/components/cl-upload/cl-upload.vue
  37. 240 0
      xiaochengxu/uni_modules/cl-upload/readme.md
  38. 411 1
      xiaochengxu/util/request.js

+ 4 - 0
unimall-admin-api/src/main/java/com/iotechn/unimall/admin/api/card/ICircleFriendsDetailService.java

@@ -41,6 +41,10 @@ public interface ICircleFriendsDetailService{
 	@HttpMethod(description = "删除",permissionName = "记录朋友圈互动信息管理")
 	public Boolean delete(@NotNull @HttpParam(name = "id", type = HttpParamType.COMMON, description = "")String id)throws ServiceException;
 
+	@HttpMethod(description = "取消点赞",permissionName = "记录朋友圈互动信息管理")
+	public Boolean cancelLike(@NotNull @HttpParam(name = "circleFriendsId", type = HttpParamType.COMMON, description = "朋友圈id")Long circleFriendsId,
+							  @HttpParam(name = "commonId", type = HttpParamType.COMMON, description = "发表人id") Long commonId)throws ServiceException;
+
 	@HttpMethod(description = "修改",  permissionName = "记录朋友圈互动信息管理")
 	public Boolean update(@NotNull @HttpParam(name = "circleFriendsDetail", type = HttpParamType.COMMON, description = "记录朋友圈互动信息") CircleFriendsDetail circleFriendsDetail)throws ServiceException;
 

+ 3 - 1
unimall-admin-api/src/main/java/com/iotechn/unimall/admin/api/card/ICircleFriendsInfoService.java

@@ -24,8 +24,10 @@ public interface ICircleFriendsInfoService{
 
 	@HttpMethod(description = "列表",  permissionName = "记录朋友圈信息管理")
 	public Page<CircleFriendsInfo> list(
+								@HttpParam(name = "circleId", type = HttpParamType.COMMON, description = "圈子id") Long circleId,
+								@HttpParam(name = "currentCommonId", type = HttpParamType.COMMON, description = "当前登录人id") Long currentCommonId,
 								@HttpParam(name = "commonId", type = HttpParamType.COMMON, description = "朋友圈发布人id") Long commonId,
-							@HttpParam(name = "head", type = HttpParamType.COMMON, description = "头像") String head,
+								@HttpParam(name = "head", type = HttpParamType.COMMON, description = "头像") String head,
 							@HttpParam(name = "nickname", type = HttpParamType.COMMON, description = "昵称") String nickname,
 							@HttpParam(name = "content", type = HttpParamType.COMMON, description = "朋友圈内容") String content,
 							@HttpParam(name = "image", type = HttpParamType.COMMON, description = "图片") String image,

+ 17 - 0
unimall-admin-api/src/main/java/com/iotechn/unimall/admin/api/card/impl/CircleFriendsDetailServiceImpl.java

@@ -3,6 +3,7 @@ package com.iotechn.unimall.admin.api.card.impl;
 import java.util.List;
 
 import com.iotechn.unimall.admin.api.card.ICircleFriendsDetailService;
+import com.iotechn.unimall.data.domain.CardExchangeInfo;
 import com.iotechn.unimall.data.domain.CircleFriendsDetail;
 import com.iotechn.unimall.data.mapper.CircleFriendsDetailMapper;
 import org.apache.ibatis.session.RowBounds;
@@ -90,6 +91,22 @@ public class CircleFriendsDetailServiceImpl implements ICircleFriendsDetailServi
 		return true;
 	}
 
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public Boolean cancelLike(Long circleFriendsId, Long commonId) throws ServiceException {
+		CircleFriendsDetail circleFriendsDetail = new CircleFriendsDetail();
+		circleFriendsDetail.setCircleFriendsId(circleFriendsId);
+		circleFriendsDetail.setCommonId(commonId);
+		circleFriendsDetail.setInteractionFlag("1");
+		circleFriendsDetail.setDeleteFlag(0l);
+		CircleFriendsDetail circleFriendsDetail1=circleFriendsDetailMapper.selectOne(circleFriendsDetail);
+		if (circleFriendsDetail1!=null){
+			circleFriendsDetail1.setDeleteFlag(1l);
+			circleFriendsDetailMapper.updateById(circleFriendsDetail1);
+		}
+		return true;
+	}
+
 	@Override
 	public Boolean update(CircleFriendsDetail circleFriendsDetail) throws ServiceException {
 		Date now = new Date();

+ 44 - 2
unimall-admin-api/src/main/java/com/iotechn/unimall/admin/api/card/impl/CircleFriendsInfoServiceImpl.java

@@ -3,11 +3,14 @@ package com.iotechn.unimall.admin.api.card.impl;
 import java.util.List;
 
 import com.iotechn.unimall.admin.api.card.ICircleFriendsInfoService;
+import com.iotechn.unimall.data.domain.CircleFriendsDetail;
 import com.iotechn.unimall.data.domain.CircleFriendsInfo;
+import com.iotechn.unimall.data.mapper.CircleFriendsDetailMapper;
 import com.iotechn.unimall.data.mapper.CircleFriendsInfoMapper;
 import org.apache.ibatis.session.RowBounds;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 
 import com.baomidou.mybatisplus.mapper.EntityWrapper;
@@ -28,6 +31,8 @@ import org.springframework.transaction.annotation.Transactional;
 public class CircleFriendsInfoServiceImpl implements ICircleFriendsInfoService {
 	@Autowired
 	private CircleFriendsInfoMapper circleFriendsInfoMapper;
+	@Autowired
+	private CircleFriendsDetailMapper circleFriendsDetailMapper;
 	
 	@Override
 	public Boolean add(CircleFriendsInfo circleFriendsInfo) throws ServiceException {
@@ -38,9 +43,12 @@ public class CircleFriendsInfoServiceImpl implements ICircleFriendsInfoService {
 	}
 
 	@Override
-	public Page<CircleFriendsInfo> list(Long commonId,String head,String nickname,String content,String image,String location,String positioning,String commentFlag,Date gmtCreate,Date gmtUpdate,Long deleteFlag, Integer page, Integer limit)throws ServiceException {
+	public Page<CircleFriendsInfo> list(Long circleId,Long currentCommonId,Long commonId,String head,String nickname,String content,String image,String location,String positioning,String commentFlag,Date gmtCreate,Date gmtUpdate,Long deleteFlag, Integer page, Integer limit)throws ServiceException {
 		Wrapper<CircleFriendsInfo> wrapper = new EntityWrapper<CircleFriendsInfo>();
-														if (!StringUtils.isEmpty(commonId)) {
+				if (!StringUtils.isEmpty(circleId)) {
+					wrapper.eq("circle_id", circleId);
+				}
+												if (!StringUtils.isEmpty(commonId)) {
 					wrapper.eq("common_id", commonId);
 				}
 												if (!StringUtils.isEmpty(head)) {
@@ -75,6 +83,40 @@ public class CircleFriendsInfoServiceImpl implements ICircleFriendsInfoService {
 				}
 							wrapper.eq("delete_flag", 0);
 		List<CircleFriendsInfo> list = circleFriendsInfoMapper.selectPage(new RowBounds((page - 1) * limit, limit), wrapper);
+		if (!CollectionUtils.isEmpty(list)){
+			for (CircleFriendsInfo circleFriendsInfo:list){
+				//点赞list
+				List<CircleFriendsDetail> circleFriendsDetailList=circleFriendsDetailMapper.selectList(new EntityWrapper<CircleFriendsDetail>()
+				.eq("circle_friends_id",circleFriendsInfo.getId())
+				.eq("delete_flag",0)
+				.eq("interaction_flag","1"));
+				if(!CollectionUtils.isEmpty(circleFriendsDetailList)){
+					circleFriendsInfo.setCircleFriendsDetailList(circleFriendsDetailList);
+					circleFriendsInfo.setCount(circleFriendsDetailList.size());
+				}
+				//判断当前账号是否点赞标识
+				List<CircleFriendsDetail> circleFriendsDetailList2=circleFriendsDetailMapper.selectList(new EntityWrapper<CircleFriendsDetail>()
+						.eq("circle_friends_id",circleFriendsInfo.getId())
+						.eq("common_id",currentCommonId)
+						.eq("delete_flag",0)
+						.eq("interaction_flag","1"));
+				if(!CollectionUtils.isEmpty(circleFriendsDetailList2)){
+					circleFriendsInfo.setHelpFlag("1");
+				}
+				else {
+					circleFriendsInfo.setHelpFlag("0");
+				}
+				//评论list
+				List<CircleFriendsDetail> circleFriendsDetailList1=circleFriendsDetailMapper.selectList(new EntityWrapper<CircleFriendsDetail>()
+						.eq("circle_friends_id",circleFriendsInfo.getId())
+						.eq("delete_flag",0)
+						.eq("interaction_flag","2"));
+				if(!CollectionUtils.isEmpty(circleFriendsDetailList1)){
+					circleFriendsInfo.setCircleFriendsDetailList1(circleFriendsDetailList1);
+					circleFriendsInfo.setCount1(circleFriendsDetailList1.size());
+				}
+			}
+		}
 		Integer count = circleFriendsInfoMapper.selectCount(wrapper);
 		return new Page<CircleFriendsInfo>(list, page, limit, count);
 	}

+ 5 - 0
unimall-data/src/main/java/com/iotechn/unimall/data/domain/CircleFriendsDetail.java

@@ -58,6 +58,11 @@ public class CircleFriendsDetail extends SuperDO {
     @TableField("comment_id")
     private Long commentId;
 
+    /** 回复的评论人昵称 */
+    @Excel(name = "回复的评论人昵称")
+    @TableField("comment_name")
+    private String commentName;
+
     /** 标识(1点赞2评论) */
     @Excel(name = "标识(1点赞2评论)")
     @TableField("interaction_flag")

+ 24 - 0
unimall-data/src/main/java/com/iotechn/unimall/data/domain/CircleFriendsInfo.java

@@ -29,6 +29,11 @@ public class CircleFriendsInfo extends SuperDO {
     @TableId("id")
     private Long id;
 
+    /** 圈子id */
+    @Excel(name = "圈子id")
+    @TableField("circle_id")
+    private Long circleId;
+
     /** 朋友圈发布人id */
     @Excel(name = "朋友圈发布人id")
     @TableField("common_id")
@@ -54,6 +59,11 @@ public class CircleFriendsInfo extends SuperDO {
     @TableField("image")
     private String image;
 
+    /** 文件类型(1图片2视频) */
+    @Excel(name = "文件类型(1图片2视频)")
+    @TableField("media_type")
+    private String mediaType;
+
     /** 位置 */
     @Excel(name = "位置")
     @TableField("location")
@@ -85,6 +95,20 @@ public class CircleFriendsInfo extends SuperDO {
     private Long deleteFlag;
     @TableField(exist = false)
     private List<CircleFriendsDetail> circleFriendsDetailList;
+    @TableField(exist = false)
+    private List<CircleFriendsDetail> circleFriendsDetailList1;
+    //点赞数
+    @TableField(exist = false)
+    private Integer count;
+    //评论数
+    @TableField(exist = false)
+    private Integer count1;
+    //当前登录人id
+    @TableField(exist = false)
+    private Long currentCommonId;
+    //是否点赞标识
+    @TableField(exist = false)
+    private String helpFlag;
 
     @Override
     public String toString() {

+ 9 - 0
xiaochengxu/README.md

@@ -223,3 +223,12 @@ static/styles/index.scss
 ```
 @click.native.stop="delSearchVal"
 ```
+## showToast
+
+```
+uni.showToast({
+	icon: "success",
+	title: '保存成功!',
+	duration: 2000
+});
+```

+ 52 - 0
xiaochengxu/common/util.js

@@ -0,0 +1,52 @@
+function friendlyDate(timestamp) {
+	var formats = {
+		'year': '%n% 年前',
+		'month': '%n% 月前',
+		'day': '%n% 天前',
+		'hour': '%n% 小时前',
+		'minute': '%n% 分钟前',
+		'second': '%n% 秒前',
+	};
+
+	var now = Date.now();
+	var seconds = Math.floor((now - timestamp) / 1000);
+	var minutes = Math.floor(seconds / 60);
+	var hours = Math.floor(minutes / 60);
+	var days = Math.floor(hours / 24);
+	var months = Math.floor(days / 30);
+	var years = Math.floor(months / 12);
+
+	var diffType = '';
+	var diffValue = 0;
+	if (years > 0) {
+		diffType = 'year';
+		diffValue = years;
+	} else {
+		if (months > 0) {
+			diffType = 'month';
+			diffValue = months;
+		} else {
+			if (days > 0) {
+				diffType = 'day';
+				diffValue = days;
+			} else {
+				if (hours > 0) {
+					diffType = 'hour';
+					diffValue = hours;
+				} else {
+					if (minutes > 0) {
+						diffType = 'minute';
+						diffValue = minutes;
+					} else {
+						diffType = 'second';
+						diffValue = seconds === 0 ? (seconds = 1) : seconds;
+					}
+				}
+			}
+		}
+	}
+	return formats[diffType].replace('%n%', diffValue);
+}
+export {
+	friendlyDate
+}

+ 776 - 0
xiaochengxu/components/lime-painter/README.md

@@ -0,0 +1,776 @@
+# Painter 画板
+> uniapp 海报画板,可根据自身需求配置生成海报
+> [查看更多](http://liangei.gitee.io/limeui-docs/components/painter/) <br>
+> Q群:458377637 <br>
+> 群里有最新Demo
+<br>
+
+
+### 平台兼容
+
+| H5  | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
+| --- | ---------- | ------------ | ---------- | ---------- | --------- | --- |
+| √   | √          | √         | 未测       | √          | √      | √   |
+
+<br>
+<br>
+
+### 引入
+
+```js
+import lPainter from '@/components/lime-painter/'
+export default {
+	components: {lPainter}
+}
+```
+
+<br>
+<br>
+
+### 代码演示
+#### 基本用法
+
+定一个画板对象,包含`width`、`height`、`background`,`views`为画板里的元素集,它是一个数组对象。<br>
+元素类型目前有`view`、`text`、`image`<br>
+css 对象里的位置都是相对于画板的绝对定位,支持`rpx`、`px`
+
+```html
+<l-painter :board="base"/>
+```
+
+```js
+export default {
+    data() {
+        return {
+            base: {
+                width: '686rpx',
+				height: '130rpx',
+				views: [
+					{
+						type: 'view',
+						css: {
+							left: '0rpx',
+							top: '0rpx',
+							background: '#07c160',
+							width: '120rpx',
+							height: '120rpx'
+						}
+					},
+                    {
+						type: 'view',
+						css: {
+							left: '180rpx',
+							top: '18rpx',
+							background: '#1989fa',
+							width: '80rpx',
+							height: '80rpx',
+							transform: 'transform: rotate(50deg)'
+						}
+					}
+				]
+            }
+        }
+    }
+}
+
+```
+
+<br><br>
+
+#### 圆角
+
+为可元素定一个圆角`radius`,支持`rpx`、`px`、`%`
+
+```html
+<l-painter :board="base"/>
+```
+
+```js
+export default {
+data() {
+    return {
+        base: {
+            width: '686rpx',
+            height: '130rpx',
+            views: [
+                {
+                    type: 'view',
+                    css: {
+                        left: '0rpx',
+                        top: '0rpx',
+                        background: '#07c160',
+                        width: '120rpx',
+                        height: '120rpx',
+                        radius: '16rpx 30rpx 10rpx 80rpx'
+                    }
+                },
+                {
+                    type: 'view',
+                    css: {
+                        left: '150rpx',
+                        top: '0rpx',
+                        background: '#1989fa',
+                        width: '120rpx',
+                        height: '120rpx',
+                        radius: '16rpx 60rpx'
+                    }
+                },
+                {
+                    type: 'view',
+                    css: {
+                        left: '300rpx',
+                        top: '0rpx',
+                        background: '#ff976a',
+                        width: '120rpx',
+                        height: '120rpx',
+                        radius: '50%'
+                    }
+                }
+            ]
+        }
+    }
+}
+}
+
+```
+
+<br><br>
+
+#### 描边投影
+
+为可元素定一个描边`border`和投影`shadow`,支持`rpx`、`px`
+
+```html
+<l-painter :board="base"/>
+```
+
+```js
+export default {
+data() {
+    return {
+        base: {
+            width: '686rpx',
+            height: '130rpx',
+            views: [
+                {
+                    type: 'view',
+                    css: {
+                        left: '10rpx',
+                        top: '10rpx',
+                        background: 'rgba(7,193,96,.1)',
+                        width: '120rpx',
+                        height: '120rpx',
+                        radius: '50%',
+                        border: '2rpx dashed rgb(7,193,96)'
+                    }
+                },
+                {
+                    type: 'view',
+                    css: {
+                        left: '150rpx',
+                        top: '10rpx',
+                        background: 'rgba(25,137,250,.4)',
+                        width: '120rpx',
+                        height: '120rpx',
+                        radius: '50%',
+                        shadow: '0 5rpx 10rpx rgba(25,137,250,.8)'
+                    }
+                },
+                {
+                    type: 'view',
+                    css: {
+                        left: '300rpx',
+                        top: '10rpx',
+                        background: 'rgba(255, 151, 106, .1)',
+                        width: '120rpx',
+                        height: '120rpx',
+                        radius: '50%',
+                        border: '2rpx solid #ff976a'
+                    }
+                }
+            ]
+        }
+    }
+}
+}
+
+```
+
+<br><br>
+
+#### 图片
+
+元素类型为`image`时,需要一个图片地址`src`,图片地址支持`相对路径`和`网络地址`<br>
+
+::: warning 温馨提示
+当为网络地址时,<br>
+H5:需要解决跨域问题或使用`isH5PathToBase64`可解决部分问题。 <br>
+小程序:需要配置 downloadFile 域名 <br>
+本插件全端支持 base64 图片 <br>
+:::
+<br>
+
+图片模式`mode`目前支持的有`aspectFill`、`aspectFit`、`scaleToFill`,默认为`scaleToFill`,`height`和`width`支持`auto`,当设置为`auto`时`mode`失效。
+
+
+```html
+<l-painter :board="base"/>
+```
+
+```js
+export default {
+data() {
+    return {
+        base: {
+            width: '686rpx',
+            height: '130rpx',
+            views: [
+                {
+                    type: 'image',
+                    src: '../../static/avatar-1.jpeg',
+                    css: {
+                        left: '0rpx',
+                        top: '0rpx',
+                        width: '120rpx',
+                        height: '120rpx'
+                    }
+                },
+                {
+                    type: 'image',
+                    src: '../../static/avatar-2.jpg',
+                    css: {
+                        left: '150rpx',
+                        top: '0rpx',
+                        width: '120rpx',
+                        height: '120rpx',
+                        radius: '16rpx'
+                    }
+                },
+                {
+                    type: 'image',
+                    src:'../../static/avatar-3.jpeg',
+                    css: {
+                        left: '300rpx',
+                        top: '0rpx',
+                        background: '#ff976a',
+                        width: '120rpx',
+                        height: '120rpx',
+                        radius: '50%'
+                    }
+                }
+            ]
+        }
+    }
+}
+}
+
+```
+
+<br><br>
+
+#### 文字
+
+元素类型`text`时,内容写在`text`里,支持`\n`换行符,css 的属性有字体大小`fontSize`、行高`lineHeight`、对齐`textAlign`、修饰`textDecoration`、粗细`fontWeight`、 宽度`width`、最大行数`maxLines`。
+设置了最大行数和宽度时,当文字内容超过会显示省略号。
+
+```html
+<l-painter :board="base"/>
+```
+
+```js
+export default {
+data() {
+    return {
+        base: {
+            width: '686rpx',
+            height: '500rpx',
+            views: [
+                {
+                    type: 'text',
+                    text: '左对齐,下划线\n无风才到地,有风还满空\n缘渠偏似雪,莫近鬓毛生',
+					// 可以指定关键字颜色
+					rules: {
+						word: ['到地'],
+						color: 'red'
+					},
+                    css: {
+                        left: '0rpx',
+                        top: '10rpx',
+                        fontSize: '28rpx',
+                        lineHeight: '36rpx',
+                        textDecoration: 'underline'
+                    }
+                },
+                {
+                    type: 'text',
+                    text: '居中,上划线\n无风才到地,有风还满空\n缘渠偏似雪,莫近鬓毛生',
+                    css: {
+                        left: '0rpx',
+                        top: '150rpx',
+                        fontSize: '28rpx',
+                        lineHeight: '36rpx',
+                        textAlign: 'center',
+                        textDecoration: 'overline'
+                    }
+                },
+                {
+                    type: 'text',
+                    text: '右对齐,中划线\n无风才到地,有风还满空\n缘渠偏似雪,莫近鬓毛生',
+                    css: {
+                        left: '0rpx',
+                        top: '290rpx',
+                        fontSize: '28rpx',
+                        lineHeight: '36rpx',
+                        textAlign: 'right',
+                        textDecoration: 'line-through',
+                    }
+                },
+                {
+                    type: 'text',
+                    text: '省略号\n明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。',
+                    rules: {
+                    	word: ['几时有'],
+                    	color: 'red'
+                    },
+					css: {
+                        left: '0rpx',
+                        top: '420rpx',
+                        fontSize: '28rpx',
+                        maxLines: 2,
+                        width: '686rpx',
+                        lineHeight: '36rpx'
+                    }
+                }
+            ]
+        }
+    }
+}
+}
+
+```
+<br><br>
+
+#### 二维码
+该方法需要[下载此链接的文件](https://gitee.com/liangei/lime-painter/blob/master/qrcode.js)覆盖插件目录的`qrcode.js`才生效。<br>
+考虑到不是很多人需要,文件又比较大。故出此下策。
+```html
+<l-painter :board="base" />
+```
+```js
+{
+	type: 'qrcode',
+	text: 'https://www.baidu.com',
+	css: {
+		left: '380rpx',
+		top: '230rpx',
+		width: '200rpx',
+		height: '200rpx',
+		color: '#1989fa',
+		backgroundColor: 'rgba(25,137,250,.1)',
+		border: '20rpx solid rgba(25,137,250,.1)',
+	}
+}
+
+```
+
+
+<br>
+
+#### 调用内部方法
+插件也允许通过`ref`获取内部方法来实现绘制和生成图片。
+需要设置画板的`width`和`height`
+```html
+<l-painter ref="painter" width="686rpx"  height="500rpx" />
+<image :src="path" />
+```
+
+```js
+export default {
+	data() {
+		return {
+			base: {
+				width: '686rpx',
+				height: '500rpx',
+				views: [
+					{
+						type: 'view',
+						css: {
+							fontSize: '28rpx',
+							lineHeight: '1.4em',
+						},
+						views: [
+							{
+								type: 'text',
+								text: '水调歌头',
+								css: {
+									display: 'block',
+									fontSize: '42rpx',
+									textAlign: 'center'
+								}
+							},
+							{
+								type: 'text',
+								text: '丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。',
+								css: {
+									display: 'block',
+									textIndent: '2em'
+								}
+							},
+							{
+								type: 'text',
+								text: '明月几时有?把酒问青天。不知天上宫阙,今夕是何年?我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间',
+								css: {
+									display: 'block',
+									color: 'red',
+									textIndent: '2em'
+								}
+							},
+							{
+								type: 'text',
+								text: '转朱阁,低绮户,照无眠。不应有恨,何事长向别时圆?人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟',
+								css: {
+									textIndent: '2em'
+								}
+							}
+						]
+					}
+				]
+			},
+			path: '',
+		}
+	},
+	methods: {
+		onRender() {
+			// 支持通过调用render传入参数
+			const painter = this.$refs.painter
+			painter.render(this.base)
+		},
+		canvasToTempFilePath() {
+			const painter = this.$refs.painter
+			// 支持通过调用canvasToTempFilePath方法传入参数 调取生成图片
+			painter.canvasToTempFilePath().then(res => this.path = res.tempFilePath)
+		}
+	}
+}
+```
+<br>
+
+#### 自定义
+有时画板提供的方法无法满足复杂个性的需要,可通过插件`ref`获取`ctx`来使用原生方法绘制。
+```html
+<l-painter ref="painter" width="686rpx"  height="500rpx" />
+<image :src="path" />
+```
+
+```js
+export default {
+	data() {
+		return {
+			path: '',
+		}
+	},
+	methods: {
+		async onCustom() {
+			const p = this.$refs.custom
+			// 自定义,使用原生和内部方法生成更个性的图形
+			await p.custom(async (ctx, draw) => {
+				// 原生方法的单位为px,如果想用rpx,请使用uni.upx2px(150)
+				// 绘制背景颜色
+				ctx.setFillStyle('#ff976a')
+				ctx.setShadow(5, 5, 50, '#ff976a')
+				ctx.beginPath()
+				ctx.arc(150, 150, 20, Math.PI, Math.PI * 1.5)
+				ctx.lineTo(150, 20)
+				ctx.arc(150, 0, 20, Math.PI * 0.5, Math.PI * 1)
+				ctx.lineTo(5, 0)
+				ctx.arc(0, 0, 20, Math.PI * 0, Math.PI * 0.5)
+				ctx.lineTo(0, 130)
+				ctx.arc(0, 150, 20, Math.PI * 1.5, Math.PI * 2)
+				ctx.lineTo(130, 150)
+				ctx.closePath()
+				ctx.fill()
+				// 绘制好形状后,调用draw的drawImage方法填充图片,图片会下载完成后绘制。无需自己下载。
+				await draw.drawImage('../../static/avatar-1.jpeg', {left: 0, top: 0, width:150, height:150})
+			})
+			await p.custom((ctx) => {
+				// 绘制背景颜色
+				ctx.setFillStyle('#07c160')
+				ctx.beginPath()
+				ctx.arc(280, 50, 50, 0, Math.PI * 2)
+				ctx.closePath()
+				ctx.fill()
+			})
+			// 单独绘制单个或多个元素的内部方法,
+			// 多个是是数组,单个是为对象
+			// 绘制二维码,由于二维码JS文件较大,也不是所有人需要。所以需自行下载覆盖。
+			await p.single({
+				type: 'qrcode',
+				text: 'https://www.baidu.com',
+				css: {
+					left: '380rpx',
+					top: '230rpx',
+					width: '200rpx',
+					height: '200rpx',
+					color: '#1989fa',
+					backgroundColor: 'rgba(25,137,250,.1)',
+					border: '20rpx solid rgba(25,137,250,.1)',
+					transform: 'rotate(45deg)'
+				}
+			})
+			await p.single({
+				type: 'text',
+				text: '床前明月光,疑是地上霜。\n举头望明月,低头思故乡。',
+				css: {
+					left: '0rpx',
+					top: '330rpx'
+				}
+			})
+			// 使用自定义时,要配合方法绘制,否则无法绘制。
+			await p.canvasDraw(true)
+		},
+		// 支持通过调用canvasToTempFilePath方法传入参数 调取生成图片
+		onCanvasToTempFilePath() {
+			const painter = this.$refs.custom
+			painter.canvasToTempFilePath({dpr:2}).then(res => this.path = res.tempFilePath)
+			
+		}
+	}
+}
+```
+
+<br><br>
+
+#### 提供一份海报样式案例
+
+是否生成图片:`isRenderImage` <br>
+生成图片成功:`success`返回一个图片临时地址。
+
+```html
+<l-painter
+  v-if="isShowPainter"
+  isRenderImage
+  custom-style="position: fixed; left: 200%;"
+  :board="base"
+  @success="path = $event"
+/>
+```
+
+```js
+export default {
+data() {
+    return {
+        base: {
+            width: '750rpx',
+            height: '1114rpx',
+            background: '#F6F7FB',
+            views: [
+                {
+                    type: 'view',
+                    css: {
+                        left: '40rpx',
+                        top: '144rpx',
+                        background: '#fff',
+                        radius: '16rpx',
+                        width: '670rpx',
+                        height: '930rpx',
+                        shadow: '0 20rpx 48rpx rgba(0,0,0,.05)'
+                    }
+                },
+                {
+                    type: 'image',
+                    src: '../../static/avatar-2.jpg',
+                    mode: 'widthFix',
+                    css: {
+                        left: '40rpx',
+                        top: '40rpx',
+                        width: '84rpx',
+                        height: '84rpx',
+                        radius: '50%',
+                        color: '#999'
+                    }
+                },
+                {
+                    type: 'text',
+                    text: '隔壁老王',
+                    css: {
+                        color: '#333',
+                        left: '144rpx',
+                        top: '40rpx',
+                        fontSize: '32rpx',
+                        fontWeight: 'bold'
+                    }
+                },
+                {
+                    type: 'text',
+                    text: '为您挑选了一个好物',
+                    css: {
+                        color: '#666',
+                        left: '144rpx',
+                        top: '90rpx',
+                        fontSize: '24rpx'
+                    }
+                },
+                {
+                    type: 'image',
+                    src: '../../static/goods.jpg',
+                    mode: 'widthFix',
+                    css: {
+                        left: '72rpx',
+                        top: '176rpx',
+                        width: '606rpx',
+                        height: '606rpx',
+                        radius: '12rpx'
+                    }
+                },
+                {
+                    type: 'text',
+                    text: '¥39.90',
+                    css: {
+                        color: '#FF0000',
+                        left: '66rpx',
+                        top: '812rpx',
+                        fontSize: '56rpx',
+                        fontWeight: 'bold'
+                    }
+                },
+                {
+                    type: 'text',
+                    text: '360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝',
+                    css: {
+                        maxLines: 2,
+                        width: '396rpx',
+                        color: '#333',
+                        left: '72rpx',
+                        top: '948rpx',
+                        fontSize: '36rpx',
+                        lineHeight: '50rpx'
+                    }
+                },
+                {
+                    type: 'image',
+                    src: '../../static/qr.png',
+                    mode: 'widthFix',
+                    css: {
+                        left: '500rpx',
+                        top: '864rpx',
+                        width: '178rpx',
+                        height: '178rpx'
+                    }
+                }
+            ]
+		}
+    }
+},
+methods: {
+    saveImage() {
+        this.isShowPopup = false
+        uni.saveImageToPhotosAlbum({
+            filePath: this.path,
+            success(res) {
+                uni.showToast({
+                    title: '已保存到相册',
+                    icon: 'success',
+                    duration: 2000
+                })
+            }
+        })
+    },
+}
+}
+
+```
+
+### API
+
+#### Props
+
+| 参数          | 说明         | 类型             | 默认值       |
+| ------------- | ------------ | ---------------- | ------------ |
+| board         | 画板对象     | <em>object</em>  | 参数请向下看 |
+| customStyle   | 自定义样式   | <em>string</em>  |              |
+| isRenderImage | 是否生成图片,在`@success`事件接收图片地址 | <em>boolean</em> | `false`      |
+| isH5PathToBase64 | H5端把网络图片转成base64,解决部分跨域问题 | <em>boolean</em> | `false`      |
+| isBase64ToPath | H5端把base64转成网络图片,应用场景不多考虑删除慎用 | <em>boolean</em> | `false`      |
+| sleep | 此参数是为解决图片渲染时不按JSON层次前后渲染。若有此情况请增大数值 | <em>number</em> | `33`  |
+| type | 只对微信小程序可有效 | <em>string</em> | `2d`  |
+| fileType | 生成图片的后缀类型 | <em>string</em> | `png`  |
+| pixelRatio | 生成图片的像素密度,默认为对应手机的像素密度 | <em>number</em> | ``  |
+| width | 画板的宽度,一般只用于通过内部方法时加上 | <em>number</em> | ``  |
+| height | 画板的高度  ,同上 | <em>number</em> | ``  |
+
+<br>
+
+#### Board
+
+| 参数       | 说明                               | 类型            |
+| ---------- | ---------------------------------- | --------------- |
+| width      | 画板的宽度                         | <em>string</em> |
+| height     | 画板的高度                         | <em>string</em> |
+| background | 画板的背景色,支持颜色渐变,但要写百分比及方向如:`background: 'linear-gradient(to right, #ff971b 0%, #ff5000 100%)'`                      | <em>string</em> |
+| views      | 画板的元素集,请向下看各元素的参数 | <em>object</em> |
+
+<br>
+
+#### 块 View
+
+| 参数 | 说明                                                                                                |
+| ---- | --------------------------------------------------------------------------------------------------- |
+| type | 元素类型`view`                                                                                      |
+| css  | 元素的样式,`top`、`left`、`width`、`height`、`background`、`radius`、`border`、`shadow` 、`transform`包含缩放: `scaleX(-1)`、旋转`rotate(50deg)`、倾斜`skewX(50deg)` |
+
+<br>
+
+#### 文本 text
+
+| 参数 | 说明                                                                                                                                                                        |
+| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| type | 元素类型`text`                                                                                                                                                              |
+| text | 文本内容                                                                                                                                                                    |
+| css  | 元素的样式,`top`、`left`、`fontSize`、`fontWeight`、`fontFamily`、`width`、`lineHeight`、`color`、`textDecoration`、`textAlign`:center, left, right、最大行数:`maxLines`、`transform` |
+| rules | 是一个对象 `Object`指定文字颜色, `word`关键词数组、`color`关键词颜色,目前只能定一种颜色。|
+				
+<br>
+
+#### 图片 image
+
+| 参数 | 说明                                                                       |
+| ---- | -------------------------------------------------------------------------- |
+| type | 元素类型`image`                                                            |
+| src  | 图片地址                                                                   |
+| css  | 元素的样式,`top`、`left`、`width`、`height`、`radius`、`border`、`shadow`、`transform` |
+
+<br>
+
+#### 二维码 qrcode
+
+| 参数 | 说明                                                                       |
+| ---- | -------------------------------------------------------------------------- |
+| type | 元素类型`qrcode`, 二维码。[需要自行下载覆盖](https://gitee.com/liangei/lime-painter/blob/master/qrcode.js)。                                                            |
+| text  | 二维码文本内容                                                                   |
+| css  | 元素的样式,`top`、`left`、`width`、`height`、`border`、`transform`、`backgroundColor`背景色、`color` 前景色|
+
+<br>
+
+#### 事件 Events
+
+| 事件名  | 说明         | 回调           |
+| ------- | ------------ | -------------- |
+| success | 生成图片成功,若使用了`isRenderImage`可以接收图片地址 |	event      |
+| fail    | 生成图片失败 | {error: error} |
+| done    | 绘制成功 |  |
+
+### 常见问题
+> 1、H5端使用网络图片需要解决跨域问题、或者添加`isH5PathToBase64`可解决部分问题。<br>
+> 2、小程序使用网络图片需要去公众平台增加下载白名单!二级域名也需要配!<br>
+> 3、H5端生成图片是base64,有时显示只有一半可以使用原生标签`<IMG/>`<br>
+> 4、发生保存图片倾斜变形或提示native buffer exceed size limit时,使用pixel-ratio="2"参数,降分辨率。<br>
+> 5、h5保存图片不需要调接口,提示用户长按图片保存。<br>
+> 6、IOS APP 请勿使用HBX2.9.3.20201014的版本!这个版本无法生成图片。<br>
+> 7、不生成图片或不绘制时,请查看是否把组件隐藏了!<br>
+> 8、APP端无成功反馈、也无失败反馈时,请更新基座和HBX。<br>
+> 9、微信小程序2D不支持真机调试,请使用真机预览方式。<br>
+>10、华为手机APP上无法生成图片,请使用HBX2.9.11++<br>
+>11、苹果微信7.0.20存在闪退和图片无法onload为微信bug,请到码云上升级本插件<br>
+### 打赏
+如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。<br>
+![输入图片说明](https://images.gitee.com/uploads/images/2020/1122/222521_bb543f96_518581.jpeg "微信图片编辑_20201122220352.jpg")

+ 78 - 0
xiaochengxu/components/lime-painter/canvas.js

@@ -0,0 +1,78 @@
+import { CHAR_WIDTH_SCALE_MAP } from './utils'
+const _expand = ctx => {
+	// #ifndef APP-PLUS
+	ctx._measureText = ctx.measureText
+	// #endif
+	return {
+		setFonts({fontFamily: ff = 'sans-serif', fontSize: fs = 14, fontWeight: fw = 'normal' , textStyle: ts = 'normal'}) {
+			// let ctx = this.ctx
+			// 设置属性
+			// #ifndef MP-TOUTIAO
+			// fw = fw == 'bold' ? 'bold' : 'normal'
+			// ts = ts == 'italic' ? 'italic' : 'normal'
+			// #endif
+			// #ifdef MP-TOUTIAO
+			fw = fw == 'bold' ? 'bold' : ''
+			ts =  ts == 'italic' ? 'italic' : ''
+			// #endif
+			ctx.font = `${ts} ${fw} ${fs}px ${ff}`;
+		},
+		// #ifndef APP-PLUS
+		measureText(text, fontSize) {
+			// app measureText为0需要累加计算0
+			return {
+				width: text.split("").reduce((widthScaleSum, char) => {
+				let code = char.charCodeAt(0);
+				let widthScale = CHAR_WIDTH_SCALE_MAP[code - 0x20] || 1;
+				return widthScaleSum + widthScale;
+			  }, 0) * fontSize
+			};
+		},
+		// #endif
+	}
+};
+export function expand(ctx) {
+	return Object.assign(ctx, _expand(ctx))
+}
+export function adaptor(ctx) {
+	return Object.assign(ctx, _expand(ctx), {
+		setStrokeStyle(val) {
+			ctx.strokeStyle = val;
+		},
+		setLineWidth(val) {
+			ctx.lineWidth = val;
+		},
+		setLineCap(val) {
+			ctx.lineCap = val;
+		},
+		setFillStyle(val) {
+			ctx.fillStyle = val;
+		},
+		setFontSize(val) {
+			ctx.font = String(val);
+		},
+		setGlobalAlpha(val) {
+			ctx.globalAlpha = val;
+		},
+		setLineJoin(val) {
+			ctx.lineJoin = val;
+		},
+		setTextAlign(val) {
+			ctx.textAlign = val;
+		},
+		setMiterLimit(val) {
+			ctx.miterLimit = val;
+		},
+		setShadow(offsetX, offsetY, blur, color) {
+			ctx.shadowOffsetX = offsetX;
+			ctx.shadowOffsetY = offsetY;
+			ctx.shadowBlur = blur;
+			ctx.shadowColor = color;
+		},
+		setTextBaseline(val) {
+			ctx.textBaseline = val;
+		},
+		createCircularGradient() {},
+		draw() {},
+	});
+}

+ 694 - 0
xiaochengxu/components/lime-painter/draw.js

@@ -0,0 +1,694 @@
+import { toPx, isNumber, getImageInfo  } from './utils'
+import { GD } from './gradient'
+import QR from './qrcode'
+let dtheight = 0
+export class Draw {
+	constructor(context, canvas, use2dCanvas = false, isH5PathToBase64 = false, sleep) {
+		this.ctx = context
+		this.canvas = canvas || null
+		this.use2dCanvas = use2dCanvas
+		this.isH5PathToBase64 = isH5PathToBase64
+		this.sleep = sleep
+	}
+	roundRect(x, y, w, h, r, fill = false, stroke = false, ) {
+		if (r < 0) return
+		const {ctx} = this
+		ctx.beginPath()
+		if(!r) {
+			ctx.rect(x, y, w, h)
+		} else if(typeof r === 'number' && [0,1,-1].includes(w - r * 2) &&  [0, 1, -1].includes(h - r * 2)) {
+			ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 2)
+		} else {
+			let {
+				borderTopLeftRadius: tl = r || 0,
+				borderTopRightRadius: tr = r || 0,
+				borderBottomRightRadius: br = r || 0,
+				borderBottomLeftRadius: bl = r || 0
+			} = r || {r,r,r,r}
+			// 右下角
+			ctx.arc(x + w - br, y + h - br, br, 0, Math.PI * 0.5)
+			ctx.lineTo(x + bl, y + h)
+			// 左下角
+			ctx.arc(x + bl, y + h - bl, bl, Math.PI * 0.5, Math.PI)
+			ctx.lineTo(x, y + tl)
+			// 左上角
+			ctx.arc(x + tl, y + tl, tl, Math.PI, Math.PI * 1.5)
+			ctx.lineTo(x + w - tr, y)
+			// 右上角
+			ctx.arc(x + w - tr, y + tr, tr, Math.PI * 1.5, Math.PI * 2)
+			ctx.lineTo(x + w, y + h - br)
+		}
+		ctx.closePath()
+		if (stroke) ctx.stroke()
+		if (fill) ctx.fill()
+	}
+	setTransform(box, {transform}) {
+		const {ctx} = this
+		let {
+			scaleX = 1,
+			scaleY = 1,
+			translateX = 0,
+			translateY = 0,
+			rotate = 0,
+			skewX = 0,
+			skewY = 0
+		} = transform || {}
+		let {
+			left: x,
+			top: y,
+			width: w,
+			height: h
+		} = box
+		translateX = toPx(translateX, w) || 0
+		translateY = toPx(translateY, h) || 0
+		ctx.scale(scaleX, scaleY)
+		ctx.translate(
+			w * (scaleX > 0 ? 1 : -1) / 2 + (x + translateX) / scaleX,  
+			h * (scaleY > 0 ? 1 : -1) / 2 + (y + translateY) / scaleY) 
+		
+		if(rotate) {
+			ctx.rotate(rotate * Math.PI / 180)
+		}
+		if(skewX || skewY) {
+			ctx.transform(1, Math.tan(skewY * Math.PI/180), Math.tan(skewX * Math.PI/180), 1 , 0, 0)
+		}
+	}
+	setBackground(bg , w, h) {
+		const {ctx} = this
+		if (!bg) {
+			// #ifndef MP-TOUTIAO || MP-BAIDU
+			ctx.setFillStyle('transparent')
+			// #endif
+			// #ifdef MP-TOUTIAO || MP-BAIDU
+			ctx.setFillStyle('rgba(0,0,0,0)')
+			// #endif
+		} else if(GD.isGradient(bg)) {
+			GD.doGradient(bg, w, h, ctx);
+		} else {
+			ctx.setFillStyle(bg)
+		}
+	}
+	setShadow({boxShadow: bs = []}) {
+		const {ctx} = this
+		if (bs.length) {
+			const [x, y, b, c] = bs
+			ctx.setShadow(x, y, b, c)
+		}
+	}
+	setBorder(box, style) {
+		const {ctx} = this
+		let {
+			left: x,
+			top: y,
+			width: w,
+			height: h
+		} = box
+		const {border, borderBottom, borderTop, borderRight, borderLeft, borderRadius: r, opacity = 1} = style;
+		const {
+			borderWidth : bw = 0,
+			borderStyle : bs,
+			borderColor : bc,
+		} = border || {}
+		const {
+			borderBottomWidth : bbw = bw,
+			borderBottomStyle : bbs = bs,
+			borderBottomColor : bbc= bc,
+		} = borderBottom || {}
+		const {
+			borderTopWidth: btw = bw,
+			borderTopStyle: bts = bs,
+			borderTopColor: btc = bc,
+		} = borderTop || {}
+		const {
+			borderRightWidth: brw = bw,
+			borderRightStyle: brs = bs,
+			borderRightColor: brc = bc,
+		} = borderRight || {}
+		const {
+			borderLeftWidth: blw = bw,
+			borderLeftStyle: bls = bs,
+			borderLeftColor: blc  = bc,
+		} = borderLeft || {}
+		
+		let {
+			borderTopLeftRadius: tl = r || 0,
+			borderTopRightRadius: tr = r || 0,
+			borderBottomRightRadius: br = r || 0,
+			borderBottomLeftRadius: bl = r || 0
+		} = r || {r,r,r,r}
+		if(!borderBottom && !borderLeft && !borderTop && !borderRight && !border) return;
+		const _borderType = (w, s, c) => {
+			// #ifndef APP-NVUE
+			if (s == 'dashed') {
+				// #ifdef MP
+				ctx.setLineDash([Math.ceil(w * 4 / 3), Math.ceil(w * 4 / 3)])
+				// #endif
+				// #ifndef MP
+				ctx.setLineDash([Math.ceil(w * 6), Math.ceil(w * 6)])
+				// #endif
+			} else if (s == 'dotted') {
+				ctx.setLineDash([w, w])
+			}
+			// #endif
+			ctx.setStrokeStyle(c)
+		}
+		const _setBorder = (x1, y1, x2, y2, x3, y3, r1, r2, p1, p2, p3,  bw, bs, bc) => {
+			ctx.save()
+			this.setOpacity(style)
+			this.setTransform(box, style)
+			ctx.setLineWidth(bw)
+			_borderType(bw, bs, bc)
+			ctx.beginPath()
+			ctx.arc(x1, y1, r1, Math.PI * p1, Math.PI * p2)
+			ctx.lineTo(x2, y2)
+			ctx.arc(x3, y3, r2, Math.PI * p2, Math.PI * p3)
+			ctx.stroke()
+			ctx.restore()
+		}
+	
+		if(border) {
+			ctx.save()
+			this.setOpacity(style)
+			this.setTransform(box, style)
+			ctx.setLineWidth(bw)
+			_borderType(bw, bs, bc)
+			this.roundRect(-w/2, -h/2, w, h, r, false, bc ? true : false)
+			ctx.restore()
+		}
+		x = -w/2
+		y = -h/2
+		if(borderBottom) {
+			_setBorder(x + w - br, y + h - br, x + bl, y + h, x + bl, y + h - bl, br, bl, 0.25, 0.5, 0.75, bbw, bbs, bbc)
+		}
+		if(borderLeft) {
+			// 左下角
+			_setBorder(x + bl, y + h - bl, x, y + tl, x + tl, y + tl, bl, tl, 0.75, 1, 1.25, blw, bls, blc)
+		}
+		if(borderTop) {
+			// 左上角
+			_setBorder(x + tl, y + tl, x + w - tr, y, x + w - tr, y + tr, tl, tr, 1.25, 1.5, 1.75, btw, bts, btc)
+		}
+		if(borderRight) {
+			// 右上角
+			_setBorder(x + w - tr, y + tr, x + w, y + h - br, x + w - br, y + h - br, tr, br, 1.75, 2, 0.25, btw, bts, btc)
+		}
+	}
+	setOpacity({opacity = 1}) {
+		this.ctx.setGlobalAlpha(opacity)
+	}
+	drawView(box, style) {
+		const {ctx} = this
+		const {
+			left: x,
+			top: y,
+			width: w,
+			height: h
+		} = box
+		let {
+			borderRadius = 0,
+			border,
+			borderTop,
+			borderBottom,
+			borderLeft,
+			borderRight,
+			color = '#000000',
+			backgroundColor: bg,
+			rotate,
+			shadow
+		} = style || {}
+		ctx.save()
+		this.setOpacity(style)
+		this.setTransform(box, style)
+		this.setShadow(style)
+		this.setBackground(bg, w, h)
+		this.roundRect(-w/2, -h/2, w, h, borderRadius, true, false)
+		ctx.restore()
+		this.setBorder(box, style)
+	}
+	async drawImage(img, box = {}, style = {}, custom = true) {
+		await new Promise(async (resolve, reject) => {
+			const {ctx} = this
+			const canvas = this.canvas
+			let {
+				borderRadius = 0,
+				mode,
+				padding = {},
+				backgroundColor: bg,
+			} = style
+			const {paddingTop: pt = 0, paddingLeft: pl= 0, paddingRight: pr= 0, paddingBottom: pb = 0} = padding
+			let {
+				left: x,
+				top: y,
+				width: w,
+				height: h
+			} = box
+			ctx.save()
+			if(!custom) {
+				this.setOpacity(style)
+				this.setTransform(box, style)
+				if(bg) {
+					this.setBackground(bg, w, h)
+				}
+				this.setShadow(style)
+				x = -w/2
+				y = -h/2
+				this.roundRect(x, y, w, h, borderRadius, borderRadius ? true : false, false)
+			}
+			ctx.clip()
+			const _modeImage = (img) => {
+				x += pl
+				y += pt
+				w = w - pl - pr
+				h = h - pt - pb
+				// 获得图片原始大小
+				let rWidth = img.width
+				let rHeight = img.height
+				let startX = 0
+				let startY = 0
+				// 绘画区域比例
+				const cp = w / h
+				// 原图比例
+				const op = rWidth / rHeight
+				if(!img.width) {
+					mode = 'scaleToFill'
+				}
+				switch(mode) {
+					case 'scaleToFill': 
+						ctx.drawImage(img.src, x, y, w, h);
+						break;
+					case 'aspectFit': 	
+						if(cp >= op) {
+							rWidth = h * op;
+							rHeight = h
+							startX = x + Math.round(w - rWidth) / 2 
+							startY = y
+						} else {
+							rWidth = w
+							rHeight = w / op;
+							startX = x
+							startY = y + Math.round(h - rHeight) / 2 
+						}
+						ctx.drawImage(img.src, startX, startY, rWidth, rHeight);
+						break;
+					case 'aspectFill': 
+						if (cp >= op) {
+							rHeight = rWidth / cp;
+							// startY = Math.round((h - rHeight) / 2)
+						} else {
+							rWidth = rHeight * cp;
+							startX = Math.round(((img.width || w) - rWidth) / 2)
+						}
+						// 百度小程序 开发工具 顺序有问题 暂不知晓真机
+						// #ifdef MP-BAIDU
+						ctx.drawImage(img.src, x, y, w, h, startX, startY, rWidth, rHeight)
+						// #endif
+						// #ifndef MP-BAIDU
+						ctx.drawImage(img.src, startX, startY, rWidth, rHeight, x, y, w, h)
+						// #endif
+						 break;
+					default: 
+						ctx.drawImage(img.src, x, y, w, h);
+				}
+			}
+			const _restore = () => {
+				ctx.restore()
+				this.setBorder(box, style)
+				setTimeout(() => {
+					resolve(true)
+				}, this.sleep)
+			}
+			const _drawImage = (img) => {
+				if (this.use2dCanvas) {
+					const Image = canvas.createImage()
+					Image.onload = () => {
+						img.src = Image
+						_modeImage(img)
+						_restore()
+					}
+					Image.onerror = () => {
+						console.error(`createImage fail: ${JSON.stringify(img)}`)
+						reject(new Error(`createImage fail: ${JSON.stringify(img)}`))
+					}
+					Image.src = img.src
+				} else {
+					_modeImage(img)
+					_restore()
+				}
+			}
+			
+			if(typeof img === 'string') {
+				const {path: src, width, height} = await getImageInfo(img, this.isH5PathToBase64)
+				_drawImage({src, width, height})
+			} else {
+				_drawImage(img)
+			}
+		})
+	}
+	drawText(text, box, style, rules) {
+	console.log(text, box, style, rules)
+		const {ctx} = this
+		let {
+			left: x,
+			top: y,
+			width: w,
+			height: h,
+			offsetLeft: ol = 0
+		} = box
+		let {
+			color = '#000000',
+			lineHeight = '1.4em',
+			fontSize = 14,
+			fontWeight,
+			fontFamily,
+			textStyle,
+			textAlign = 'left',
+			verticalAlign: va = 'top',
+			backgroundColor: bg,
+			maxLines,
+			display,
+			padding = {},
+			borderRadius = 0,
+			textDecoration: td
+		} = style
+		const {paddingTop: pt = 0, paddingLeft: pl = 0} = padding
+		lineHeight = toPx(lineHeight, fontSize)
+		if (!text) return
+		ctx.save()
+		this.setOpacity(style)
+		this.setTransform(box, style)
+		x = -w/2
+		y = -h/2
+		ctx.setTextBaseline(va)
+		ctx.setFonts({fontFamily, fontSize, fontWeight, textStyle})
+		ctx.setTextAlign(textAlign)
+		
+		if(bg) {
+			this.setBackground(bg, w, h)
+			this.roundRect(x, y, w, h, borderRadius, 1, 0)
+		 }
+		 if(display && display.includes('lock')) {
+			x += pl
+			y += pt 
+		 }
+		this.setShadow(style)
+		ctx.setFillStyle(color)
+		let rulesObj = {};
+		if(rules) {
+			if (rules.word.length > 0) {
+				for (let i = 0; i < rules.word.length; i++) {
+					let startIndex = 0,
+						index;
+					while ((index = text.indexOf(rules.word[i], startIndex)) > -1) {
+						rulesObj[index] = { 
+							reset: true
+						};
+						for (let j = 0; j < rules.word[i].length; j++) {
+							rulesObj[index + j] = { 
+								reset: true
+							};
+						}
+						startIndex = index + 1;
+					}
+				}
+			}
+		}
+		// 水平布局
+		switch (textAlign) {
+			case 'left':
+				break
+			case 'center':
+				x += 0.5 * w
+				break
+			case 'right':
+				x += w
+				break
+			default:
+				break
+		}
+		const textWidth = ctx.measureText(text, fontSize).width
+		const actualHeight = Math.ceil(textWidth / w) * lineHeight
+		let paddingTop = Math.ceil((h - actualHeight) / 2)
+		if (paddingTop < 0) paddingTop = 0
+		// 垂直布局
+		switch (va) {
+			case 'top':
+				break
+			case 'middle':
+				y += fontSize / 2
+				break
+			case 'bottom':
+				y += fontSize 
+				break
+			default:
+				break
+		}
+		
+		// 绘线
+		const _drawLine = (x, y, textWidth) => {
+			const { system } = uni.getSystemInfoSync()
+			if(/win|mac/.test(system)){
+				y += (fontSize / 3)
+			}
+			// 垂直布局
+			switch (va) {
+				case 'top':
+					break
+				case 'middle':
+					y -= fontSize / 2 
+					break
+				case 'bottom':
+					y -= fontSize
+					break
+				default:
+					break
+			}
+			let to = x
+			switch (textAlign) {
+				case 'left':
+					x = x
+					to+= textWidth
+					break
+				case 'center':
+					x = x - textWidth / 2
+					to = x + textWidth
+					break
+				case 'right':
+					to = x
+					x = x - textWidth
+					break
+				default:
+					break
+			}
+			
+			if(td) {
+				ctx.setLineWidth(fontSize / 13);
+				ctx.beginPath();
+				
+				if (/\bunderline\b/.test(td)) {
+					y -= inlinePaddingTop * 0.8
+					ctx.moveTo(x, y);
+					ctx.lineTo(to, y);
+				}
+				
+				if (/\boverline\b/.test(td)) {
+					y += inlinePaddingTop
+					ctx.moveTo(x, y - lineHeight);
+					ctx.lineTo(to, y - lineHeight);
+				}
+				if (/\bline-through\b/.test(td)) {
+					ctx.moveTo(x , y - lineHeight / 2 );
+					ctx.lineTo(to, y - lineHeight /2 );
+				}
+				ctx.closePath();
+				ctx.setStrokeStyle(color);
+				ctx.stroke();
+			}
+		}
+		const _reset = (text, x, y) => {
+			const rs = Object.keys(rulesObj)
+			for (let i = 0; i < rs.length; i++) {
+				const item = rulesObj[rs[i]]
+				// ctx.globalCompositeOperation = "destination-out";
+				ctx.save();
+				ctx.setFillStyle(rules.color);
+				if(item.char) {
+					ctx.fillText(item.char, item.x , item.y)
+				}
+				ctx.restore();
+			}
+		}
+		const _setText = (isReset, char) => {
+			if(isReset) {
+				const t1 = Math.round(ctx.measureText('\u0020', fontSize).width)
+				const t2 = Math.round(ctx.measureText('\u3000', fontSize).width)
+				const width = Math.round(ctx.measureText(char, fontSize).width)
+				let _char = ''
+				let _num = 1
+				if(width == t2){
+					_char ='\u3000'
+					_num = 1
+				} else {
+					_char = '\u0020'
+					_num = Math.ceil(width / t1)
+				}
+				return {char: new Array(_num).fill(_char).join(''), width}
+			} else {
+				return {char}
+			}
+		}
+		const _setRulesObj = (text, index, x, y) => {
+			rulesObj[index].x = x
+			rulesObj[index].y = y
+			rulesObj[index].char = text
+		}
+		const _setRules = (x, rs, text, textWidth, {startIndex = 0, endIndex}) => {
+			let clonetext = text
+			if(/·/.test(text)) {
+				clonetext = clonetext.replace(/·/g, '.')
+				textWidth = ctx.measureText(clonetext, fontSize).width
+			}
+			let _text = text.split('')
+			console.log(_text)
+			let _x = x
+			let first = true
+			for (let i = 0; i < rs.length; i++) {
+				const index = rs[i]
+				const key = index - startIndex
+				const t = _text[key]
+
+				if(t) {
+					let {char, width} = _setText(rulesObj[index], t)
+					_text[key] = char
+					if(first) {
+						first = false
+						let dot = 0
+						let dot2 = 0
+						let num = 0
+						if(textAlign == 'center') {
+							_x = x - 0.5 * (textWidth - width - (dot2 - dot) * num)
+						}
+						if(textAlign == 'right') {
+							 _x = x - textWidth + width + (dot2 - dot) * num
+						}
+					}
+					_setRulesObj(t, index, _x  + ctx.measureText(clonetext.substring(0, key), fontSize).width, y + inlinePaddingTop)
+				} else {
+					continue
+				}
+				
+			}
+			return _text
+		}
+		const inlinePaddingTop = Math.ceil((lineHeight - fontSize) / 2)
+		// 不超过一行
+		if (textWidth + ol <= w && !text.includes('\n')) {
+			x = x + ol
+			const rs = Object.keys(rulesObj)
+			let _text = ''
+			if(rs) {
+				_text = _setRules(x, rs, text, textWidth, {})
+				_reset()
+			}
+			ctx.fillText(_text.join(''), x, y + inlinePaddingTop)
+			y += lineHeight
+			_drawLine(x, y, textWidth)
+			ctx.restore()
+			this.setBorder(box, style)
+			return
+		}
+		// 多行文本
+		const chars = text.split('')
+		const _y = y
+		let _x = x
+		// 逐行绘制
+		let line = ''
+		let lineIndex = 0
+		let startIndex = 0
+		for(let index = 0 ; index <= chars.length; index++){
+			let ch = chars[index] || ''
+			const isLine = ch === '\n'
+			const isRight = ch == ''// index == chars.length
+			ch = isLine ? '' : ch;
+			let textline = line + ch
+			let textWidth = ctx.measureText(textline, fontSize).width
+			// 绘制行数大于最大行数,则直接跳出循环
+			if (lineIndex >= maxLines) {
+				break;
+			}
+			if(lineIndex == 0) {
+				textWidth = textWidth + ol
+				_x = x + ol
+			} else {
+				textWidth = textWidth
+				_x = x
+			}
+			
+			if (textWidth > w || isLine || isRight) {
+				let endIndex = index
+				lineIndex++
+				line = isRight && textWidth <= w ? textline : line
+				if(lineIndex === maxLines && textWidth > w) {
+					while( ctx.measureText(`${line}...`, fontSize).width > w) {
+						if (line.length <= 1) {
+							// 如果只有一个字符时,直接跳出循环
+							break;
+						}
+						line = line.substring(0, line.length - 1);
+					}
+					line += '...'
+				}
+				const rs = Object.keys(rulesObj)
+				let _text = ''
+				if(rs) {
+					_text = _setRules(x, rs, line, textWidth, {startIndex, endIndex})
+					_reset()
+				}
+				ctx.fillText(_text.join(''), _x, y + inlinePaddingTop)
+				y += lineHeight
+				_drawLine(_x, y, textWidth)
+				line = ch
+				startIndex = endIndex + (isLine ? 1 : 0)
+				// if ((y + lineHeight) > (_y + h)) break
+			} else {
+				line = textline
+			}
+		}
+		// const rs = Object.keys(rulesObj)
+		// if(rs) {
+		// 	_reset()
+		// }
+		ctx.restore()
+		this.setBorder(box, style)
+	}
+	async drawNode(element) {
+		console.log(element,'element')
+		const {
+			layoutBox,
+			computedStyle,
+			name,
+			rules,
+			use
+		} = element
+		const {
+			src,
+			text
+		} = element.attributes
+		var that = this
+		if (name === 'view') {
+			this.drawView(layoutBox, computedStyle)
+		} else if (name === 'image' && src) {
+			// console.log(element.attributes, layoutBox, computedStyle)
+			await this.drawImage(element.attributes, layoutBox, computedStyle, false)
+		} else if (name === 'text') {
+			this.drawText(text, layoutBox, computedStyle, rules)
+		} else if (name === 'qrcode') {
+			QR.api.draw(text, that, layoutBox, computedStyle)
+		}
+		if (!element.children) return
+		const childs = Object.values ? Object.values(element.children) : Object.keys(element.children).map((key) => element.children[key]);
+			for (const child of childs) {
+				await this.drawNode(child)
+			}
+		
+	}
+}

+ 107 - 0
xiaochengxu/components/lime-painter/gradient.js

@@ -0,0 +1,107 @@
+/* eslint-disable */
+
+export const GD = {
+	isGradient(bg) {
+		if (bg && (bg.startsWith('linear') || bg.startsWith('radial'))) {
+			return true;
+		}
+		return false;
+	},
+	doGradient(bg, width, height, ctx) {
+		if (bg.startsWith('linear')) {
+			linearEffect(width, height, bg, ctx);
+		} else if (bg.startsWith('radial')) {
+			radialEffect(width, height, bg, ctx);
+		}
+	},
+}
+
+function analizeGrad(string) {
+	const colorPercents = string.substring(0, string.length - 1).split("%,");
+	const colors = [];
+	const percents = [];
+	for (let colorPercent of colorPercents) {
+		colors.push(colorPercent.substring(0, colorPercent.lastIndexOf(" ")).trim());
+		percents.push(colorPercent.substring(colorPercent.lastIndexOf(" "), colorPercent.length) / 100);
+	}
+	return {
+		colors: colors,
+		percents: percents
+	};
+}
+
+function radialEffect(width, height, bg, ctx) {
+	const colorPer = analizeGrad(bg.match(/radial-gradient\((.+)\)/)[1]);
+	const grd = ctx.createCircularGradient(0, 0, width < height ? height / 2 : width / 2);
+	for (let i = 0; i < colorPer.colors.length; i++) {
+		grd.addColorStop(colorPer.percents[i], colorPer.colors[i]);
+	}
+	ctx.setFillStyle(grd);
+}
+
+function analizeLinear(bg, width, height) {
+	const direction = bg.match(/([-]?\d{1,3})deg/);
+	const dir = direction && direction[1] ? parseFloat(direction[1]) : 0;
+	let coordinate;
+	switch (dir) {
+		case 0:
+			coordinate = [0, -height / 2, 0, height / 2];
+			break;
+		case 90:
+			coordinate = [width / 2, 0, -width / 2, 0];
+			break;
+		case -90:
+			coordinate = [-width / 2, 0, width / 2, 0];
+			break;
+		case 180:
+			coordinate = [0, height / 2, 0, -height / 2];
+			break;
+		case -180:
+			coordinate = [0, -height / 2, 0, height / 2];
+			break;
+		default:
+			let x1 = 0;
+			let y1 = 0;
+			let x2 = 0;
+			let y2 = 0;
+			if (direction[1] > 0 && direction[1] < 90) {
+				x1 = (width / 2) - ((width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (
+					90 - direction[1]) * Math.PI * 2 / 360) / 2;
+				y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
+				x2 = -x1;
+				y1 = -y2;
+			} else if (direction[1] > -180 && direction[1] < -90) {
+				x1 = -(width / 2) + ((width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (
+					90 - direction[1]) * Math.PI * 2 / 360) / 2;
+				y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
+				x2 = -x1;
+				y1 = -y2;
+			} else if (direction[1] > 90 && direction[1] < 180) {
+				x1 = (width / 2) + (-(width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (
+					90 - direction[1]) * Math.PI * 2 / 360) / 2;
+				y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
+				x2 = -x1;
+				y1 = -y2;
+			} else {
+				x1 = -(width / 2) - (-(width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 *
+					(90 - direction[1]) * Math.PI * 2 / 360) / 2;
+				y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1;
+				x2 = -x1;
+				y1 = -y2;
+			}
+			coordinate = [x1, y1, x2, y2];
+			break;
+	}
+	return coordinate;
+}
+
+function linearEffect(width, height, bg, ctx) {
+	const param = analizeLinear(bg, width, height);
+	const grd = ctx.createLinearGradient(param[0], param[1], param[2], param[3]);
+	const content = bg.match(/linear-gradient\((.+)\)/)[1];
+	const colorPer = analizeGrad(content.substring(content.indexOf(',') + 1));
+	for (let i = 0; i < colorPer.colors.length; i++) {
+		grd.addColorStop(colorPer.percents[i], colorPer.colors[i]);
+	}
+	ctx.setFillStyle(grd);
+}

+ 300 - 0
xiaochengxu/components/lime-painter/index.vue

@@ -0,0 +1,300 @@
+<template>
+	<canvas 
+		v-if="use2dCanvas" 
+		:id="canvasId" 
+		type="2d" 
+		:style="style"
+		style='position: fixed;
+    top: 100rpx;
+    left: 750rpx;'
+		>
+	</canvas>
+	<canvas
+		v-else 
+		:canvas-id="canvasId" 
+		:style="style" 
+		:id="canvasId" 
+		:width="boardWidth * dpr" 
+		:height="boardHeight * dpr"
+		style='position: fixed;
+		top: 100rpx;
+		left: 750rpx;'
+		>
+	</canvas>
+</template>
+
+<script>
+import { toPx, base64ToPath, compareVersion} from './utils';
+import { Draw } from './draw';
+import { Layout } from './layout';
+import { adaptor, expand } from './canvas';
+export default {
+	// version: '1.5.9.7',
+	name: 'l-painter',
+	props: {
+		board: Object,
+		fileType: {
+			type: String,
+			default: 'png'
+		},
+		quality: {
+			type: Number,
+			default: 1
+		},
+		width: [Number, String],
+		height: [Number, String],
+		pixelRatio: Number,
+		customStyle: String,
+		isRenderImage: Boolean,
+		isBase64ToPath: Boolean,
+		isH5PathToBase64: Boolean,
+		sleep: {
+			type: Number,
+			default: 1000/30
+		},
+		// #ifdef MP-WEIXIN
+		type: {
+			type: String,
+			default: '2d',
+		},
+		// #endif
+		
+	},
+	data() {
+		return {
+			// #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU
+			canvasId: `l-painter${this._uid}`,
+			// #endif
+			// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU
+			canvasId: `l-painter`,
+			// #endif
+			// #ifdef MP-WEIXIN
+			use2dCanvas: true,
+			// #endif
+			// #ifndef MP-WEIXIN
+			use2dCanvas: false,
+			// #endif
+			draw: null,
+			ctx: null,
+			layout: new Layout()
+		};
+	},
+	computed: {
+		newboard() {
+			console.log(1111,'board')
+			return this.board && JSON.parse(JSON.stringify(this.board))
+		},
+		style() {
+			console.log( `width:${this.boardWidth}px; height: ${this.boardHeight}px; ${this.customStyle}`)
+				return `width:${this.boardWidth}px; height: ${this.boardHeight}px; ${this.customStyle}`;
+		},
+		dpr() {
+			return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
+		},
+		boardWidth() {
+			const { width = 200 } = this.board || {};
+			return toPx(this.width || width);
+		},
+		boardHeight() {
+			const { height = 200 } = this.board || {};
+			var h=height.split('r')[0]
+			let height1=uni.getStorageSync('dtheight')?Number(h)+Number(uni.getStorageSync('dtheight'))+'rpx':height
+			
+			return toPx(this.height || height1);
+		}
+	},
+	watch: {
+		style() {
+			// #ifdef MP-WEIXIN
+			if(this.use2dCanvas) {
+				this.inited = false;
+			}
+			// #endif
+			// #ifdef MP-ALIPAY
+			this.inited = false;
+			// #endif
+		},
+	},
+	mounted() {
+		// #ifdef MP-WEIXIN
+		const {SDKVersion, version, platform} = wx.getSystemInfoSync()
+		// ios wx7.0.20 createImage bug
+		this.use2dCanvas = (this.type === '2d' && compareVersion(SDKVersion, '2.9.2') >= 0) && !(/ios/.test(platform) && /7.0.20/.test(version));
+		// #endif
+		this.$watch('board', async (val, old) => {
+			if (JSON.stringify(val) === '{}' || !val) return;
+			this.render();
+		}, {
+			deep: true, 
+			immediate: true,
+			})
+	},
+	methods: {
+		async render(args = {}, single = false) {
+			const isArgsNoEmpty = JSON.stringify(args) != '{}'
+			const ctx = await this.getContext()
+			const { use2dCanvas, boardWidth, boardHeight, board, canvas, isBase64ToPath, isH5PathToBase64, sleep } = this;
+			if (use2dCanvas && !canvas) {
+				return Promise.reject(new Error('render: fail canvas has not been created'));
+			}
+			this.boundary = {
+			  top: 0,
+			  left: 0,
+			  width: boardWidth,
+			  height: boardHeight,
+			}
+			if(!single) {
+				ctx.clearRect(0, 0, boardWidth, boardHeight);
+			}
+			if(!this.draw || isArgsNoEmpty) {
+				this.draw = new Draw(ctx, canvas, use2dCanvas, isH5PathToBase64, sleep);
+			} 
+			this.layout.init(ctx, this.boundary, this.isH5PathToBase64)
+			if(isArgsNoEmpty || board && JSON.stringify(board) != '{}') {
+				this.node = await this.layout.calcNode(isArgsNoEmpty ? args : board)
+			}
+			// console.log(this.node,'node')
+			if(this.node) {
+				await this.draw.drawNode(this.node);
+			}
+			await new Promise(resolve => this.$nextTick(resolve)) 
+			if (!use2dCanvas && !single) {
+				await this.canvasDraw(ctx);
+			}
+			this.$emit('done')
+			if(this.isRenderImage && !single) {
+				this.canvasToTempFilePath()
+				.then(async res => {
+					if(/^data:image\/(\w+);base64/.test(res.tempFilePath) && isBase64ToPath) {
+						const img = await base64ToPath(res.tempFilePath)
+						this.$emit('success', img)
+					} else {
+						this.$emit('success', res.tempFilePath)
+					}
+				})
+				.catch(err => {
+					this.$emit('fail', err)
+					new Error(JSON.stringify(err))
+					console.error(JSON.stringify(err))
+				})
+			}
+			
+			return Promise.resolve({ctx, draw: this.draw});
+		},
+		async custom(cb) {
+			const {ctx, draw} = await this.render({}, true)
+			ctx.save()
+			await cb(ctx, draw)
+			ctx.restore()
+			return Promise.resolve(true);
+		},
+		async single(args = {}) {
+			await this.render(args, true)
+			return Promise.resolve(true);
+		},
+		canvasDraw(flag = false) {
+			const {ctx} = this
+			return new Promise(resolve => {
+				ctx.draw(flag, () => {
+					resolve(true);
+				});
+			});
+		},
+		async getContext() {
+			if(this.ctx && this.inited) {
+				return Promise.resolve(this.ctx)
+			};
+			const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
+			await new Promise(resolve => this.$nextTick(resolve)) 
+			const _getContext = () => {
+				console.log(11111)
+				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
+							}
+							// #ifdef MP-ALIPAY
+							ctx.scale(dpr, dpr);
+							// #endif
+							this.ctx = expand(ctx)
+						}
+					})
+				})
+			}
+			// #ifndef MP-WEIXIN 
+			return _getContext()
+			// #endif
+			
+			if(!use2dCanvas) {
+				return _getContext()
+			}
+			return new Promise(resolve => {
+				uni.createSelectorQuery()
+					.in(this)
+					.select('#l-painter')
+					.node()
+					.exec(res => {
+						const canvas = res[0].node;
+						if(!canvas) {
+							this.use2dCanvas = false;
+							return this.getContext()
+						}
+						const ctx = canvas.getContext(type);
+						if (!this.inited) {
+							this.inited = true;
+							canvas.width = boardWidth * dpr;
+							canvas.height = boardHeight * dpr;
+							this.use2dCanvas = true;
+							this.canvas = canvas
+							ctx.scale(dpr, dpr);
+						}
+						this.ctx = adaptor(ctx)
+						resolve(this.ctx);
+					});
+				
+			});
+		},
+		canvasToTempFilePath(args = {}) {
+		  const {use2dCanvas, canvasId} = this
+		  return new Promise((resolve, reject) => {
+		    let { top: y = 0, left: x = 0, width, height } = this.boundary || this
+			let destWidth = width * this.dpr
+			let destHeight = height * this.dpr
+			// #ifdef MP-ALIPAY
+			width = width * this.dpr
+			height = height * this.dpr
+			// #endif
+		    const copyArgs = {
+		      x,
+		      y,
+		      width,
+		      height,
+		      destWidth,
+		      destHeight,
+		      canvasId,
+		      fileType: args.fileType || this.fileType,
+		      quality: /\d/.test(args.quality) ? args.quality : this.quality,
+		      success: resolve,
+		      fail: reject
+		    }
+		    if (use2dCanvas) {
+		      delete copyArgs.canvasId
+		      copyArgs.canvas = this.canvas
+		    }
+			console.log(this)
+		    uni.canvasToTempFilePath(copyArgs, this)
+		  })
+		}
+	}
+};
+</script>
+
+<style></style>

+ 376 - 0
xiaochengxu/components/lime-painter/layout.js

@@ -0,0 +1,376 @@
+import { toPx, isNumber, getImageInfo  } from './utils'
+let uuid = 0;
+export class Layout {
+	constructor(context, root, isH5PathToBase64) {
+		this.ctx = context
+		this.root = root
+		this.isH5PathToBase64 = isH5PathToBase64
+	}
+	init(context, root, isH5PathToBase64) {
+		this.ctx = context
+		this.root = root
+		this.isH5PathToBase64 = isH5PathToBase64
+	}
+	async getNodeTree(element, parent = {}, index = 0, siblings = [], source) {
+		let computedStyle = Object.assign({}, this.getComputedStyle(element, parent, index));
+		let attributes = await this.getAttributes(element)
+		let node = {
+			id: uuid++,
+			parent,
+			computedStyle,
+			rules: element.rules,
+			attributes: Object.assign({}, attributes),
+			name: element?.type || 'view',
+			use:element?.use || 'view',
+		}
+		if(JSON.stringify(parent) === '{}' && !element.type) {
+			const {left = 0, top = 0, width = 0, height = 0 } = computedStyle
+			node.layoutBox = {left, top, width, height }
+		} else {
+			node.layoutBox = Object.assign({left: 0, top: 0}, this.getLayoutBox(node, parent, index, siblings, source))
+		}
+		if (element?.views) {
+			let childrens = []
+			node.children = []
+			for (let i = 0; i < element.views.length; i++) {
+				console.log(childrens,'childrens')
+				let v = element.views[i]
+				childrens.push(await this.getNodeTree(v, node, i, childrens, element))
+			}
+			 node.children = childrens
+		}
+		return node
+	}
+	getComputedStyle(element, parent = {}, index = 0) {
+		const style = {}
+		const name = element.name || element.type
+		const node = JSON.stringify(parent) == '{}' && !name ? element :  element.css;
+		
+		if(!node) return style
+		const inheritProps = ['color', 'fontSize', 'lineHeight', 'verticalAlign', 'fontWeight', 'textAlign']
+		if(parent.computedStyle) {
+			inheritProps.forEach(prop => {
+				if(node[prop] || parent.computedStyle[prop]) {
+					node[prop] = node[prop] || parent.computedStyle[prop]
+				}
+			})
+		}
+		for (let value of Object.keys(node)) {
+			const item = node[value]
+			if(value == 'views') {continue}
+			if (/^(box)?shadow$/i.test(value)) {
+				let shadows = item.split(' ').map(v => /^\d/.test(v) ? toPx(v) : v)
+				style.boxShadow = shadows
+				continue
+			}
+			if (/^border(?!radius)/i.test(value)) {
+				const prefix = value.match(/^border([BTRLa-z]+)?/)[0]
+				const type = value.match(/[W|S|C][a-z]+/)
+				let v = item.split(' ').map(v => /^\d/.test(v) ? toPx(v) : v)
+				
+				if(v.length > 1) {
+					style[prefix] = {
+						[prefix + 'Width'] : v[0] || 1,
+						[prefix + 'Style'] : v[1] || 'solid',
+						[prefix + 'Color'] : v[2] || 'black'
+					}
+				} else {
+					style[prefix] = {
+						[prefix + 'Width'] :  1,
+						[prefix + 'Style'] : 'solid',
+						[prefix + 'Color'] : 'black'
+					}
+					style[prefix][prefix + type[0]] = v[0]
+				}
+				continue
+			}
+			if (/^background(Color)?$/i.test(value)) {
+				style['backgroundColor'] = item
+				continue
+			}
+			if(/padding|margin|radius/i.test(value)) {
+				let isRadius = value.includes('adius')
+				let prefix = isRadius ? 'borderRadius' : value.match(/[a-z]+/)[0]
+				let pre = [0,0,0,0].map((item, i) => isRadius ? ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'][i] : [prefix + 'Top', prefix + 'Right', prefix + 'Bottom', prefix + 'Left'][i] )
+				if(value === 'padding' || value === 'margin' || value === 'radius' || value === 'borderRadius') {
+					let v = item?.split(' ').map((item) => /^\d/.test(item) && toPx(item, node['width']), []) ||[0];
+					let type = isRadius ? 'borderRadius' : value;
+					if(v.length == 1) {
+						style[type] = v[0]
+					} else {
+						let [t, r, b, l] = v
+						style[type] = {
+							[pre[0]]: t,
+							[pre[1]]: isNumber(r) ? r : t,
+							[pre[2]]: isNumber(b) ? b : t,
+							[pre[3]]: isNumber(l) ? l : r
+						}
+					}
+				} else {
+					if(typeof style[prefix] === 'object') {
+						style[prefix][value] = toPx(item, node['width'])
+					} else {
+						style[prefix] = {
+							[pre[0]]: style[prefix] || 0,
+							[pre[1]]: style[prefix] || 0,
+							[pre[2]]: style[prefix] || 0,
+							[pre[3]]: style[prefix] || 0
+						}
+						style[prefix][value] = toPx(item, node['width'])
+					}
+				}
+				continue
+			}
+			 if(/^(width|height)$/i.test(value)) {
+				if(/%$/.test(item)) {
+					style[value] = toPx(item, parent.layoutBox[value])
+				} else {
+					style[value] = /px|rpx$/.test(item) ? toPx(item) : item
+				}
+				continue
+			}
+			if(/^transform$/i.test(value)) {
+				if(!item){ item = 'translate(0%,0)' }
+				style[value]= {}
+				item.replace(/([a-zA-Z]+)\(([0-9,-\.%rpxdeg\s]+)\)/g, (g1, g2, g3) => {
+					const v = g3.split(',').map(k => k.replace(/(^\s*)|(\s*$)/g,''))
+					const transform = (v, r) => {
+						return v.includes('deg') ? v * 1 : r && !/%$/.test(r) ? toPx(v, r) : v
+					}
+					if(g2.includes('matrix')) {
+						style[value][g2] = v.map(v => v * 1)
+					} else if(g2.includes('rotate')) {
+						style[value][g2] = g3.match(/\d+/)[0] * 1
+					}else if(/[X, Y]/.test(g2)) {
+						style[value][g2] = /[X]/.test(g2) ? transform(v[0], node['width']) : transform(v[0], node['height'])
+					} else {
+						style[value][g2+'X'] = transform(v[0], node['width'] )
+						style[value][g2+'Y'] = transform(v[1] || v[0], node['height'])
+					}
+				})
+				continue
+			}
+			if(/%/.test(item)) {
+				const {width: pw, height: ph, left: pl, top: pt} = parent.layoutBox;
+				const {width: rw, height: rh} = this.root;
+				const isR = style.position == 'relative'
+				
+				if(value == 'width') {
+					style[value] = toPx(item, pw || rw)
+				}else if(value == 'height') {
+					style[value] = toPx(item, ph || rh)
+				}else if(value == 'left') {
+					style[value] = item // isR ? toPx(item, pw) + pl : toPx(item, rw)
+				}else if(value == 'top') {
+					style[value] = item // isR ? toPx(item, ph) + pt : toPx(item, rh)
+				} else {
+					style[value] = toPx(item, node['width'])
+				}
+			} else {
+				style[value] = /px|rpx$/.test(item) ? toPx(item) : /em$/.test(item) && name == 'text'? toPx(item, node['fontSize']) : item
+			}
+		}
+		if(/image/.test(element.name||element.type ) && !style.mode) {
+			style.mode = element.mode || 'scaleToFill' 
+			if((!node.width || node.width == 'auto') && (!node.height || node.width == 'auto') ) {
+				style.mode = ''
+			} 
+		}
+		return style
+	}
+	getLayoutBox(element, parent = {}, index = 0, siblings = [], source = {}) {
+		
+		let box = {}
+		let {name, computedStyle: cstyle, layoutBox, attributes} = element || {}
+		if(!name) return box
+		const {ctx} = this
+		const pbox = parent.layoutBox || this.root
+		const pstyle = parent.computedStyle
+		let { 
+			verticalAlign: v, 
+			left: x, 
+			top: y,
+			width: w,
+			height: h,
+			fontSize = 14,
+			lineHeight = '1.4em',
+			maxLines,
+			fontWeight,
+			fontFamily,
+			textStyle,
+			position,
+			display
+			}  = cstyle;
+		const p = cstyle.padding
+		const m = cstyle.margin
+		const { paddingTop: pt = 0, paddingRight: pr = 0, paddingBottom: pb = 0, paddingLeft: pl = 0, } = cstyle.padding || {p,p,p,p}
+		const { marginTop: mt = 0, marginRight: mr = 0, marginBottom: mb = 0, marginLeft: ml = 0, } = cstyle.margin || {m,m,m,m}
+		const {layoutBox: lbox, computedStyle: ls, name: lname} = siblings[index - 1] || {}
+		const {layoutBox: rbox, computedStyle: rs, name: rname} = siblings[index + 1] || {}
+		
+		const lmb = ls?.margin?.marginBottom || 0
+		const lmr = ls?.margin?.marginRight || 0
+		
+		if(/%$/.test(x)) {
+			x = toPx(x, pbox.width)
+		}
+		if(/%$/.test(y)) {
+			y = toPx(y, pbox.height)
+		}
+		if(position == 'relative') {
+			x += pbox.left
+			y += pbox.top
+		}
+		if(name === 'text') {
+			const text = attributes.text ||''
+			lineHeight = toPx(lineHeight, fontSize)
+			ctx.save()
+			ctx.setFonts({fontFamily, fontSize, fontWeight, textStyle})
+			const isLeft = index == 0 
+			const islineBlock = display === 'inlineBlock'
+			const isblock = display === 'block' || ls?.display === 'block' 
+			const isOnly = isLeft && !rbox || !parent?.id
+			const lboxR = isLeft || isblock ? 0 : lbox.offsetRight || 0
+			let texts = text.split('\n')
+			let lineIndex = 1
+			let line = ''
+			const textIndent = cstyle.textIndent || 0
+			if(!isOnly && !islineBlock) {
+				texts.map((t, i) => {
+					lineIndex += i
+					const chars = t.split('')
+					for (let j = 0; j < chars.length; j++) {
+						let ch = chars[j]
+						let textline = line + ch
+						let textWidth = ctx.measureText(textline, fontSize).width
+						if(lineIndex == 1) {
+							textWidth = textWidth + (isblock ? 0 : lboxR) +  textIndent
+						}
+						if(textWidth > pbox.width) {
+							lineIndex++
+							line = ch
+						} else {
+							line = textline
+						}
+					}
+				})
+			} else {
+				line = text
+				lineIndex = Math.max(texts.length, Math.ceil(ctx.measureText(text, fontSize).width / ((w || pbox.width) - ctx.measureText('!', fontSize).width / 2)))
+			}
+			if(!islineBlock) {
+				box.offsetLeft =  (isNumber(x) || isblock || isOnly ? textIndent : lboxR) +  pl + ml;
+			}
+			
+			// 剩下的字宽度
+			const remain = ctx.measureText(line, fontSize).width
+			let width =  lineIndex > 1 ? pbox.width : remain + (box?.offsetLeft || 0);
+			if(!islineBlock) {
+				box.offsetRight = (x || 0) + box.offsetLeft + (w ? w : (isblock ? pbox.width : remain)) + pr + mr;
+			}
+			const lboxOffset = lbox ? lbox.left + lbox.width : 0;
+			const _getLeft = () => {
+				if(islineBlock) {
+					return (lboxOffset +  width > pbox.width || isLeft ? pbox.left : lboxOffset + lmr ) + ml
+				}
+				return (x || pbox.left)
+			}
+			const _getWidth = () => {
+				if(islineBlock) {
+					return width + pl + pr 
+				}
+				return w || (!isOnly || isblock ? pbox.width : (width > pbox.width - box.left || lineIndex > 1 ?  pbox.width - box.left : width))
+			}
+			const _getHeight = () => {
+				if(h) {
+					return h
+				} else if(lineIndex > 1 ) {
+					return (maxLines || lineIndex) * lineHeight + pt + pb 
+				} else {
+					return lineHeight + pt + pb 
+				}
+			}
+			const _getTop = () => {
+				let _y = y
+				if(_y) {
+					// return _y + pt + mt
+				} else if(isLeft) {
+					_y = pbox.top
+				} else if((lineIndex == 1 && width < pbox.width && lname === 'text' && !isblock && !islineBlock) || lbox.width < pbox.width && !(islineBlock && (lboxOffset +  width > pbox.width))) {
+					_y = lbox.top
+				} else {
+					_y = lbox.top + lbox.height - (ls?.lineHeight || 0)
+				}
+				if (v === 'bottom') {
+					_y = pbox.top + (pbox.height - box.height || 0)
+				}
+				if (v === 'middle') {
+					_y = pbox.top + (pbox.height ? (pbox.height - box.height || 0) / 2 : 0)
+				}
+				return _y + mt + (isblock && ls?.lineHeight || 0 ) + (lboxOffset +  width > pbox.width ? lmb : 0)
+			}
+			box.left = _getLeft() 
+			box.width = _getWidth() 
+			box.height = _getHeight()
+			box.top = _getTop()
+			if(pstyle && !pstyle.height) {
+				pbox.height = box.top - pbox.top + box.height
+			}
+			ctx.restore()
+		} else if(['view', 'qrcode'].includes(name)) {
+			box.left = ( x || pbox.left) + ml - mr
+			box.width = (w || pbox?.width) - pl - pr
+			box.height = (h || 0 ) 
+			if(isNumber(y)) {
+				box.top = y + mt
+			} else {
+				box.top = (lbox && (lbox.top + lbox.height) || pbox.top) + mt + lmb
+			}
+			
+		} else if(name === 'image') {
+			const {
+				width: rWidth,
+				height: rHeight
+			} = attributes
+			const limageOffset = lbox && (lbox.left + lbox.width)
+			if(isNumber(x)) {
+				box.left = x + ml - mr
+			} else {
+				box.left = (lbox && (limageOffset < pbox.width ? limageOffset : pbox.left) || pbox.left) + ml - mr
+			}
+			if(isNumber(w)) {
+				box.width = w // - pl - pr 
+			} else {
+				box.width = Math.round(isNumber(h) ? rWidth * h / rHeight : pbox?.width) // - pl - pr
+			}
+			if(isNumber(h)) {
+				box.height = h
+			} else {
+				const cH = Math.round(box.width * rHeight / rWidth )
+				box.height = Math.min(cH, pbox?.height)
+			}
+			if(isNumber(y)) {
+				box.top = y + mt
+			} else {
+				box.top = (lbox && (limageOffset < pbox.width ? limageOffset : (lbox.top + lbox.height)) || pbox.top) + mt + lmb
+			}
+		}
+		return box
+	}
+	async getAttributes(element) {
+		let arr = { }
+		if(element?.url || element?.src) {
+			arr.src = element.url || element?.src;
+			const {width = 0, height = 0, path: src, url} = await getImageInfo(arr.src, this.isH5PathToBase64) || {}
+			arr = Object.assign({}, arr, {width, height, src, url})
+		}
+		if(element?.text) {
+			arr.text = element.text
+		}
+		return arr
+	}
+	async calcNode(element) {
+		const node = element || this.element
+		return await this.getNodeTree(node)
+	}
+}

+ 791 - 0
xiaochengxu/components/lime-painter/qrcode.js

@@ -0,0 +1,791 @@
+/* eslint-disable */
+!(function () {
+
+  // alignment pattern
+  let adelta = [
+    0, 11, 15, 19, 23, 27, 31,
+    16, 18, 20, 22, 24, 26, 28, 20, 22, 24, 24, 26, 28, 28, 22, 24, 24,
+    26, 26, 28, 28, 24, 24, 26, 26, 26, 28, 28, 24, 26, 26, 26, 28, 28
+  ];
+
+  // version block
+  let vpat = [
+    0xc94, 0x5bc, 0xa99, 0x4d3, 0xbf6, 0x762, 0x847, 0x60d,
+    0x928, 0xb78, 0x45d, 0xa17, 0x532, 0x9a6, 0x683, 0x8c9,
+    0x7ec, 0xec4, 0x1e1, 0xfab, 0x08e, 0xc1a, 0x33f, 0xd75,
+    0x250, 0x9d5, 0x6f0, 0x8ba, 0x79f, 0xb0b, 0x42e, 0xa64,
+    0x541, 0xc69
+  ];
+
+  // final format bits with mask: level << 3 | mask
+  let fmtword = [
+    0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976,    //L
+    0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0,    //M
+    0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed,    //Q
+    0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b    //H
+  ];
+
+  // 4 per version: number of blocks 1,2; data width; ecc width
+  let eccblocks = [
+    1, 0, 19, 7, 1, 0, 16, 10, 1, 0, 13, 13, 1, 0, 9, 17,
+    1, 0, 34, 10, 1, 0, 28, 16, 1, 0, 22, 22, 1, 0, 16, 28,
+    1, 0, 55, 15, 1, 0, 44, 26, 2, 0, 17, 18, 2, 0, 13, 22,
+    1, 0, 80, 20, 2, 0, 32, 18, 2, 0, 24, 26, 4, 0, 9, 16,
+    1, 0, 108, 26, 2, 0, 43, 24, 2, 2, 15, 18, 2, 2, 11, 22,
+    2, 0, 68, 18, 4, 0, 27, 16, 4, 0, 19, 24, 4, 0, 15, 28,
+    2, 0, 78, 20, 4, 0, 31, 18, 2, 4, 14, 18, 4, 1, 13, 26,
+    2, 0, 97, 24, 2, 2, 38, 22, 4, 2, 18, 22, 4, 2, 14, 26,
+    2, 0, 116, 30, 3, 2, 36, 22, 4, 4, 16, 20, 4, 4, 12, 24,
+    2, 2, 68, 18, 4, 1, 43, 26, 6, 2, 19, 24, 6, 2, 15, 28,
+    4, 0, 81, 20, 1, 4, 50, 30, 4, 4, 22, 28, 3, 8, 12, 24,
+    2, 2, 92, 24, 6, 2, 36, 22, 4, 6, 20, 26, 7, 4, 14, 28,
+    4, 0, 107, 26, 8, 1, 37, 22, 8, 4, 20, 24, 12, 4, 11, 22,
+    3, 1, 115, 30, 4, 5, 40, 24, 11, 5, 16, 20, 11, 5, 12, 24,
+    5, 1, 87, 22, 5, 5, 41, 24, 5, 7, 24, 30, 11, 7, 12, 24,
+    5, 1, 98, 24, 7, 3, 45, 28, 15, 2, 19, 24, 3, 13, 15, 30,
+    1, 5, 107, 28, 10, 1, 46, 28, 1, 15, 22, 28, 2, 17, 14, 28,
+    5, 1, 120, 30, 9, 4, 43, 26, 17, 1, 22, 28, 2, 19, 14, 28,
+    3, 4, 113, 28, 3, 11, 44, 26, 17, 4, 21, 26, 9, 16, 13, 26,
+    3, 5, 107, 28, 3, 13, 41, 26, 15, 5, 24, 30, 15, 10, 15, 28,
+    4, 4, 116, 28, 17, 0, 42, 26, 17, 6, 22, 28, 19, 6, 16, 30,
+    2, 7, 111, 28, 17, 0, 46, 28, 7, 16, 24, 30, 34, 0, 13, 24,
+    4, 5, 121, 30, 4, 14, 47, 28, 11, 14, 24, 30, 16, 14, 15, 30,
+    6, 4, 117, 30, 6, 14, 45, 28, 11, 16, 24, 30, 30, 2, 16, 30,
+    8, 4, 106, 26, 8, 13, 47, 28, 7, 22, 24, 30, 22, 13, 15, 30,
+    10, 2, 114, 28, 19, 4, 46, 28, 28, 6, 22, 28, 33, 4, 16, 30,
+    8, 4, 122, 30, 22, 3, 45, 28, 8, 26, 23, 30, 12, 28, 15, 30,
+    3, 10, 117, 30, 3, 23, 45, 28, 4, 31, 24, 30, 11, 31, 15, 30,
+    7, 7, 116, 30, 21, 7, 45, 28, 1, 37, 23, 30, 19, 26, 15, 30,
+    5, 10, 115, 30, 19, 10, 47, 28, 15, 25, 24, 30, 23, 25, 15, 30,
+    13, 3, 115, 30, 2, 29, 46, 28, 42, 1, 24, 30, 23, 28, 15, 30,
+    17, 0, 115, 30, 10, 23, 46, 28, 10, 35, 24, 30, 19, 35, 15, 30,
+    17, 1, 115, 30, 14, 21, 46, 28, 29, 19, 24, 30, 11, 46, 15, 30,
+    13, 6, 115, 30, 14, 23, 46, 28, 44, 7, 24, 30, 59, 1, 16, 30,
+    12, 7, 121, 30, 12, 26, 47, 28, 39, 14, 24, 30, 22, 41, 15, 30,
+    6, 14, 121, 30, 6, 34, 47, 28, 46, 10, 24, 30, 2, 64, 15, 30,
+    17, 4, 122, 30, 29, 14, 46, 28, 49, 10, 24, 30, 24, 46, 15, 30,
+    4, 18, 122, 30, 13, 32, 46, 28, 48, 14, 24, 30, 42, 32, 15, 30,
+    20, 4, 117, 30, 40, 7, 47, 28, 43, 22, 24, 30, 10, 67, 15, 30,
+    19, 6, 118, 30, 18, 31, 47, 28, 34, 34, 24, 30, 20, 61, 15, 30
+  ];
+
+  // Galois field log table
+  let glog = [
+    0xff, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b,
+    0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71,
+    0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45,
+    0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6,
+    0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88,
+    0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40,
+    0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d,
+    0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57,
+    0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18,
+    0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e,
+    0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61,
+    0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2,
+    0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6,
+    0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a,
+    0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7,
+    0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf
+  ];
+
+  // Galios field exponent table
+  let gexp = [
+    0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26,
+    0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0,
+    0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23,
+    0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1,
+    0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0,
+    0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2,
+    0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce,
+    0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc,
+    0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54,
+    0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73,
+    0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff,
+    0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41,
+    0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6,
+    0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09,
+    0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16,
+    0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x00
+  ];
+
+  // Working buffers:
+  // data input and ecc append, image working buffer, fixed part of image, run lengths for badness
+  let strinbuf = [], eccbuf = [], qrframe = [], framask = [], rlens = [];
+  // Control values - width is based on version, last 4 are from table.
+  let version, width, neccblk1, neccblk2, datablkw, eccblkwid;
+  let ecclevel = 2;
+  // set bit to indicate cell in qrframe is immutable.  symmetric around diagonal
+  function setmask(x, y) {
+    let bt;
+    if (x > y) {
+      bt = x;
+      x = y;
+      y = bt;
+    }
+    // y*y = 1+3+5...
+    bt = y;
+    bt *= y;
+    bt += y;
+    bt >>= 1;
+    bt += x;
+    framask[bt] = 1;
+  }
+
+  // enter alignment pattern - black to qrframe, white to mask (later black frame merged to mask)
+  function putalign(x, y) {
+    let j;
+
+    qrframe[x + width * y] = 1;
+    for (j = -2; j < 2; j++) {
+      qrframe[(x + j) + width * (y - 2)] = 1;
+      qrframe[(x - 2) + width * (y + j + 1)] = 1;
+      qrframe[(x + 2) + width * (y + j)] = 1;
+      qrframe[(x + j + 1) + width * (y + 2)] = 1;
+    }
+    for (j = 0; j < 2; j++) {
+      setmask(x - 1, y + j);
+      setmask(x + 1, y - j);
+      setmask(x - j, y - 1);
+      setmask(x + j, y + 1);
+    }
+  }
+
+  //========================================================================
+  // Reed Solomon error correction
+  // exponentiation mod N
+  function modnn(x) {
+    while (x >= 255) {
+      x -= 255;
+      x = (x >> 8) + (x & 255);
+    }
+    return x;
+  }
+
+  let genpoly = [];
+
+  // Calculate and append ECC data to data block.  Block is in strinbuf, indexes to buffers given.
+  function appendrs(data, dlen, ecbuf, eclen) {
+    let i, j, fb;
+
+    for (i = 0; i < eclen; i++)
+      strinbuf[ecbuf + i] = 0;
+    for (i = 0; i < dlen; i++) {
+      fb = glog[strinbuf[data + i] ^ strinbuf[ecbuf]];
+      if (fb != 255)     /* fb term is non-zero */
+        for (j = 1; j < eclen; j++)
+          strinbuf[ecbuf + j - 1] = strinbuf[ecbuf + j] ^ gexp[modnn(fb + genpoly[eclen - j])];
+      else
+        for (j = ecbuf; j < ecbuf + eclen; j++)
+          strinbuf[j] = strinbuf[j + 1];
+      strinbuf[ecbuf + eclen - 1] = fb == 255 ? 0 : gexp[modnn(fb + genpoly[0])];
+    }
+  }
+
+  //========================================================================
+  // Frame data insert following the path rules
+
+  // check mask - since symmetrical use half.
+  function ismasked(x, y) {
+    let bt;
+    if (x > y) {
+      bt = x;
+      x = y;
+      y = bt;
+    }
+    bt = y;
+    bt += y * y;
+    bt >>= 1;
+    bt += x;
+    return framask[bt];
+  }
+
+  //========================================================================
+  //  Apply the selected mask out of the 8.
+  function applymask(m) {
+    let x, y, r3x, r3y;
+
+    switch (m) {
+      case 0:
+        for (y = 0; y < width; y++)
+          for (x = 0; x < width; x++)
+            if (!((x + y) & 1) && !ismasked(x, y))
+              qrframe[x + y * width] ^= 1;
+        break;
+      case 1:
+        for (y = 0; y < width; y++)
+          for (x = 0; x < width; x++)
+            if (!(y & 1) && !ismasked(x, y))
+              qrframe[x + y * width] ^= 1;
+        break;
+      case 2:
+        for (y = 0; y < width; y++)
+          for (r3x = 0, x = 0; x < width; x++ , r3x++) {
+            if (r3x == 3)
+              r3x = 0;
+            if (!r3x && !ismasked(x, y))
+              qrframe[x + y * width] ^= 1;
+          }
+        break;
+      case 3:
+        for (r3y = 0, y = 0; y < width; y++ , r3y++) {
+          if (r3y == 3)
+            r3y = 0;
+          for (r3x = r3y, x = 0; x < width; x++ , r3x++) {
+            if (r3x == 3)
+              r3x = 0;
+            if (!r3x && !ismasked(x, y))
+              qrframe[x + y * width] ^= 1;
+          }
+        }
+        break;
+      case 4:
+        for (y = 0; y < width; y++)
+          for (r3x = 0, r3y = ((y >> 1) & 1), x = 0; x < width; x++ , r3x++) {
+            if (r3x == 3) {
+              r3x = 0;
+              r3y = !r3y;
+            }
+            if (!r3y && !ismasked(x, y))
+              qrframe[x + y * width] ^= 1;
+          }
+        break;
+      case 5:
+        for (r3y = 0, y = 0; y < width; y++ , r3y++) {
+          if (r3y == 3)
+            r3y = 0;
+          for (r3x = 0, x = 0; x < width; x++ , r3x++) {
+            if (r3x == 3)
+              r3x = 0;
+            if (!((x & y & 1) + !(!r3x | !r3y)) && !ismasked(x, y))
+              qrframe[x + y * width] ^= 1;
+          }
+        }
+        break;
+      case 6:
+        for (r3y = 0, y = 0; y < width; y++ , r3y++) {
+          if (r3y == 3)
+            r3y = 0;
+          for (r3x = 0, x = 0; x < width; x++ , r3x++) {
+            if (r3x == 3)
+              r3x = 0;
+            if (!(((x & y & 1) + (r3x && (r3x == r3y))) & 1) && !ismasked(x, y))
+              qrframe[x + y * width] ^= 1;
+          }
+        }
+        break;
+      case 7:
+        for (r3y = 0, y = 0; y < width; y++ , r3y++) {
+          if (r3y == 3)
+            r3y = 0;
+          for (r3x = 0, x = 0; x < width; x++ , r3x++) {
+            if (r3x == 3)
+              r3x = 0;
+            if (!(((r3x && (r3x == r3y)) + ((x + y) & 1)) & 1) && !ismasked(x, y))
+              qrframe[x + y * width] ^= 1;
+          }
+        }
+        break;
+    }
+    return;
+  }
+
+  // Badness coefficients.
+  let N1 = 3, N2 = 3, N3 = 40, N4 = 10;
+
+  // Using the table of the length of each run, calculate the amount of bad image 
+  // - long runs or those that look like finders; called twice, once each for X and Y
+  function badruns(length) {
+    let i;
+    let runsbad = 0;
+    for (i = 0; i <= length; i++)
+      if (rlens[i] >= 5)
+        runsbad += N1 + rlens[i] - 5;
+    // BwBBBwB as in finder
+    for (i = 3; i < length - 1; i += 2)
+      if (rlens[i - 2] == rlens[i + 2]
+        && rlens[i + 2] == rlens[i - 1]
+        && rlens[i - 1] == rlens[i + 1]
+        && rlens[i - 1] * 3 == rlens[i]
+        // white around the black pattern? Not part of spec
+        && (rlens[i - 3] == 0 // beginning
+          || i + 3 > length  // end
+          || rlens[i - 3] * 3 >= rlens[i] * 4 || rlens[i + 3] * 3 >= rlens[i] * 4)
+      )
+        runsbad += N3;
+    return runsbad;
+  }
+
+  // Calculate how bad the masked image is - blocks, imbalance, runs, or finders.
+  function badcheck() {
+    let x, y, h, b, b1;
+    let thisbad = 0;
+    let bw = 0;
+
+    // blocks of same color.
+    for (y = 0; y < width - 1; y++)
+      for (x = 0; x < width - 1; x++)
+        if ((qrframe[x + width * y] && qrframe[(x + 1) + width * y]
+          && qrframe[x + width * (y + 1)] && qrframe[(x + 1) + width * (y + 1)]) // all black
+          || !(qrframe[x + width * y] || qrframe[(x + 1) + width * y]
+            || qrframe[x + width * (y + 1)] || qrframe[(x + 1) + width * (y + 1)])) // all white
+          thisbad += N2;
+
+    // X runs
+    for (y = 0; y < width; y++) {
+      rlens[0] = 0;
+      for (h = b = x = 0; x < width; x++) {
+        if ((b1 = qrframe[x + width * y]) == b)
+          rlens[h]++;
+        else
+          rlens[++h] = 1;
+        b = b1;
+        bw += b ? 1 : -1;
+      }
+      thisbad += badruns(h);
+    }
+
+    // black/white imbalance
+    if (bw < 0)
+      bw = -bw;
+
+    let big = bw;
+    let count = 0;
+    big += big << 2;
+    big <<= 1;
+    while (big > width * width)
+      big -= width * width, count++;
+    thisbad += count * N4;
+
+    // Y runs
+    for (x = 0; x < width; x++) {
+      rlens[0] = 0;
+      for (h = b = y = 0; y < width; y++) {
+        if ((b1 = qrframe[x + width * y]) == b)
+          rlens[h]++;
+        else
+          rlens[++h] = 1;
+        b = b1;
+      }
+      thisbad += badruns(h);
+    }
+    return thisbad;
+  }
+
+  function genframe(instring) {
+    let x, y, k, t, v, i, j, m;
+
+    // find the smallest version that fits the string
+    t = instring.length;
+    version = 0;
+    do {
+      version++;
+      k = (ecclevel - 1) * 4 + (version - 1) * 16;
+      neccblk1 = eccblocks[k++];
+      neccblk2 = eccblocks[k++];
+      datablkw = eccblocks[k++];
+      eccblkwid = eccblocks[k];
+      k = datablkw * (neccblk1 + neccblk2) + neccblk2 - 3 + (version <= 9);
+      if (t <= k)
+        break;
+    } while (version < 40);
+
+    // FIXME - insure that it fits insted of being truncated
+    width = 17 + 4 * version;
+
+    // allocate, clear and setup data structures
+    v = datablkw + (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2;
+    for (t = 0; t < v; t++)
+      eccbuf[t] = 0;
+    strinbuf = instring.slice(0);
+
+    for (t = 0; t < width * width; t++)
+      qrframe[t] = 0;
+
+    for (t = 0; t < (width * (width + 1) + 1) / 2; t++)
+      framask[t] = 0;
+
+    // insert finders - black to frame, white to mask
+    for (t = 0; t < 3; t++) {
+      k = 0;
+      y = 0;
+      if (t == 1)
+        k = (width - 7);
+      if (t == 2)
+        y = (width - 7);
+      qrframe[(y + 3) + width * (k + 3)] = 1;
+      for (x = 0; x < 6; x++) {
+        qrframe[(y + x) + width * k] = 1;
+        qrframe[y + width * (k + x + 1)] = 1;
+        qrframe[(y + 6) + width * (k + x)] = 1;
+        qrframe[(y + x + 1) + width * (k + 6)] = 1;
+      }
+      for (x = 1; x < 5; x++) {
+        setmask(y + x, k + 1);
+        setmask(y + 1, k + x + 1);
+        setmask(y + 5, k + x);
+        setmask(y + x + 1, k + 5);
+      }
+      for (x = 2; x < 4; x++) {
+        qrframe[(y + x) + width * (k + 2)] = 1;
+        qrframe[(y + 2) + width * (k + x + 1)] = 1;
+        qrframe[(y + 4) + width * (k + x)] = 1;
+        qrframe[(y + x + 1) + width * (k + 4)] = 1;
+      }
+    }
+
+    // alignment blocks
+    if (version > 1) {
+      t = adelta[version];
+      y = width - 7;
+      for (; ;) {
+        x = width - 7;
+        while (x > t - 3) {
+          putalign(x, y);
+          if (x < t)
+            break;
+          x -= t;
+        }
+        if (y <= t + 9)
+          break;
+        y -= t;
+        putalign(6, y);
+        putalign(y, 6);
+      }
+    }
+
+    // single black
+    qrframe[8 + width * (width - 8)] = 1;
+
+    // timing gap - mask only
+    for (y = 0; y < 7; y++) {
+      setmask(7, y);
+      setmask(width - 8, y);
+      setmask(7, y + width - 7);
+    }
+    for (x = 0; x < 8; x++) {
+      setmask(x, 7);
+      setmask(x + width - 8, 7);
+      setmask(x, width - 8);
+    }
+
+    // reserve mask-format area
+    for (x = 0; x < 9; x++)
+      setmask(x, 8);
+    for (x = 0; x < 8; x++) {
+      setmask(x + width - 8, 8);
+      setmask(8, x);
+    }
+    for (y = 0; y < 7; y++)
+      setmask(8, y + width - 7);
+
+    // timing row/col
+    for (x = 0; x < width - 14; x++)
+      if (x & 1) {
+        setmask(8 + x, 6);
+        setmask(6, 8 + x);
+      }
+      else {
+        qrframe[(8 + x) + width * 6] = 1;
+        qrframe[6 + width * (8 + x)] = 1;
+      }
+
+    // version block
+    if (version > 6) {
+      t = vpat[version - 7];
+      k = 17;
+      for (x = 0; x < 6; x++)
+        for (y = 0; y < 3; y++ , k--)
+          if (1 & (k > 11 ? version >> (k - 12) : t >> k)) {
+            qrframe[(5 - x) + width * (2 - y + width - 11)] = 1;
+            qrframe[(2 - y + width - 11) + width * (5 - x)] = 1;
+          }
+          else {
+            setmask(5 - x, 2 - y + width - 11);
+            setmask(2 - y + width - 11, 5 - x);
+          }
+    }
+
+    // sync mask bits - only set above for white spaces, so add in black bits
+    for (y = 0; y < width; y++)
+      for (x = 0; x <= y; x++)
+        if (qrframe[x + width * y])
+          setmask(x, y);
+
+    // convert string to bitstream
+    // 8 bit data to QR-coded 8 bit data (numeric or alphanum, or kanji not supported)
+    v = strinbuf.length;
+
+    // string to array
+    for (i = 0; i < v; i++)
+      eccbuf[i] = strinbuf.charCodeAt(i);
+    strinbuf = eccbuf.slice(0);
+
+    // calculate max string length
+    x = datablkw * (neccblk1 + neccblk2) + neccblk2;
+    if (v >= x - 2) {
+      v = x - 2;
+      if (version > 9)
+        v--;
+    }
+
+    // shift and repack to insert length prefix
+    i = v;
+    if (version > 9) {
+      strinbuf[i + 2] = 0;
+      strinbuf[i + 3] = 0;
+      while (i--) {
+        t = strinbuf[i];
+        strinbuf[i + 3] |= 255 & (t << 4);
+        strinbuf[i + 2] = t >> 4;
+      }
+      strinbuf[2] |= 255 & (v << 4);
+      strinbuf[1] = v >> 4;
+      strinbuf[0] = 0x40 | (v >> 12);
+    }
+    else {
+      strinbuf[i + 1] = 0;
+      strinbuf[i + 2] = 0;
+      while (i--) {
+        t = strinbuf[i];
+        strinbuf[i + 2] |= 255 & (t << 4);
+        strinbuf[i + 1] = t >> 4;
+      }
+      strinbuf[1] |= 255 & (v << 4);
+      strinbuf[0] = 0x40 | (v >> 4);
+    }
+    // fill to end with pad pattern
+    i = v + 3 - (version < 10);
+    while (i < x) {
+      strinbuf[i++] = 0xec;
+      // buffer has room    if (i == x)      break;
+      strinbuf[i++] = 0x11;
+    }
+
+    // calculate and append ECC
+
+    // calculate generator polynomial
+    genpoly[0] = 1;
+    for (i = 0; i < eccblkwid; i++) {
+      genpoly[i + 1] = 1;
+      for (j = i; j > 0; j--)
+        genpoly[j] = genpoly[j]
+          ? genpoly[j - 1] ^ gexp[modnn(glog[genpoly[j]] + i)] : genpoly[j - 1];
+      genpoly[0] = gexp[modnn(glog[genpoly[0]] + i)];
+    }
+    for (i = 0; i <= eccblkwid; i++)
+      genpoly[i] = glog[genpoly[i]]; // use logs for genpoly[] to save calc step
+
+    // append ecc to data buffer
+    k = x;
+    y = 0;
+    for (i = 0; i < neccblk1; i++) {
+      appendrs(y, datablkw, k, eccblkwid);
+      y += datablkw;
+      k += eccblkwid;
+    }
+    for (i = 0; i < neccblk2; i++) {
+      appendrs(y, datablkw + 1, k, eccblkwid);
+      y += datablkw + 1;
+      k += eccblkwid;
+    }
+    // interleave blocks
+    y = 0;
+    for (i = 0; i < datablkw; i++) {
+      for (j = 0; j < neccblk1; j++)
+        eccbuf[y++] = strinbuf[i + j * datablkw];
+      for (j = 0; j < neccblk2; j++)
+        eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))];
+    }
+    for (j = 0; j < neccblk2; j++)
+      eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))];
+    for (i = 0; i < eccblkwid; i++)
+      for (j = 0; j < neccblk1 + neccblk2; j++)
+        eccbuf[y++] = strinbuf[x + i + j * eccblkwid];
+    strinbuf = eccbuf;
+
+    // pack bits into frame avoiding masked area.
+    x = y = width - 1;
+    k = v = 1;         // up, minus
+    /* inteleaved data and ecc codes */
+    m = (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2;
+    for (i = 0; i < m; i++) {
+      t = strinbuf[i];
+      for (j = 0; j < 8; j++ , t <<= 1) {
+        if (0x80 & t)
+          qrframe[x + width * y] = 1;
+        do {        // find next fill position
+          if (v)
+            x--;
+          else {
+            x++;
+            if (k) {
+              if (y != 0)
+                y--;
+              else {
+                x -= 2;
+                k = !k;
+                if (x == 6) {
+                  x--;
+                  y = 9;
+                }
+              }
+            }
+            else {
+              if (y != width - 1)
+                y++;
+              else {
+                x -= 2;
+                k = !k;
+                if (x == 6) {
+                  x--;
+                  y -= 8;
+                }
+              }
+            }
+          }
+          v = !v;
+        } while (ismasked(x, y));
+      }
+    }
+
+    // save pre-mask copy of frame
+    strinbuf = qrframe.slice(0);
+    t = 0;           // best
+    y = 30000;         // demerit
+    // for instead of while since in original arduino code
+    // if an early mask was "good enough" it wouldn't try for a better one
+    // since they get more complex and take longer.
+    for (k = 0; k < 8; k++) {
+      applymask(k);      // returns black-white imbalance
+      x = badcheck();
+      if (x < y) { // current mask better than previous best?
+        y = x;
+        t = k;
+      }
+      if (t == 7)
+        break;       // don't increment i to a void redoing mask
+      qrframe = strinbuf.slice(0); // reset for next pass
+    }
+    if (t != k)         // redo best mask - none good enough, last wasn't t
+      applymask(t);
+
+    // add in final mask/ecclevel bytes
+    y = fmtword[t + ((ecclevel - 1) << 3)];
+    // low byte
+    for (k = 0; k < 8; k++ , y >>= 1)
+      if (y & 1) {
+        qrframe[(width - 1 - k) + width * 8] = 1;
+        if (k < 6)
+          qrframe[8 + width * k] = 1;
+        else
+          qrframe[8 + width * (k + 1)] = 1;
+      }
+    // high byte
+    for (k = 0; k < 7; k++ , y >>= 1)
+      if (y & 1) {
+        qrframe[8 + width * (width - 7 + k)] = 1;
+        if (k)
+          qrframe[(6 - k) + width * 8] = 1;
+        else
+          qrframe[7 + width * 8] = 1;
+      }
+    return qrframe;
+  }
+  let _canvas = null;
+  let api = {
+    get ecclevel() {
+      return ecclevel;
+    },
+    set ecclevel(val) {
+      ecclevel = val;
+    },
+    get size() {
+      return _size;
+    },
+    set size(val) {
+      _size = val
+    },
+    get canvas() {
+      return _canvas;
+    },
+    set canvas(el) {
+      _canvas = el;
+    },
+    getFrame(string) {
+      return genframe(string);
+    },
+    //这里的utf16to8(str)是对Text中的字符串进行转码,让其支持中文
+    utf16to8(str) {
+      let out, i, len, c;
+      out = "";
+      len = str.length;
+      for (i = 0; i < len; i++) {
+        c = str.charCodeAt(i);
+        if ((c >= 0x0001) && (c <= 0x007F)) {
+          out += str.charAt(i);
+        } else if (c > 0x07FF) {
+          out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
+          out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
+          out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
+        } else {
+          out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
+          out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
+        }
+      }
+      return out;
+    },
+	draw(str, $this, box, style, ecc) {
+		const {ctx} = $this
+		let {
+			left: startX, 
+			top: startY, 
+			width: cavW, 
+			height: cavH
+		} = box
+		const {
+			borderRadius = 0,
+			backgroundColor: bg,
+			color = '#000000',
+			border
+		} = style
+		const { borderWidth: bw } = border || {}
+      let that = this;
+      ecclevel = ecc || ecclevel;
+      if (!ctx) {
+        console.warn('No canvas provided to draw QR code in!')
+        return;
+      }
+	  ctx.save()
+	  if(bw) {
+		 cavW = cavW - bw
+		 cavH = cavH - bw
+	  }
+      let size = Math.min(cavW, cavH);
+	  //增加中文显示
+      str = that.utf16to8(str);
+
+      let frame = that.getFrame(str);
+      let px = size / width;
+	  $this.setOpacity(style)
+	  $this.setTransform(box, style)
+	  startX = -cavW/2,
+	  startY = -cavH/2
+      if (bg) {
+		  $this.setBackground(bg, cavW, cavH)
+		  $this.roundRect(startX, startY, cavW, cavH, borderRadius, true, false)
+      }
+	  ctx.setFillStyle(color)
+      for (let i = 0; i < width; i++) {
+        for (let j = 0; j < width; j++) {
+          if (frame[j * width + i]) {
+            ctx.fillRect(startX + px * i, startY + px * j, px, px);
+          }
+        }
+      }
+	  ctx.restore()
+	  $this.setBorder(box, style)
+    }
+  }
+  module.exports = { api }
+})();

+ 437 - 0
xiaochengxu/components/lime-painter/utils.js

@@ -0,0 +1,437 @@
+const screen = uni.getSystemInfoSync().windowWidth / 750;
+// 缓存图片
+let cache = {}
+export function isNumber(value) {
+	return /^-?\d+(\.\d+)?$/.test(value);
+}
+export function toPx(value, baseSize) {
+	// 如果是数字
+	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 = Math.floor(value * (screen || 0.5) * 1);
+		} else if (unit === 'px') {
+			res = Math.floor(value * 1);
+		} else if (unit === '%') {
+			res = Math.floor(value * toPx(baseSize) / 100);
+		} else if (unit === 'em') {
+			res = Math.ceil(value * toPx(baseSize || 14));
+		}
+		return res;
+	}
+}
+
+// 计算版本
+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
+}
+
+/** 从 0x20 开始到 0x80 的字符宽度数据 */
+export const CHAR_WIDTH_SCALE_MAP = [0.296, 0.313, 0.436, 0.638, 0.586, 0.89, 0.87, 0.256, 0.334, 0.334, 0.455, 0.742,
+	0.241, 0.433, 0.241, 0.427, 0.586, 0.586, 0.586, 0.586, 0.586, 0.586, 0.586, 0.586, 0.586, 0.586, 0.241, 0.241, 0.742,
+	0.742, 0.742, 0.483, 1.031, 0.704, 0.627, 0.669, 0.762, 0.55, 0.531, 0.744, 0.773, 0.294, 0.396, 0.635, 0.513, 0.977,
+	0.813, 0.815, 0.612, 0.815, 0.653, 0.577, 0.573, 0.747, 0.676, 1.018, 0.645, 0.604, 0.62, 0.334, 0.416, 0.334, 0.742,
+	0.448, 0.295, 0.553, 0.639, 0.501, 0.64, 0.567, 0.347, 0.64, 0.616, 0.266, 0.267, 0.544, 0.266, 0.937, 0.616, 0.636,
+	0.639, 0.64, 0.382, 0.463, 0.373, 0.616, 0.525, 0.79, 0.507, 0.529, 0.492, 0.334, 0.269, 0.334, 0.742, 0.296
+];
+// #ifdef MP
+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
+}
+
+const base64ToArrayBuffer = (data) => {
+	/**
+	 * base64ToArrayBuffer
+	 * Base64Binary.decode(base64_string);  
+	 * Base64Binary.decodeArrayBuffer(base64_string); 
+	 */
+	const Base64Binary = {
+	  _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+	  
+	  /* will return a  Uint8Array type */
+	  decodeArrayBuffer(input) {
+	    const bytes = (input.length/4) * 3;
+	    const ab = new ArrayBuffer(bytes);
+	    this.decode(input, ab);
+	    return ab;
+	  },
+	 
+	  removePaddingChars(input) {
+	    const lkey = this._keyStr.indexOf(input.charAt(input.length - 1));
+	    if(lkey == 64){
+	      return input.substring(0,input.length - 1);
+	    }
+	    return input;
+	  },
+	 
+	  decode(input, arrayBuffer) {
+	    //get last chars to see if are valid
+	    input = this.removePaddingChars(input);
+	    input = this.removePaddingChars(input);
+	 
+	    const bytes = parseInt((input.length / 4) * 3, 10);
+	    
+	    let uarray;
+	    let chr1, chr2, chr3;
+	    let enc1, enc2, enc3, enc4;
+	    let i = 0;
+	    let j = 0;
+	    
+	    if (arrayBuffer)
+	      uarray = new Uint8Array(arrayBuffer);
+	    else
+	      uarray = new Uint8Array(bytes);
+	    
+	    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+	    
+	    for (i=0; i<bytes; i+=3) {  
+	      //get the 3 octects in 4 ascii chars
+	      enc1 = this._keyStr.indexOf(input.charAt(j++));
+	      enc2 = this._keyStr.indexOf(input.charAt(j++));
+	      enc3 = this._keyStr.indexOf(input.charAt(j++));
+	      enc4 = this._keyStr.indexOf(input.charAt(j++));
+	  
+	      chr1 = (enc1 << 2) | (enc2 >> 4);
+	      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+	      chr3 = ((enc3 & 3) << 6) | enc4;
+	  
+	      uarray[i] = chr1;      
+	      if (enc3 != 64) uarray[i+1] = chr2;
+	      if (enc4 != 64) uarray[i+2] = chr3;
+	    }
+	    return uarray;  
+	  }
+	 }
+	return (uni.base64ToArrayBuffer && uni.base64ToArrayBuffer(data)) || Base64Binary.decodeArrayBuffer(data)
+}
+// #endif
+
+/**
+ * base64转路径
+ * @param {Object} base64
+ */
+export function base64ToPath(base64) {
+	const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
+	
+	return new Promise((resolve, reject) => {
+		// #ifdef MP
+		const fs = uni.getFileSystemManager()
+		
+		//自定义文件名
+		if (!format) {
+			console.error('ERROR_BASE64SRC_PARSE')
+			reject(new Error('ERROR_BASE64SRC_PARSE'))
+		}
+		const time = new Date().getTime();
+		let pre = prefix()
+		const filePath = `${pre.env.USER_DATA_PATH}/${time}.${format}`
+		let buffer = base64ToArrayBuffer(bodyData)
+		fs.writeFile({
+			filePath,
+			data: buffer,
+			encoding: 'binary',
+			success() {
+				resolve(filePath)
+			},
+			fail(err) {
+				console.error('获取base64图片失败', JSON.stringify(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) {
+				console.error('ERROR_BASE64SRC_PARSE')
+				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()
+					console.error(`${JSON.stringify(error)}`)
+					reject(error)
+				})
+		}, (error) => {
+			bitmap.clear()
+			console.error(`${JSON.stringify(error)}`)
+			reject(error)
+		})
+		// #endif
+	})
+}
+
+/**
+ * 路径转base64
+ * @param {Object} string
+ */
+
+export function pathToBase64(path) {
+	return new Promise((resolve, reject) => {
+		// #ifdef H5
+		const _canvas = ()=> {
+			let image = new Image();
+			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
+			image.setAttribute("crossOrigin",'Anonymous');
+			image.src = path;
+			image.onerror = (error) => {
+				console.error(`urlToBase64 error: ${path}`, JSON.stringify(error))
+			    reject(new Error('urlToBase64 error'));
+			};
+		}
+		const _fileReader = (blob) => {
+			const fileReader = new FileReader();
+			fileReader.onload = (e) => {
+			    resolve(e.target.result);
+			};
+			fileReader.readAsDataURL(blob);
+			fileReader.onerror = (error) => {
+				console.error('blobToBase64 error:', JSON.stringify(error))
+			    reject(new Error('blobToBase64 error'));
+			};
+		}
+		const isFileReader = typeof FileReader === 'function'
+		if(/^(http|\/\/)/.test(path) && isFileReader ) {
+			window.URL = window.URL || window.webkitURL;
+			const xhr = new XMLHttpRequest();
+			xhr.open("get", path, true);
+			xhr.timeout = 2000;
+			xhr.responseType = "blob";
+			xhr.onload = function() {
+				if(this.status == 200) {
+					_fileReader(this.response)
+				} else {
+					_canvas()
+				}
+			}
+			xhr.onreadystatechange = function() {
+				if(this.status === 0) {
+					console.error('图片跨域了,得后端处理咯')
+				}
+			}
+			xhr.send();
+		} else if(/^blob/.test(path) && isFileReader){
+			_fileReader(path)
+		} else {
+			_canvas()
+		}
+		// #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('urlToBase64 error:', JSON.stringify(error))
+			        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) => {
+					console.error('pathToBase64 error:', JSON.stringify(error))
+		            reject(error)
+		        }
+		        fileReader.readAsDataURL(file)
+		    }, (error) => {
+				console.error('pathToBase64 error:', JSON.stringify(error))
+		        reject(error)
+		    })
+		}, (error) => {
+			console.error('pathToBase64 error:', JSON.stringify(error))
+		    reject(error)
+		})
+		// #endif
+	})
+}
+
+// #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
+
+export function getImageInfo(img, isH5PathToBase64) {
+	return new Promise(async (resolve, reject) => {
+		const base64Reg = /^data:image\/(\w+);base64/
+		const localReg = /^\.|^\/(?=[^\/])/;
+		const networkReg = /^(http|\/\/)/
+		// #ifdef H5
+		if(networkReg.test(img) && isH5PathToBase64) {
+			img = await pathToBase64(img)
+		}
+		// #endif
+		// #ifndef MP-ALIPAY 
+		if(base64Reg.test(img)) {
+			if(!cache[img]) {
+				const imgName = img
+				img = await base64ToPath(img)
+				cache[imgName] = img
+			} else {
+				img = cache[img]
+			}
+		}
+		// #endif
+		if(cache[img] && cache[img].errMsg) {
+			resolve(cache[img])
+		} else {
+			uni.getImageInfo({
+				src: img,
+				success: (image) => {
+					// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
+					image.path = localReg.test(img) ?  `/${image.path}` : image.path;
+					// #endif
+					// image.path = /^(http|\/\/|\/|wxfile|data:image\/(\w+);base64|file|bdfile|ttfile|blob)/.test(image.path) ? image.path : `/${image.path}`;
+					image.url = img
+					cache[img] = image
+					resolve(cache[img])
+				},
+				fail(err) {
+					resolve({path: img})
+					console.error(`getImageInfo:fail ${img} failed ${JSON.stringify(err)}`);
+				}
+			})
+		}
+	})
+}
+	
+	
+	
+export class DataUtil {
+  /**
+   * 设置差异数据
+   * @param component
+   * @param data
+   */
+  setDiffData(component, data) {
+	const diffData = {};
+
+	// 遍历获取到有差异的数据
+	Object.keys(data).forEach(key => {
+	  if (component[key] !== data[key]) {
+		diffData[key] = data[key];
+	  }
+	});
+
+	// 设置数据
+	if (Object.keys(diffData).length) {
+	  // component.setData(diffData);
+	}
+  }
+}
+// const dataUtil = new DataUtil;
+// export dataUtil;

+ 8 - 5
xiaochengxu/components/ossutil/uploadFile.js

@@ -12,7 +12,7 @@ const Crypto = require('./crypto.js');
  *@param - successc:成功回调
  *@param - failc:失败回调
  */ 
-const uploadFile = function (filePath, dir, successc, failc) {
+const uploadFile = function (filePath, dir, successc, failc,type) {
   if (!filePath || filePath.length < 9) {
     uni.showModal({
       title: '图片错误',
@@ -21,9 +21,12 @@ const uploadFile = function (filePath, dir, successc, failc) {
     })
     return;
   }
-  //图片名字 可以自行定义,     这里是采用当前的时间戳 + 150内的随机数来给图片命名的
-  const aliyunFileKey = dir + new Date().getTime() + Math.floor(Math.random() * 150) + '.png';
-  
+   let aliyunFileKey=''
+  if(type=="image"){
+	     aliyunFileKey = dir + new Date().getTime() + Math.floor(Math.random() * 150) + '.png';
+  }else{
+	    aliyunFileKey = dir + new Date().getTime() + Math.floor(Math.random() * 150) + '.mp4';
+  }  
   const aliyunServerURL = env.uploadImageUrl;//OSS地址,需要https
   const accessid = env.OSSAccessKeyId;
   const policyBase64 = getPolicyBase64();
@@ -41,7 +44,7 @@ const uploadFile = function (filePath, dir, successc, failc) {
       'success_action_status': '200',
     },
     success: function (res) {
-			console.log(res);
+			console.log("文件地址",res);
       if (res.statusCode != 200) {
         failc(new Error('上传错误:' + JSON.stringify(res)))
         return;

+ 26 - 0
xiaochengxu/pages.json

@@ -210,6 +210,32 @@
             }
             
         }
+        ,{
+
+            "path" : "pages/mySet/poster",
+            "style" :                                                                                    
+            {
+                "navigationBarTitleText": "生成海报"
+			}
+		},
+		{
+            "path" : "pages/circle/friendSCirlce",
+            "style" :                                                                                    
+            {
+                "navigationBarTitleText": "动态",
+                "enablePullDownRefresh": false
+            }
+            
+        }
+        ,{
+            "path" : "pages/circle/addFriendCirlce",
+            "style" :                                                                                    
+            {
+                "navigationBarTitleText": "添加动态",
+                "enablePullDownRefresh": false
+            }
+            
+        }
     ],
 	"tabBar": {
 		"custom": false,

+ 13 - 2
xiaochengxu/pages/cardHolder/scanCodeAddCard.vue

@@ -338,10 +338,21 @@
 			})
 			that = this
 			console.log(options)
-			this.id = options.id.indexOf(",") ? options.id.split(",")[0] : options.id
+			if(options.id){
+				this.id = options.id.indexOf(",") ? options.id.split(",")[0] : options.id
+			}else if(options.q){
+				var id = decodeURIComponent(options.q).split("=")[1]
+				this.id =id.indexOf(",") ? id.split(",")[0] : id
+			}
 			if (uni.getStorageSync("userInfo").phone) {
 				this.userInfo = uni.getStorageSync("userInfo")
-				// this.commonId = options.id.indexOf(",") ? options.id.split(",")[1] : ''
+				if(options.q){
+					var id = decodeURIComponent(options.q).split("=")[1]
+					this.commonId =id.indexOf(",") ? id.split(",")[1] : ''
+				}else{
+					this.commonId = options.id.indexOf(",") ? options.id.split(",")[1] : ''
+				}
+				
 				
 				this.getList()
 				

+ 155 - 0
xiaochengxu/pages/circle/addFriendCirlce.vue

@@ -0,0 +1,155 @@
+<template>
+	<view class="content">
+		<view class="row1">
+			<u-input placeholder="这一刻得想法..." focus :border="false" v-model="formData.content"></u-input>
+		</view>
+		<view class="row2">
+			<cl-upload v-model="fileList" @onSuccess="onSuccess" @showType='getShowType($event)'></cl-upload>
+		</view>
+		<view class="row3 flex flex-between" @click="selectPlace">
+			<view class="left flex">
+				<u-icon name="map-fill" color="#112253" size="18"></u-icon>
+				<text style="margin:0 20rpx;">{{formData.location?formData.location:'所在位置'}}</text>
+			</view>
+			<view class="right">
+				<u-icon name="arrow-right" color="#112253" size="20"></u-icon>
+			</view>
+		</view>
+		<view class="row4 flex flex-between">
+			<view class="left flex">
+				<u-icon name="pushpin" color="#112253" size="18"></u-icon>
+				<text style="margin:0 20rpx;">开启评论</text>
+				<u-switch v-model="switchStatus" activeColor="#112253" @change="change($event,1)" size="20"></u-switch>
+			</view>
+			<view class="right">
+				<u-icon name="arrow-right" color="#112253" size="20"></u-icon>
+			</view>
+		</view>
+		<view @click="submit" class="submit">提交</view>
+	</view>
+</template>
+
+<script>
+	var that;
+	import uploadImage from '@/components/ossutil/uploadFile.js';
+	export default {
+		data() {
+			return {
+				userInfo: {},
+				switchStatus: true,
+				fileList: [],
+				formData: {
+					commonId: '',
+					head: '',
+					nickname: '',
+					content: '',
+					image: '',
+					location: '',
+					commentFlag: '',
+					positioning:'',
+					circleId:'',
+					mediaType:''
+					
+				}
+			};
+		},
+		onShow() {
+			this.userInfo = uni.getStorageSync("userInfo")
+		},
+		onLoad(options) {
+			that = this
+			this.formData.circleId = options.id
+		},
+		methods: {
+			getShowType(type){debugger
+			console.log("文件类型",type)
+				this.mediaType = type
+			},
+			onSuccess(reslut) {
+				// 把服务端返回的图片地址添加到list中与组件数据同步
+				this.fileList.push(reslut)
+			},
+			submit() {debugger
+				this.formData.commonId = this.userInfo.id
+				this.formData.head = this.userInfo.head
+				this.formData.nickname = this.userInfo.nickname
+				this.formData.image = this.fileList.toString()
+				this.formData.mediaType = this.mediaType
+				if (this.switchStatus) {
+					this.formData.commentFlag  = 1
+				} else {
+					this.formData.commentFlag  = 0
+				}
+				if (!this.formData.content) {
+					uni.showToast({
+						icon: "none",
+						title: '内容不能为空!',
+						duration: 2000
+					});
+					return
+				}
+				this.$request.baseRequest('admin.unimall.circleFriendsInfo', 'add', {
+					circleFriendsInfo: JSON.stringify(this.formData)
+				}, failres => {
+					uni.showToast({
+						icon: "none",
+						title: failres.errmsg,
+						duration: 3000
+					});
+
+					uni.hideLoading()
+				}).then(res => {
+					debugger
+				})
+				uni.showToast({
+					icon: "success",
+					title: '保存成功!',
+					duration: 2000
+				});
+				setTimeout(() => {
+					uni.navigateBack()
+				}, 2000)
+			},
+			selectPlace() {
+				uni.chooseLocation({
+					success: function(res) {
+						that.formData.location = res.address
+						console.log('位置名称:' + res.name);
+						console.log('详细地址:' + res.address);
+						console.log('纬度:' + res.latitude);
+						console.log('经度:' + res.longitude);
+					}
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.content {
+		background-color: #fff;
+		padding: 40rpx;
+
+		.row2,
+		.row3 {
+			border-bottom: 1px solid #E6E6E6;
+			padding: 30rpx 0;
+		}
+
+		.row4 {
+			padding-top: 30rpx;
+		}
+	}
+
+	.submit {
+		position: fixed;
+		bottom: 100rpx;
+		background: #112253;
+		color: #fff;
+		width: calc(100% - 80rpx);
+		padding: 20rpx;
+		box-sizing: border-box;
+		text-align: center;
+		border-radius: 20rpx;
+	}
+</style>

+ 515 - 0
xiaochengxu/pages/circle/circle-item.vue

@@ -0,0 +1,515 @@
+<template>
+	<!-- <mescroll-uni :ref="'mescrollRef' + i" @init="mescrollInit" :height="height" :down="downOption" @down="downCallback" :up="upOption" @up="upCallback" @emptyclick="emptyClick" @scroll="scroll"> -->
+	<view class="u-demo-block">
+		<view class="u-demo-block__content" v-for="(item, index) in list" :key="index" @touchstart="click">
+			<view class="album" style="padding: 30upx;">
+					<image :src="item.head" mode="" style="width: 80upx;height: 80upx;border-radius: 10rpx;"></image>
+				<view class="album__content">
+					<view class="album__info">
+						<text class="info-name" @click="toDetail(item)">{{ item.nickname }}</text>
+						<text class="info-content" @click="toDetail(item)">{{ item.content }}</text>
+						<template v-if="item.mediaType == 1">
+							<u-album v-if="item.urlList && item.urlList.length > 0" :urls="item.urlList" multipleSize="90" space='10'></u-album>
+							<u-album v-else :urls="item.urlList"></u-album>
+						</template>
+						<view v-else class="video">
+								<video v-show="isShowVideo"
+									class="video-info"
+									:show-center-play-btn="false"
+									:src="item.image"
+									id="myVideo"
+									:autoplay="true"
+									:loop="true"
+									:controls= "true"
+								></video>
+							<view v-if="item.direction==1&&!isShowVideo" class="relative">
+								<image :src="item.image + '?x-oss-process=video/snapshot,t_1000,f_jpg,w_800,h_600,m_fast,ar_auto'" mode="aspectFill" style="width: 424rpx;height:320rpx"></image>
+								<image class="cover" src="/static/play.png" @click="playVideo"></image>
+							</view>
+							<view v-if="item.direction==2&&!isShowVideo" class="relative">
+								<image :src="item.image + '?x-oss-process=video/snapshot,t_1000,f_jpg,w_800,h_600,m_fast,ar_auto'" mode="aspectFill" style="width: 320rpx;height:424rpx"></image>
+								<image class="cover1" src="/static/play.png" @click="playVideo"></image>
+							</view>
+						</view>
+					</view>
+					<view class="location" v-if="item.location">
+						<view class="location-left">
+							<text class="location-left-name">{{ item.location }}</text>
+						</view>
+					</view>
+					<view class="time">
+							<text class="time-text">{{$u.timeFrom(new Date(item.gmtCreate).getTime(),'yyyy年mm月dd日')}}</text>
+						</view>
+					<view class="comment">
+						<text class="time-text">{{ item.dateTime }}</text>
+						<view class="comment-right">
+							<view class="comment-item" @click="doThumb(item, index)">
+								<image class="image-love" :src="item.helpFlag==1 ? '../../static/love-fill.png' : '../../static/love.png'"></image>
+								<text class="number">{{ item.count }}</text>
+							</view>
+							<view class="comment-item" @click="doComment(item, null, index)">
+								<image class="image-comment" src="../../static/comment.png"></image>
+								<text class="number">{{ item.count1 }}</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+			<view class="thumb-comment" v-if="item.circleFriendsDetailList&&(item.circleFriendsDetailList.length > 0 || item.comments.circleFriendsDetailList.length > 0)">
+				<view class="thumbs" v-if="item.circleFriendsDetailList&&item.circleFriendsDetailList.length > 0">
+					<image class="image-love thumbs-icon" src="../../static/love.png"></image>
+					<view class="thumbs-headers">
+						<view class="thumbs-headers-img" v-for="(item2, index2) in item.circleFriendsDetailList" :key="index2">
+							<view class="thumbs-item">
+								<text class="thumbs-name">{{ item2.nickname }}</text>
+								<text class="thumbs-name" v-if="index2 != item.circleFriendsDetailList.length - 1">,</text>
+							</view>
+						</view>
+						<text class="thumbs-headers-img thumbs-headers-more" v-if="item.circleFriendsDetailList.length > 20">等</text>
+					</view>
+				</view>
+				<view class="comments" v-if="item.circleFriendsDetailList1&&item.circleFriendsDetailList1.length > 0">
+					<image class="image-comment comments-icon" src="../../static/comment.png"></image>
+					<view class="comments-headers">
+						<view class="comments-headers-item" v-for="(item3, index3) in item.circleFriendsDetailList1" :key="index3">
+							<view class="item-left">
+								<u-avatar :src="item3.head" shape="square" size="32"></u-avatar>
+							</view>
+							<view class="item-right">
+								<view class="item-right-name">
+									<text class="item-right-name-top">{{ item3.nickname }}</text>
+									<text class="item-right-name-bottom">{{$u.timeFrom(new Date(item3.gmtCreate).getTime(),'yyyy年mm月dd日')}}</text>
+								</view>
+								<view class="comment-content">
+									<view class="comment-response" v-if="item3.commentName">
+										<text class="comment-response-txt">回复</text>
+										<text class="comment-response-name">{{ item3.commentName }}</text>
+										<text class="comment-response-txt">:</text>
+									</view>
+									<text class="item-right-info" @click="doComment(item, item3, index)">{{ item3.commentContent }}</text>
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+			<u-line></u-line>
+		</view>
+	</view>
+	<!-- </mescroll-uni> -->
+</template>
+
+<script>
+import { mapState, mapMutations } from 'vuex';
+import MescrollMixin from '@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js';
+import MescrollMoreItemMixin from '@/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js';
+// import { apiGoods } from '@/api/mock.js';
+
+export default {
+	mixins: [MescrollMixin, MescrollMoreItemMixin], // 注意此处还需使用MescrollMoreItemMixin (必须写在MescrollMixin后面)
+	data() {
+		return {
+			isShowVideo:false,
+			videoContext:'',
+			downOption: {
+				auto: false // 不自动加载 (mixin已处理第一个tab触发downCallback)
+			},
+			upOption: {
+				onScroll: true,
+				auto: false, // 不自动加载
+				// page: {
+				// 	num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+				// 	size: 10 // 每页数据的数量
+				// },
+				noMoreSize: 5, //如果列表已无数据,可设置列表的总数量要大于半页才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看; 默认5
+				empty: {
+					tip: '空空如也', // 提示
+					icon: 'http://cdn.uviewui.com/uview/empty/data.png'
+				}
+			},
+			showInput: false
+		};
+	},
+	props: {
+		list: {
+			type: Array,
+			default: function(e) {
+				return [];
+			}
+		}
+	},
+
+	computed: {
+		...mapState(['locateInformation'])
+	},
+
+	created() {},
+
+	filters: {
+		formatDistance(distance) {
+			var strDistance = '';
+			if (distance < 1) {
+				//一公里以内的
+				strDistance = distance * 1000 + 'm';
+			} else {
+				distance = distance.toFixed(1);
+				strDistance = distance + 'km';
+			}
+			return strDistance;
+		}
+	},
+
+	methods: {
+		playVideo(val) {
+			this.isShowVideo = true
+			console.log('--play--');
+			// 点击显示全屏
+			this.videoContext = uni.createVideoContext('myVideo')
+			this.videoContext.requestFullScreen();
+		},
+
+		onJump(url) {
+			uni.navigateTo({
+				url: url
+			});
+		},
+
+		toDetail(item) {
+			uni.navigateTo({
+				url: '/pages/circle/circleDetail?item=' + JSON.stringify(item)
+			});
+		},
+
+		click() {
+			this.$emit('click');
+		},
+
+		doThumb(item, index) {
+			item.index = index;
+			this.$emit('doThumb', item);
+		},
+
+		doComment(item, comment, index) {debugger
+			item.index = index;
+			this.$emit('doComment', item, comment);
+		},
+
+		doComment2Com(item, comment) {
+			this.$emit('doComment2Com', item, comment);
+		}
+	}
+};
+</script>
+<style lang="scss">
+.view {
+	flex-direction: column;
+}
+.album {
+	display: flex;
+	flex-direction: row;
+	align-items: flex-start;
+}
+.album__avatar {
+	background-color: #fff;
+	padding: 5px;
+	border-radius: 3px;
+}
+.album__content {
+	margin-left: 10px;
+	flex: 1;
+}
+.album__info {
+	margin-bottom: 15upx;
+	display: flex;
+	flex-direction: column;
+}
+.info-name {
+	color: #5786cc;
+	font-size: 30upx;
+	font-weight: bold;
+}
+.info-content {
+	color: #333;
+	font-size: 30upx;
+	padding: 5px 0 8px 0;
+}
+.video {
+	position: relative;
+	image {
+		width: 100%;
+		height: 180px;
+	}
+	.cover {
+	width: 50px;
+	    height: 50px;
+	    position: absolute;
+	    z-index: 5;
+	    left: -180rpx;
+	    right: 0;
+	    bottom: 0;
+	    top: 0;
+	    margin: auto;
+	}
+	.cover1 {
+	width: 50px;
+	    height: 50px;
+	    position: absolute;
+	    z-index: 5;
+	    left: -270rpx;
+	    right: 0;
+	    bottom: 0;
+	    top: 0;
+	    margin: auto;
+	}
+}
+.video-info {
+	width: 280px;
+	height: 200px;
+}
+.video-cover {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 270px;
+	height: 200px;
+}
+.location {
+}
+.location-left {
+	display: flex;
+	flex-direction: row;
+}
+.location-left-name {
+	font-size: 24upx;
+	color: #5786cc;
+}
+.location-left-distance {
+	margin-left: 30upx;
+	font-size: 24upx;
+	color: #5786cc;
+}
+
+.location-right {
+	color: #999;
+	font-size: 24upx;
+}
+
+.time-text {
+	color: #666;
+	font-size: 24upx;
+}
+
+.comment {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	margin-top: 15upx;
+}
+
+.comment-right {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+}
+
+.comment-item {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	margin-left: 40upx;
+	color: #666;
+}
+
+.image-love {
+	width: 32upx;
+	height: 32upx;
+	margin-right: 6upx;
+}
+.image-comment {
+	width: 30upx;
+	height: 30upx;
+	margin-right: 8upx;
+}
+
+.number {
+	color: #666;
+	padding-left: 5upx;
+	font-size: 26upx;
+}
+.yzb {
+	margin-right: 8upx;
+}
+.yzb-pinglun1 {
+}
+.thumb-on {
+	font-size: 32upx;
+	color: red;
+}
+.thumb-off {
+	font-size: 32upx;
+}
+
+.thumb-comment {
+	padding: 20upx;
+}
+.thumbs {
+	padding: 20upx;
+	border-radius: 10upx;
+	background-color: #f8f8f8;
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+}
+.thumbs-icon {
+	margin-right: 10upx;
+	color: #666;
+	/* margin-top: 5upx; */
+}
+
+.thumbs-headers {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	overflow: hidden;
+	white-space: nowrap;
+	flex-wrap: wrap;
+	flex: 1;
+}
+.thumbs-headers-img {
+	margin-left: 10upx;
+	margin-bottom: 10upx;
+}
+
+.thumbs-headers-more {
+	font-size: 30upx;
+	color: #999;
+}
+
+.thumbs-item {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+}
+
+.thumbs-name {
+	font-size: 28upx;
+	color: #5786cc;
+}
+
+.comments {
+	margin-top: 1upx;
+	padding: 20upx;
+	border-radius: 10upx;
+	background-color: #f8f8f8;
+	display: flex;
+	flex-direction: row;
+}
+.comments-icon {
+	margin-right: 10upx;
+	color: #666;
+	margin-top: 15upx;
+	font-size: 24upx;
+}
+.comments-headers {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+}
+.comments-headers-item {
+	flex: 1;
+	display: flex;
+	flex-direction: row;
+	/* align-items: center; */
+	margin-bottom: 20upx;
+}
+.item-left {
+	margin: 0 10upx;
+	margin-top: 10upx;
+}
+.item-right {
+	margin-left: 5upx;
+	display: flex;
+	flex: 1;
+	flex-direction: column;
+}
+.item-right-name {
+	display: flex;
+	flex: 1;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+}
+.item-right-name-top {
+	color: #5786cc;
+	font-size: 26upx;
+}
+.item-right-name-bottom {
+	font-size: 24upx;
+	color: #999;
+	margin-top: 10upx;
+}
+.item-right-info {
+	font-size: 28upx;
+	color: #333;
+	flex: 1;
+}
+
+.comment-content {
+	display: flex;
+	flex-direction: row;
+	align-items: flex-start;
+	margin-top: 3upx;
+	flex: 1;
+}
+.comment-response {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	margin-right: 10upx;
+}
+.comment-response-txt {
+	font-size: 28upx;
+}
+.comment-response-name {
+	font-size: 28upx;
+	color: #5786cc;
+	padding: 0 8upx;
+}
+
+.bottom {
+	bottom: 0;
+	position: fixed;
+	height: 120rpx;
+	width: 100%;
+	padding: 0 20rpx;
+	box-sizing: border-box;
+	background-color: #f8f8f8;
+	align-items: center;
+	justify-content: center;
+	display: flex;
+
+	.bottom-bt {
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		input {
+			width: 550rpx;
+			height: 70rpx;
+			padding: 0 15rpx;
+			border-radius: 10rpx;
+			background-color: #fff;
+			margin-right: 20rpx;
+		}
+		button {
+			padding: 0 20rpx;
+			height: 60rpx;
+			line-height: 60rpx;
+			background-color: #12ae85;
+			color: #fff;
+			font-size: 28rpx;
+		}
+	}
+}
+</style>

+ 45 - 9
xiaochengxu/pages/circle/detail.vue

@@ -39,15 +39,19 @@
 						<span @click="changeCardStatus(2)" :class="selectIndex==2?'active':'text'">未交换</span>
 					</view>
 					<view class="right flex">
-						<view class="flex"  v-if="dataObj.circleCardInfo" @click="myCardClick">
+					<!-- 	<view class="flex"  v-if="dataObj.circleCardInfo" @click="myCardClick">
 							<image src="../../static/imgs/cirlce/account.png" mode="widthFix"
 								style="width: 36rpx;margin-right: 15rpx;height: auto;"></image>我的名片
-						</view>
+						</view> -->
 						<view class="">
+							<image src="../../static/imgs/card/share1.png" mode="widthFix"
+								style="width: 50rpx;margin-left: 30rpx;height: 50rpx;" @click="toFriendsCirlce()"></image>
 							<image src="../../static/imgs/card/buju1.png" mode="widthFix"
-								style="width: 50rpx;margin-left: 30rpx;height: 50rpx;" v-if="layout==1" @click="changeLayout(2)"></image>
-								<image src="../../static/imgs/card/buju2.png" mode="widthFix"
-									style="width: 50rpx;margin-left: 30rpx;height: 50rpx;" v-if="layout==2" @click="changeLayout(1)"></image>
+								style="width: 50rpx;margin-left: 30rpx;height: 50rpx;" @click="change()"></image>
+			<!-- 				<image src="../../static/imgs/card/buju1.png" mode="widthFix"
+								style="width: 50rpx;margin-left: 30rpx;height: 50rpx;" v-if="layout==1" @click="changeLayout(2)"></image> -->
+						<!-- 		<image src="../../static/imgs/card/buju2.png" mode="widthFix"
+									style="width: 50rpx;margin-left: 30rpx;height: 50rpx;" v-if="layout==2" @click="changeLayout(1)"></image> -->
 						</view>
 					</view>
 				</view>
@@ -57,7 +61,7 @@
 			<mescroll-uni height='1200' :up="upOption" :down="downOption" ref="mescrollRef" @init="mescrollInit" @up="upCallback" @down="downCallback" >
 			<view class="content3" v-for="(item,index) in changeCardList"
 				:style="item.currentBackground?'background:url('+item.currentBackground+');background-size:100% 100%':''"
-				:key="index" v-if="layout==1">
+				:key="index" v-if="layout">
 				<view class="flex item">
 					<view class="top flex">
 						<view class="left">
@@ -92,7 +96,7 @@
 						@click="changeCard(item)" v-if="item.notDisplay!=1"></image>
 				</view>
 			</view>
-			<view class="layout2" v-if="layout==2">
+			<view class="layout2" v-if="!layout">
 				<view class="list_box">
 					<view class="list-item" v-for="(item,index) in changeCardList" :key="index" @click="itemClick(item,index)">
 						<view class="left">
@@ -122,6 +126,8 @@
 		
 		<u-picker :immediateChange ="true" @cancel="isShowCard=false" :show="isShowCard" :columns="cardList" keyName="cardBusiness"
 			@confirm="cardConfirm"></u-picker>
+			<u-picker :immediateChange ="true" @cancel="changeMore=false" :show="changeMore" :columns="moreList" keyName="name"
+				@confirm="changeMoreConfirm"></u-picker>
 		<u-toast ref="uToast"></u-toast>
 		<u-modal :show="show" :content='content' @confirm="$u.debounce(joinCircle, 500)" showCancelButton
 			@cancel="show=false" @close="show=false" closeOnClickOverlay></u-modal>
@@ -201,7 +207,18 @@
 		mixins: [MescrollMixin], // 使用mixin
 		data() {
 			return {
-				layout:1,
+				moreList:[
+					[
+						{
+							"name":'切换名片'
+						},
+						{
+							"name":'更换布局'
+						}
+					]
+				],
+				changeMore:false,
+				layout:true,
 				showAuthorizeUser: false,
 				showAuthorizePhone: false,
 				customStyleUnOk: {
@@ -286,8 +303,27 @@
 			}
 		},
 		methods: {
+			toFriendsCirlce(){
+				uni.navigateTo({
+					url:"/pages/circle/friendSCirlce?id="+this.dataObj.id
+				})
+			},
+			changeMoreConfirm(val){
+				console.log(val)
+				if(val.value[0].name=="切换名片"){
+					this.isShowCard = true
+					this.isMyCard = true
+				}else{
+					this.layout = !this.layout
+				}
+				
+				this.changeMore = false
+			},
+			change(){
+				this.changeMore = true
+			},
 			changeLayout(type){
-				this.layout = type
+				
 			},
 			//获取昵称输入内容
 			userNameInput(e) {

+ 414 - 0
xiaochengxu/pages/circle/friendSCirlce.vue

@@ -0,0 +1,414 @@
+<template>
+	<view class="u-page">
+		<mescroll-uni :up="upOption" :down="downOption" ref="mescrollRef" @init="mescrollInit" @up="upCallback"
+			@down="downCallback">
+			<circle-item ref="mescrollItem" :list="list" @click="showInput = false" @doThumb="doThumb"
+				@doComment="doComment"></circle-item>
+		</mescroll-uni>
+		<view class="bottom" v-if="showInput">
+			<view class="bottom-bt">
+				<input class="bottom-bt-input" placeholder="请输入内容" v-model="commentValue" />
+				<text class="bottom-bt-button" @click="submitComment">发送</text>
+			</view>
+		</view>
+		<image src="../../static/imgs/cirlce/add.png" mode="widthFix" class="add" @click="addCircle"></image>
+		<u-action-sheet :show="show2" @close="show2 = false" @select="sheetSelect" :actions="actions2"
+			cancelText="取消"></u-action-sheet>
+	</view>
+</template>
+
+<script>
+	var that;
+	import circleItem from './circle-item.vue';
+	import {
+		mapState,
+		mapMutations
+	} from 'vuex';
+	import MescrollMixin from '@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js';
+	import {
+		friendlyDate
+	} from '@/common/util.js';
+
+	export default {
+		mixins: [MescrollMixin],
+		components: {
+			circleItem
+		},
+		data() {
+			return {
+				formData: {
+					circleFriendsId: '',
+					commonId: '',
+					head: '',
+					nickname: '',
+					interactionFlag: '',
+
+				},
+				userInfo: {},
+				circleId: '',
+				downOption: {
+					auto: false,
+					textColor: '#bbb'
+				},
+				upOption: {
+					page: {
+						size: 10 // 每页数据的数量,默认10
+					},
+					auto: false,
+					noMoreSize: 1,
+					textNoMore: '没有更多了~',
+					textColor: '#bbb'
+				},
+				list: [],
+				requestParams: {
+					searchType: 1,
+					latitude: 0,
+					longitude: 0,
+					pageSize: 10,
+					pageNo: 1
+				},
+
+				show2: false,
+				actions2: [{
+					name: '删除'
+				}],
+				showInput: false
+			};
+		},
+		onLoad(options) {
+			that = this
+			this.circleId = options.id
+			this.userInfo = uni.getStorageSync("userInfo")
+			// 需要固定swiper的高度 (需减去悬浮tabs的高度64rpx)
+			// this.height = uni.getSystemInfoSync().windowHeight - uni.upx2px(100) + 'px';
+			// this.tabHeight = uni.upx2px(100) + 'px';
+			this.loadData(true);
+		},
+		methods: {
+			addCircle() {
+				uni.navigateTo({
+					url: "/pages/circle/addFriendCirlce?id=" + this.circleId
+				})
+			},
+			/*下拉刷新的回调 */
+			downCallback() {
+				// this.loadData(true);
+				this.mescroll.resetUpScroll();
+			},
+			/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
+			upCallback(page) {
+				console.log('upCallback=================', page);
+				this.requestParams.pageNo = page.num;
+				this.requestParams.pageSize = page.size;
+				this.loadData();
+			},
+
+			scroll() {},
+
+			change(index) {
+				console.log('change=========', index);
+				this.requestParams.searchType = index + 1;
+				this.loadData(true);
+			},
+			// 处理图片
+			imageInfo(url) {
+				let promise = new Promise(function(resolve, reject) {
+					uni.getImageInfo({
+						src: url,
+						success: function(image) {
+							resolve(image)
+							console.log(image.width);
+							console.log(image.height);
+						}
+					});
+				})
+				return promise
+			},
+			loadData(refresh) {
+				this.$request.baseRequest('admin.unimall.circleFriendsInfo', 'list', {
+					page: this.requestParams.pageNo,
+					limit: this.requestParams.pageSize,
+					circleId: this.circleId,
+					currentCommonId: this.userInfo.id
+				}, failres => {
+					console.log('res+++++', failres.errmsg)
+					uni.showToast({
+						icon: "none",
+						title: failres.errmsg,
+						duration: 3000
+					});
+					uni.hideLoading()
+				}).then(async res => {
+					console.log(res)
+					if (this.requestParams.pageNo.num == 1) this.list = [];
+					let curPageLen = res.data.items.length;
+					let totalPage = res.data.total;
+					this.list = res.data.items
+					for (let i = 0; i < this.list.length; i++) {
+						this.list[i].urlList = this.list[i].image.split(",")
+						if (this.list[i].mediaType == 2) {
+							let _image = await this.imageInfo(this.list[i].image +
+								'?x-oss-process=video/snapshot,t_1000,f_jpg,w_800,h_600,m_fast,ar_auto')
+							console.log("_image", _image)
+							if (_image.width > _image.height) {
+								this.list[i].direction = 1
+							} else {
+								this.list[i].direction = 2
+							}
+						}
+					}
+					this.$nextTick(() => {
+						that.mescroll.endBySize(curPageLen, totalPage)
+					});
+					uni.hideLoading()
+				})
+			},
+
+			getCircleDetail(id, index) {
+				let param = {
+					id: id,
+					searchType: 1,
+					latitude: 0,
+					longitude: 0
+				};
+				if (this.locateInformation.location) {
+					param.latitude = this.locateInformation.location.lat;
+					param.longitude = this.locateInformation.location.lng;
+				}
+				getCircleDetail({
+						params: param
+					})
+					.then(res => {
+						console.log('getCircleDetail====', res);
+						if (res) {
+							let data = res;
+							if (data.mediaType == 1) {
+								let arr = data.url.split(',');
+								if (arr.length > 1) {
+									let list = [];
+									arr.forEach(item2 => {
+										list.push(item2);
+									});
+									data.urlList = list;
+								} else {
+									data.urlList = arr;
+								}
+							}
+							data.dateTime = friendlyDate(new Date(data.createTime.replace(/\-/g, '/')).getTime());
+							this.list[index].thumbNumber = data.thumbNumber;
+							this.list[index].commentNumber = data.commentNumber;
+							this.list[index].thumbed = data.thumbed;
+							this.list[index].thumbs = data.thumbs;
+							this.list[index].comments = data.comments;
+							console.log('data====', data);
+							console.log('index====', index);
+							console.log('this.list====', this.list);
+							this.$forceUpdate();
+						} else {}
+					})
+					.catch(err => {
+						console.log(err, 'catch');
+					});
+			},
+			//点赞
+			async doThumb(item) {
+				if (item.helpFlag == 0) {
+					this.formData = {
+						circleFriendsId: item.id,
+						commonId: this.userInfo.id,
+						head: this.userInfo.head,
+						nickname: this.userInfo.nickname
+					}
+					
+					this.formData.interactionFlag = 1
+					this.$request.baseRequest('admin.unimall.circleFriendsDetail', 'add', {
+						circleFriendsDetail: JSON.stringify(this.formData)
+					}, failres => {
+						console.log('res+++++', failres.errmsg)
+						uni.showToast({
+							icon: "none",
+							title: failres.errmsg,
+							duration: 3000
+						});
+						uni.hideLoading()
+					}).then(async res => {
+						console.log(res)
+						uni.showToast({
+							icon: "success",
+							title: '点赞成功!',
+							duration: 2000
+						});
+						this.loadData()
+					})
+				} else {
+					this.$request.baseRequest('admin.unimall.circleFriendsDetail', 'cancelLike', {
+						circleFriendsId: item.id,
+						commonId: this.userInfo.id,
+					}, failres => {
+						console.log('res+++++', failres.errmsg)
+						uni.showToast({
+							icon: "none",
+							title: failres.errmsg,
+							duration: 3000
+						});
+						uni.hideLoading()
+					}).then(async res => {
+						console.log(res)
+						uni.showToast({
+							icon: "success",
+							title: '取消成功!',
+							duration: 2000
+						});
+						this.loadData()
+					})
+				}
+			},
+
+			doComment(item, comment) {debugger
+				this.selectedComment = comment;
+				if(!this.selectedComment){
+					this.selectedComment.id = ''
+				}
+				this.selectedCircle = item;
+				if (comment != null && comment.userId == this.userInfo.id) {
+					this.show2 = true;
+					return;
+				}
+				this.showInput = !this.showInput;
+				this.commentValue = '';
+				this.$forceUpdate();
+			},
+
+			async submitComment() {
+				debugger
+				// if (!this.commentValue) {
+				// 	uni.$u.toast('请输入内容');
+				// 	return;
+				// }
+				
+				this.formData = {
+					circleFriendsId: this.selectedCircle.id,
+					commonId: this.userInfo.id,
+					head: this.userInfo.head,
+					nickname: this.userInfo.nickname,
+					commentContent:this.commentValue,
+					commentId:this.selectedComment.id,
+					commentName:this.selectedComment.nickname,
+				}
+				
+				this.formData.interactionFlag = 2
+				this.$request.baseRequest('admin.unimall.circleFriendsDetail', 'add', {
+					circleFriendsDetail: JSON.stringify(this.formData)
+				}, failres => {
+					console.log('res+++++', failres.errmsg)
+					uni.showToast({
+						icon: "none",
+						title: failres.errmsg,
+						duration: 3000
+					});
+					uni.hideLoading()
+				}).then(async res => {
+					console.log(res)
+					uni.showToast({
+						icon: "success",
+						title: '评论成功!',
+						duration: 2000
+					});
+					this.loadData()
+				})
+				
+				// let params = {
+				// 	comment: this.commentValue,
+				// 	circleId: this.selectedCircle.id,
+				// 	circleUserId: this.selectedCircle.userId
+				// };
+				// if (this.selectedComment) {
+				// 	let tmp = {
+				// 		pid: this.selectedComment.id,
+				// 		comUserId: this.selectedComment.userId
+				// 	};
+				// 	params = uni.$u.deepMerge(params, tmp);
+				// }
+				// let res = await addComment(params);
+				// if (res) {
+				// 	uni.$u.toast('评论成功');
+				// 	this.getCircleDetail(this.selectedCircle.id, this.selectedCircle.index);
+				// }
+			},
+
+			sheetSelect(val) {
+				console.log('---sheetSelect---');
+				this.doDelComment();
+			},
+
+			async doDelComment() {
+				let params = {
+					id: this.selectedComment.id,
+					circleId: this.selectedComment.circleId
+				};
+				let res = await delComment(params);
+				console.log('doDelComment', res);
+				if (res) {
+					uni.$u.toast('删除成功');
+					this.getCircleDetail(this.selectedCircle.id, this.selectedCircle.index);
+				}
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	.u-swiper {
+		position: relative;
+	}
+
+	.bottom {
+		bottom: 0;
+		position: fixed;
+		height: 120upx;
+		width: 750upx;
+		// padding: 0 20upx;
+		background-color: #eee;
+		align-items: center;
+		justify-content: center;
+		display: flex;
+		z-index: 999;
+	}
+
+	.bottom-bt {
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+	}
+
+	.bottom-bt-input {
+		width: 550upx;
+		height: 70upx;
+		padding: 0 15upx;
+		border-radius: 10upx;
+		background-color: #fff;
+		margin-right: 20upx;
+		font-size: 28upx;
+		color: #333;
+	}
+
+	.bottom-bt-button {
+		padding: 0 20upx;
+		height: 55upx;
+		line-height: 55upx;
+		background-color: #344577;
+		font-size: 28upx;
+		color: #fff;
+		border-radius: 10upx;
+	}
+
+	.add {
+		width: 84rpx;
+		height: auto;
+		position: fixed;
+		bottom: 200rpx;
+		right: 30rpx;
+		height: auto;
+		z-index: 999;
+	}
+</style>

+ 0 - 1
xiaochengxu/pages/mySet/help.vue

@@ -23,7 +23,6 @@
 .bg{
 	height:162rpx;
 	background: linear-gradient(180deg, #112253 0%,  #FCFCFC 100%);
-	
 }
 // .content{
 // 	padding:20rpx;

+ 5 - 0
xiaochengxu/pages/mySet/mySet.vue

@@ -175,6 +175,11 @@
 						type: "contactCustomer",
 						src: '../../static/imgs/mySet/kf.png'
 					},
+					{
+						name: "生成海报",
+						type: "poster",
+						src: '../../static/imgs/mySet/hb.png'
+					},
 					{
 						name: "设置",
 						type: 'set',

+ 1127 - 0
xiaochengxu/pages/mySet/poster.vue

@@ -0,0 +1,1127 @@
+<template>
+	<view class="page">
+		<view class="poster" v-if="!show_poster">
+			<view class="canvas-content" style="text-align:center;position:relative;" :style='{background:(posterObj1.backgroundType=="rgb"? posterObj1.background:"url("+posterObj1.background+") no-repeat;background-size:100% 100%;"),borderRadius:posterObj1.borderRadius,padding:(check_idx==2?"80rpx 68rpx":"40rpx 36rpx 80rpx")}'>
+				<image v-if='check_idx==1' style='position:absolute;width:103rpx;height:103rpx;bottom: 0;left: 0;' src="https://s.yun-live.com/images/20210201/d88d56843d43b917e2a28550b2a62723.png"></image>
+				<image v-if='check_idx==1' style='position:absolute;width:56.4rpx;height:56.4rpx;right:0rpx;top:50%;' src="https://s.yun-live.com/images/20210201/247736ffd279276b891ec14db8ed0fd0.png"></image>
+				<image :style='{width:posterObj1.width,height:posterObj1.height,borderRadius:posterObj1.imageBorderRadius}' :src="posterObj1.image"></image>
+				<view :style='{fontSize:posterObj1.titleCssFontSize,fontWeight:posterObj1.titleCssFontWeight,color:posterObj1.titleCssColor,lineWeight:posterObj1.titleCssLineHeight
+				}' class="title">{{posterObj1.title}}</view>
+				<view class='flex justify-space-between' style='align-items: flex-start;margin-top:18rpx;'>
+					<view style='text-align:left;margin-top:30rpx;'>
+						<view :style='{fontSize:posterObj1.title1CssFontSize,fontWeight:posterObj1.title1CssFontWeight,color:posterObj1.title1CssColor,lineWeight:posterObj1.title1CssLineHeight
+						}' class="title1">{{posterObj1.title1}}</view>
+						<view :style='{fontSize:posterObj1.title2CssFontSize,color:posterObj1.title2CssColor,lineWeight:posterObj1.title2CssLineHeight
+						}' class="title2">{{posterObj1.title2}}</view>
+					</view>		
+					<view>
+						<image :style='{width:posterObj1.qrcodeWidth,height:posterObj1.qrcodeHeight,}' :src="posterObj1.qrcode"></image>
+					</view>
+				</view>
+			</view>
+			<view class="footer-btn">
+				<view class="">
+					返回
+				</view>
+				<view class="save" @click="toSave">
+					保存
+				</view>
+			</view>
+		</view>
+		<view class="poster-btn">
+			<view class='diy_image'>
+				上传图片
+				<view>
+					<u-radio-group @change='imagechange($event)' v-model="imageValue">
+						<u-radio shape="square" name='自定义图片' label="自定义图片"></u-radio>
+						<u-radio shape="square" name='选择名片' label="选择名片"></u-radio>
+					</u-radio-group>
+				</view>
+			</view>
+			<view style='color:red;font-size:24rpx;padding: 20rpx 20rpx 0;'>注:如勾选“选择名片”,其他用户扫描海报二维码后会自动与您交换名片。</view>
+			<view class='diy_title'>
+				编辑内容
+				<view> 
+				<u--input v-model="value" @focus='titleclick' placeholder="请输入内容" border="bottom" clearable></u--input></view>
+			</view>
+			<view class='check_bg'>
+				<view style='font-size:28rpx'>定义背景</view>
+				<view :class="{'is-check':check_idx==index}" v-for="(item,index) in footer_arr" :key="index" @click="toChoose(index)">
+					<image :src="item" mode=""></image>
+					<view v-if="check_idx==index"></view>
+				</view>	
+			</view>
+			
+			
+		</view>
+		<!-- 生成的图片 -->
+		<uni-popup type="center" ref="posterImg" :maskClick="false">
+			<view class="poster-img">
+				<text @click="closePopup"></text>
+				<view style='overflow: scroll;width: 670rpx;height: 928rpx;'>
+					<image :src="path" mode="widthFix" @click="previewImg"></image>
+				</view>
+				<!-- #ifdef H5 -->
+				<view class="">
+					长按图片保存到手机
+				</view>
+				<!-- #endif -->
+				<!-- #ifndef H5 -->
+				<view class="">
+					点击图片保存到手机
+				</view>
+				<!-- #endif -->
+			</view>
+		</uni-popup>
+		<u-popup :show="titleStatus"  :round="10" mode="center" @close="close" @open="open">
+		    <view style='width:600rpx;padding:20rpx;'>
+		        <u--textarea v-model="value" maxlength='300' count placeholder="请输入内容" ></u--textarea>
+				<view style='margin:20rpx 0rpx 0;'>
+					<u-button @click='confirm' type="success" text="确定"></u-button>
+				</view>
+		    </view>
+		</u-popup>
+		<u-picker :immediateChange="true" keyName="cardBusiness" confirmText='确定' title="选择名片" @cancel="show1=false"
+			closeOnClickOverlay @confirm="cardConfirm" :show="show1" :columns="cardList"></u-picker>
+		<poster :data="canvasData" background-color="#FFF" :width='948' :height='534' @on-success="posterSuccess"
+			ref="poster" @on-error="posterError"></poster>
+		<lPainter :board="posterObj" ref="painter"></lPainter>
+		<canvas id="canvasId" 
+		:type="type" 
+		style="width:0;height:0;"></canvas>
+	</view>
+</template>
+
+<script>
+	var that;
+	import uploadImage from '@/components/ossutil/uploadFile.js';
+	import uniPopup from "@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue";
+	import lPainter from '@/components/lime-painter/index.vue';
+	import { Layout } from '@/components/lime-painter/layout';
+	import Poster from '../../components/zhangyuhao-poster/Poster.vue';
+	import { toPx, isNumber, getImageInfo, base64ToPath, compareVersion  } from '@/components/lime-painter/utils'
+	import { adaptor, expand } from '@/components/lime-painter/canvas';
+	export default {
+		components: {
+			lPainter,
+			uniPopup,
+			Poster,
+		},
+		data() {
+			return {
+				imageValue:'',
+				canvasData:{},
+				value:'云现场 | 2020年 麓客城市 WO-LUNTEER创想礼·麓客共创之夜',
+				show_poster:false,//显示海报
+				path: '', //生成的图片地址
+				image:'',
+				show1:false,
+				cardList: [],
+				list:[],
+				theight:0,
+				canvas1:'',
+				poster:'',
+				type:'2d',
+				height:0,
+				use2dCanvas:false,
+				layout1: new Layout(),
+				titleStatus:false,
+				posterObj1:{
+					background:'#fff',
+					backgroundType:'rgb',
+					borderRadius: '16rpx',
+					image:'https://s.yun-live.com/images/20210201/eb694718fa6c7b90d60a2c250847a192.jpg',
+					width: '606rpx',
+					height: '341rpx',
+					imageBorderRadius:'16rpx',
+					title:'云现场 | 2020年 麓客城市 WO-LUNTEER创想礼·麓客共创之夜',
+					titleCssFontSize: '32rpx',
+					titleCssColor: '#1A2033',
+					titleCssFontWeight: 'bold',
+					titleCssLineHeight: '45rpx',
+					title1:'厦门吴彦祖',
+					title1CssFontSize: '28rpx',
+					title1CssColor: '#1A2033',
+					title1CssFontWeight: 'bold',
+					title1CssLineHeight: '28rpx',
+					title2:'扫描识别二维码',
+					title2CssFontSize: '24rpx',
+					title2CssColor: '#4070FF',
+					title2CssLineHeight: '24rpx',
+					qrcode:'../../static/imgs/qrcode.jpg',
+					qrcodeWidth: '240rpx',
+					qrcodeHeight: '240rpx',
+				},
+				posterObj: {
+					width: '670rpx',
+					height: '890rpx',
+					background: '#fff',
+					borderRadius: '16rpx',
+					views: [
+						// {
+						// 	type: 'image',
+						// 	src: 'https://s.yun-live.com/images/20210201/9a4e7df322dc5c9f30ea9b126d3269a6.png',
+						// 	css: {
+						// 		width: '128rpx',
+						// 		height: '50rpx',
+						// 		left: '542rpx',
+						// 		top: '0rpx',
+						// 	}
+						// },
+						// {
+						// 	type: 'image',
+						// 	src: 'https://s.yun-live.com/images/20210201/eb694718fa6c7b90d60a2c250847a192.jpg',
+						// 	css: {
+						// 		left: '32rpx',
+						// 		top: '480rpx',
+						// 		borderRadius: '50%',
+						// 		width: '80rpx',
+						// 		height: '80rpx'
+						// 	}
+						// },
+						{
+							type: 'text',
+							text: '云现场 | 2020年 麓客城市 WO-LUNTEER创想礼·麓客共创之夜',
+							use:'title',
+							css: {
+								fontSize: '32rpx',
+								color: '#1A2033',
+								fontWeight: 'bold',
+								lineHeight: '45rpx',
+								left: '32rpx',
+								top: '400rpx',
+								width: '606rpx'
+							}
+						},
+						{
+							type: 'text',
+							text: '厦门吴彦祖',
+							use:'title1',
+							css: {
+								fontSize: '28rpx',
+								fontWeight: 'bold',
+								color: '#1A2033',
+								lineHeight: '28rpx',
+								left: '32rpx',
+								top: '515rpx'
+							}
+						},
+						{
+							type: 'text',
+							text: '扫描识别二维码',
+							use:'title2',
+							css: {
+								fontSize: '24rpx',
+								color: '#4070FF',
+								lineHeight: '24rpx',
+								left: '32rpx',
+								top: '559rpx'
+							}
+						},
+						{
+							type: 'image',
+							src: 'https://s.yun-live.com/images/20210201/eb694718fa6c7b90d60a2c250847a192.jpg',
+							use:'dt',
+							css: {
+								left: '32rpx',
+								top: '34rpx',
+								width: '606rpx',
+								height: '341rpx',
+								borderRadius: '16rpx'
+							}
+						},
+						
+						{
+							type: 'qrcode',
+							text: 'https://www.yun-live.com/',
+							css: {
+								left: '445rpx',
+								top: '500rpx',
+								width: '200rpx',
+								height: '200rpx',
+							}
+						},
+						// {
+						// 	type: 'text',
+						// 	text: '————  由云现场提供技术支持  ————',
+						// 	css: {
+						// 		left: '0',
+						// 		top: '863rpx',
+						// 		width: '100%',
+						// 		textAlign: 'center',
+						// 		fontSize: '24rpx',
+						// 		color: '#989FB3',
+						// 		lineHeight: '33rpx'
+						// 	}
+						// }
+					]
+				}, //画板数据
+				footer_arr: [
+					'https://s.yun-live.com/images/20210201/d502979c734077930cee837739ee9285.png',
+					'https://s.yun-live.com/images/20210201/0289000561415e1f9f6e542a3553906d.png',
+					'https://s.yun-live.com/images/20210201/5d2e237728e1dd8727835ca95084721e.png',
+					'https://s.yun-live.com/images/20210201/15075d31c26cc446333d569b0d2346e8.png',
+					'https://s.yun-live.com/images/20210201/6e716c556d1a80e90ecb0260e0990add.png'
+				], //底部选项
+				check_idx: 0, //底部选中的下标
+				dtindex:0,
+				titleindex:0,
+				title1index:0,
+				title2index:0,
+			}
+		},
+		onLoad() {
+			that = this
+		},
+		onShow(){
+			// #ifdef MP-WEIXIN
+			const {SDKVersion, version, platform} = uni.getSystemInfoSync()
+			// ios wx7.0.20 createImage bug
+			this.use2dCanvas = (this.type === '2d' && compareVersion(SDKVersion, '2.9.2') >= 0) && !(/ios/.test(platform) && /7.0.20/.test(version));
+			// #endif
+			this.posterObj1.title1=uni.getStorageSync("userInfo").nickname+'推荐给你'
+			this.$request.baseRequest('admin.unimall.cardManagementInfo', 'list', {
+				commonId: uni.getStorageSync("userInfo").id
+			}, failres => {
+				uni.hideLoading()
+				uni.showToast({
+					icon: "none",
+					title: failres.errmsg,
+					duration: 3000
+				});
+			
+			}).then(res => {
+				this.list = res.data.items
+				this.cardList = [res.data.items]
+			})
+		},
+		watch: {
+			// check_idx(newVal,oldVal){
+			// 	console.log(111,newVal);
+			// 	console.log(222,oldVal);
+			// }
+		},
+		methods: {
+			confirm(e){
+				this.titleStatus=false
+				this.posterObj1.title=this.value
+			},
+			titleclick(){
+				this.titleStatus=true
+			},
+			posterError(err) {
+				console.log(err)
+				uni.hideLoading()
+			},
+			posterSuccess(url) {
+				console.log("hahahah", url)
+				// 生成成功,会把临时路径在这里返回
+				this.poster = url;
+				this.posterObj1.image = url
+				this.posterObj1.qrcode=this.currectData.qrCodeMyself
+				console.log(url)
+				this.show1 = false
+				// this.show=false
+				uni.hideLoading()
+			},
+			cardConfirm(e) {
+				console.log(e)
+				this.currectData = e.value[0]
+				uni.showLoading({
+					title: '加载中',
+					mask: true
+				})
+				var data = [{
+						type: 'image',
+						path: this.currectData.currentBackground,
+						use: 'bg',
+						x: -24,
+						y: -27,
+						width: 1000,
+						height: 600
+					},
+					{
+						type: 'image',
+						path: this.currectData.headSculpture ? this.currectData.headSculpture :
+							'../../static/imgs/card/defaulthead.png',
+						shape: 'circle',
+						use: 'head',
+						imageType: this.currectData.headSculpture ? 'wl' : 'bd',
+						x: 60,
+						y: 60,
+						width: 150,
+						height: 150
+					},
+					{
+						type: 'text',
+						text: this.currectData.name,
+						use: 'name',
+						x: 300,
+						y: 80,
+						size: 40,
+						color: '#000'
+			
+					},
+					{
+						type: 'text',
+						text: this.currectData.post,
+						use: 'post',
+						x: 440,
+						y: 85,
+						size: 32,
+						color: '#666666'
+			
+					},
+					{
+						type: 'text',
+						text: this.currectData.companyName,
+						use: 'companyName',
+						x: 300,
+						y: 155,
+						size: 32,
+						color: '#000'
+			
+					},
+					{
+						type: 'image',
+						path: '../../static/imgs/card/address1.png',
+						use: 'address-icon',
+						x: 300,
+						y: 220,
+						width: 30,
+						height: 40
+					},
+					{
+						type: 'textarea',
+						text: this.currectData.province + this.currectData.city + this.currectData.area + this
+							.currectData.detailedAddress,
+						lineSpace: 1,
+						width: 640,
+						use: 'address',
+						x: 345,
+						y: 228,
+						size: 28,
+						color: '#000'
+					},
+					{
+						type: 'image',
+						path: '../../static/imgs/card/phone1.png',
+						use: 'phone-icon',
+						x: 300,
+						y: 280,
+						width: 32,
+						height: 32
+					},
+					{
+						type: 'text',
+						text: this.currectData.phone,
+						use: 'phone',
+						x: 345,
+						y: 284,
+						size: 28,
+						color: '#000'
+					},
+					{
+						type: 'image',
+						path: '../../static/imgs/card/remark1.png',
+						use: 'remark-icon',
+						x: 300,
+						y: 340,
+						width: 26,
+						height: 32
+					},
+					{
+						type: 'textarea',
+						text: this.currectData.remark ? this.currectData.remark : '单击添加备注',
+						use: 'remark',
+						lineSpace: 2,
+						width: 200,
+						x: 345,
+						y: 345,
+						size: 26,
+						color: '#000'
+					},
+					{
+						type: 'image',
+						path: '../../static/imgs/card/bg3.png',
+						use: 'bg1',
+						x: 0,
+						y: 418,
+						width: 687,
+						height: 120
+					},
+					{
+						type: 'image',
+						path: '../../static/imgs/card/bg4.png',
+						use: 'bg2',
+						x: 647,
+						y: 418,
+						width: 305,
+						height: 120
+					},
+					{
+						type: 'image',
+						path: '../../static/imgs/card/home.png',
+						use: 'home',
+						x: 65,
+						y: 455,
+						width: 55,
+						height: 55
+					},
+					{
+						type: 'text',
+						text: this.currectData.classifyName ? this.currectData.classifyName : '默',
+						use: 'classify',
+						x: 245,
+						y: 460,
+						size: 54,
+						color: '#fff'
+					},
+					{
+						type: 'image',
+						path: '../../static/imgs/card/share.png',
+						use: 'share',
+						x: 435,
+						y: 453,
+						width: 66,
+						height: 52
+					},
+				]
+				console.log(this.currectData.cuttentTemplate, 111111)
+				for (var i = 0; i < data.length; i++) {
+					switch (this.currectData.cuttentTemplate) {
+						case '2':
+							if (data[i].use === 'name' ||
+								data[i].use === 'companyName' ||
+								data[i].use === 'address-icon' ||
+								data[i].use === 'phone-icon' ||
+								data[i].use === 'remark-icon') {
+								data[i].x = 30
+							}
+							if (data[i].use === 'post') {
+								data[i].x = 100
+							}
+							if (data[i].use === 'address' ||
+								data[i].use === 'phone' ||
+								data[i].use === 'remark') {
+								data[i].x = 60
+							}
+							if (data[i].use == 'head') {
+								data[i].x = 370
+							}
+							break;
+						case '3':
+							if (data[i].use === 'name') {
+								data[i].x = 30
+								data[i].y = 150
+							}
+							if (data[i].use === 'companyName') {
+								data[i].x = 30
+								data[i].y = 180
+							}
+							if (data[i].use === 'post') {
+								data[i].x = 100
+								data[i].y = 153
+							}
+							if (data[i].use === 'address-icon' ||
+								data[i].use === 'phone-icon' ||
+								data[i].use === 'remark-icon') {
+								data[i].x = 170
+							}
+							if (data[i].use === 'address-icon') {
+								data[i].y = 150
+							}
+							if (data[i].use === 'address') {
+								data[i].y = 155;
+								data[i].width = 300
+							}
+							if (data[i].use === 'phone-icon') {
+								data[i].y = 180
+							}
+							if (data[i].use === 'phone') {
+								data[i].y = 183
+							}
+							if (data[i].use === 'remark-icon') {
+								data[i].y = 210
+							}
+							if (data[i].use === 'remark') {
+								data[i].y = 212
+							}
+							if (data[i].use === 'address' ||
+								data[i].use === 'phone' ||
+								data[i].use === 'remark') {
+								data[i].x = 190
+							}
+							break;
+						case '4':
+							if (data[i].use === 'name' ||
+								data[i].use === 'companyName') {
+								data[i].x = 30
+							}
+							if (data[i].use === 'companyName') {
+								data[i].y = 90
+							}
+							if (data[i].use === 'post') {
+								data[i].x = 100
+							}
+							if (data[i].use == 'head') {
+								data[i].x = 25
+								data[i].y = 120
+							}
+							if (data[i].use === 'address-icon' ||
+								data[i].use === 'phone-icon' ||
+								data[i].use === 'remark-icon') {
+								data[i].x = 170
+							}
+							if (data[i].use === 'address') {
+								data[i].width = 300
+							}
+							if (data[i].use === 'address' ||
+								data[i].use === 'phone' ||
+								data[i].use === 'remark') {
+								data[i].x = 190
+							}
+							break;
+						default:
+					}
+				}
+				this.canvasData = {
+					clicknum: this.clicknum++,
+					list: data
+				}
+			},
+			open() {
+			        // console.log('open');
+			},
+			close() {
+			    this.show = false
+			        // console.log('close');
+			},
+			
+			imagechange(e){
+				console.log(e,1111)
+				if(e=='自定义图片'){
+					uni.showActionSheet({
+					    itemList: ["拍照", "从手机相册选择"],
+					    success(res) {
+					        let sourceType = "camera";
+					        if (res.tapIndex == 0) {
+					            sourceType = "camera";
+					        } else if (res.tapIndex == 1) {
+					            sourceType = "album";
+					        }
+					        uni.chooseImage({
+					            count: 1, //最多可以选择的图片张数,默认9
+					            sizeType: ["original", "compressed"],   //original 原图,compressed 压缩图,默认二者都有
+					            sourceType: [sourceType], //album 从相册选图,camera 使用相机,默认二者都有。如需直接开相机或直接选相册,请只使用一个选项
+					            success: function(res) {
+					                let tempFilePaths = res.tempFilePaths;
+									that.posterObj1.image = tempFilePaths[0]
+									this.posterObj1.qrcode = '../../static/imgs/qrcode.jpg'
+					               console.log(tempFilePaths)
+					            }
+					          });
+					        }
+					      });
+
+				}else{
+					if(this.list.length>0){
+						this.show1=true
+					}else{
+						uni.showToast({
+							icon: "none",
+							title: '您还未创建名片',
+							duration: 3000
+						})
+					}
+				}
+			},
+			
+			previewImg(){
+				// #ifdef H5
+				return;
+				// #endif
+				uni.previewImage({
+					current:this.path,
+					urls: [this.path]
+				});
+			},
+			closePopup(){
+				this.$refs.posterImg.close();
+				this.show_poster=false;
+			},
+			async toSave() {
+			var that = this
+			//写个同步方法 把所谓 高度宽度 位置 都算好
+			let query = wx.createSelectorQuery();
+			await query.select('.canvas-content').boundingClientRect(rect=>{
+			  this.height = rect.height;
+			}).exec()
+			await query.select('.title').boundingClientRect(rect=>{
+			  this.theight = rect.height;
+			}).exec()
+		
+			setTimeout(()=>{
+				var posterObj = this.$request.makeCanvasData(this.check_idx,this.height,this.posterObj1,this.theight)
+				uni.showLoading({
+					title:'海报生成中',
+				})
+				this.posterObj = posterObj
+				setTimeout(()=>{
+				const painter = this.$refs.painter;
+				console.log(painter)
+					painter.canvasToTempFilePath().then(res => {
+						console.log(res.tempFilePath,'path')
+						this.path = res.tempFilePath;
+						this.$refs.posterImg.open();
+						this.show_poster=true;
+						uni.hideLoading()
+					});
+				},4000)
+			},1000)
+				
+			
+				console.log(this.posterObj)
+				
+				console.log(this.check_idx)
+				if (this.check_idx == 4) return;
+				// switch (this.check_idx) {
+				// 	case 0:
+
+				// 		this.posterObj.background = '#fff';
+				// 		this.posterObj.views=[
+					
+				// 				{
+				// 					type: 'text',
+				// 					text: this.posterObj1.title,
+				// 					use:'title',
+				// 					css: {
+				// 						fontSize: '32rpx',
+				// 						color: '#1A2033',
+				// 						fontWeight: 'bold',
+				// 						lineHeight: '45rpx',
+				// 						left: '32rpx',
+				// 						top: '400rpx',
+				// 						width: '606rpx'
+				// 					}
+				// 				},
+				// 				{
+				// 					type: 'text',
+				// 					text:  this.posterObj1.title1,
+				// 					use:'title1',
+				// 					css: {
+				// 						fontSize: '28rpx',
+				// 						fontWeight: 'bold',
+				// 						color: '#1A2033',
+				// 						lineHeight: '28rpx',
+				// 						left: '32rpx',
+				// 						top: '515rpx'
+				// 					}
+				// 				},
+				// 				{
+				// 					type: 'text',
+				// 					text: '长按或扫描识别二维码',
+				// 					use:'title2',
+				// 					css: {
+				// 						fontSize: '24rpx',
+				// 						color: '#4070FF',
+				// 						lineHeight: '24rpx',
+				// 						left: '32rpx',
+				// 						top: '559rpx'
+				// 					}
+				// 				},
+				// 				{
+				// 					type: 'image',
+				// 					src: 'https://s.yun-live.com/images/20210201/eb694718fa6c7b90d60a2c250847a192.jpg',
+				// 					use:'dt',
+				// 					css: {
+				// 						left: '32rpx',
+				// 						top: '34rpx',
+				// 						width: '606rpx',
+				// 						height: '341rpx',
+				// 						borderRadius: '16rpx'
+				// 					}
+				// 				},
+								
+				// 				{
+				// 					type: 'qrcode',
+				// 					text: 'https://www.yun-live.com/',
+				// 					css: {
+				// 						left: '445rpx',
+				// 						top: '500rpx',
+				// 						width: '200rpx',
+				// 						height: '200rpx',
+				// 					}
+				// 				},
+				// 				// {
+				// 				// 	type: 'text',
+				// 				// 	text: '————  由云现场提供技术支持  ————',
+				// 				// 	css: {
+				// 				// 		left: '0',
+				// 				// 		top: '863rpx',
+				// 				// 		width: '100%',
+				// 				// 		textAlign: 'center',
+				// 				// 		fontSize: '24rpx',
+				// 				// 		color: '#989FB3',
+				// 				// 		lineHeight: '33rpx'
+				// 				// 	}
+				// 				// }
+				// 			]
+				// 		break;
+				// 	case 1:
+				// 	this.posterObj.height=(this.height*2)+'rpx'
+				// 	this.posterObj.views=[{
+				// 				type: 'image',
+				// 				use:'bg',
+				// 				src: 'https://s.yun-live.com/images/20210201/39ae4d9d8ad0b1acac7c224e845c641f.png',
+				// 				css: {
+				// 					left: '0',
+				// 					top: '0',
+				// 					width: '100%',
+				// 					height: '100%'
+				// 				}
+				// 			},
+				// 			{
+				// 				type: 'image',
+				// 				src: 'https://s.yun-live.com/images/20210201/eb694718fa6c7b90d60a2c250847a192.jpg',
+				// 				css: {
+				// 					left: '32rpx',
+				// 					top: '34rpx',
+				// 					width: '606rpx',
+				// 					height: '341rpx',
+				// 					borderRadius: '16rpx'
+				// 				}
+				// 			},
+				// 			{
+				// 				type: 'text',
+				// 				text: this.posterObj1.title,
+				// 				use:'title',
+				// 				css: {
+				// 					fontSize: '32rpx',
+				// 					color: '#fff',
+				// 					fontWeight: 'bold',
+				// 					lineHeight: '45rpx',
+				// 					left: '32rpx',
+				// 					top: '400rpx',
+				// 					width: '606rpx'
+				// 				}
+				// 			},
+				// 			{
+				// 				type: 'text',
+				// 				text: this.posterObj1.title1,
+				// 				use:'title1',
+				// 				css: {
+				// 					fontSize: '28rpx',
+				// 					fontWeight: 'bold',
+				// 					color: '#D8AB87',
+				// 					lineHeight: '28rpx',
+				// 					left: '32rpx',
+				// 					top: '515rpx'
+				// 				}
+				// 			},
+				// 			{
+				// 				type: 'text',
+				// 				text: '长按或扫描识别二维码',
+				// 				use:'title2',
+				// 				css: {
+				// 					fontSize: '24rpx',
+				// 					color: '#FFFFFF',
+				// 					lineHeight: '24rpx',
+				// 					left: '32rpx',
+				// 					top: '559rpx'
+				// 				}
+				// 			},
+							
+				// 			{
+				// 				type: 'image',
+				// 				src: 'https://s.yun-live.com/images/20210201/d88d56843d43b917e2a28550b2a62723.png',
+				// 				css: {
+				// 					left: '551rpx',
+				// 					top: '111rpx',
+				// 					width: '103rpx',
+				// 					height: '103rpx',
+				// 				}
+				// 			},
+				// 			{
+				// 				type: 'image',
+				// 				src: 'https://s.yun-live.com/images/20210201/247736ffd279276b891ec14db8ed0fd0.png',
+				// 				css: {
+				// 					left: '43rpx',
+				// 					top: '432rpx',
+				// 					width: '56.4rpx',
+				// 					height: '56.4rpx',
+				// 				}
+				// 			},
+							
+				// 			{
+				// 				type: 'image',
+				// 				src: 'https://s.yun-live.com/images/20210201/63a9b504fb745b43b9fe5c0adef8fddb.png',
+				// 				css: {
+				// 					left: '50%',
+				// 					top: '750rpx',
+				// 					transform: 'translate(-50%,0)',
+				// 					width: '192rpx',
+				// 					height: '78rpx',
+				// 				}
+				// 			},
+				// 			{
+				// 				type: 'view',
+				// 				use:'qrcode-view',
+				// 				css: {
+				// 					left: '445rpx',
+				// 					top: '500rpx',
+				// 					width: '200rpx',
+				// 					height: '200rpx',
+				// 					background: '#fff',
+				// 				}
+				// 			},
+				// 			{
+				// 				type: 'image',
+				// 				src:this.posterObj1.qrcode,
+				// 				use:'qrcode',
+				// 				css: {
+				// 					left: '455rpx',
+				// 					top: '510rpx',
+				// 					// transform: 'translate(-50%,0)',
+				// 					width: '180rpx',
+				// 					height: '180rpx',
+				// 				}
+				// 			},
+
+				// 		]
+						
+				// 		break;
+				// 	case 2:
+				// 		this.posterObj1.backgroundType='image'
+				// 		this.posterObj1.background='https://s.yun-live.com/images/20210201/78f227bd701da20676c9da9166ce3144.png';
+				// 		this.posterObj1.width='540rpx',
+				// 		this.posterObj1.height='303rpx',
+				// 		this.posterObj1.titleCssColor='#1D1D25'
+				// 		this.posterObj1.title1CssColor='#1D1D25'
+				// 		this.posterObj1.title2CssColor='#6CB37A'
+				// 		break;
+				// 	case 3:
+				// 		this.posterObj1.backgroundType='image'
+				// 		this.posterObj1.background='https://s.yun-live.com/images/20210201/524ab6a41fe8c7eb57b35df9a547d388.png';
+				// 		this.posterObj1.width='606rpx',
+				// 		this.posterObj1.height='341rpx',
+				// 		this.posterObj1.titleCssColor='#1D1D25'
+				// 		this.posterObj1.title1CssColor='#1D1D25'
+				// 		this.posterObj1.title2CssColor='#6CB37A'
+				// 		break;
+				// 	default:
+				// 		break;
+				// }
+				// var node = await this.layout1.calcNode(this.posterObj)
+				// console.log(node,'node')
+				// if(node&&node.children.length===this.posterObj.views.length){
+					// for(var i=0;i<node.children.length;i++){
+					// 	if(node.children[i].use==='title'){
+					// 		dth=node.children[i].layoutBox.height-48
+					// 	}
+					// 	if(node.children[i].use==='title'||node.children[i].use==='title1'||node.children[i].use==='title2'){
+					// 		h+=node.children[i].layoutBox.height
+					// 	}
+					// }
+					// console.log(h,h+780,'h打印')
+					// for(var i=0;i<this.posterObj.views.length;i++){
+					// 	if(this.posterObj.views[i].use==='title1'||this.posterObj.views[i].use==='title2'
+					// 	||this.posterObj.views[i].use==='qrcode-view'||this.posterObj.views[i].use==='qrcode'){
+					// 		var top=this.posterObj.views[i].css.top.split('r')[0]
+					// 		this.posterObj.views[i].css.top=(Number(top)+Number(dth*2))+'rpx'
+					// 	}
+					// }
+					// this.posterObj.width='670rpx'
+					
+					// console.log(this.posterObj,'posterObj')
+					// uni.showLoading({
+					// 	title:'海报生成中',
+					// })
+					// const painter = this.$refs.painter;
+					// setTimeout(()=>{
+					// 	console.log(this.height*2,'height')
+					// 	painter.canvasToTempFilePath().then(res => {
+					// 		console.log(res.tempFilePath,'path')
+					// 		this.path = res.tempFilePath;
+					// 		this.$refs.posterImg.open();
+					// 		this.show_poster=true;
+					// 		uni.hideLoading()
+					// 	});
+					// },1000)
+				// }
+				
+				
+			},
+			toChoose(index) {
+				if (index == 4) return;
+				this.check_idx = index;
+				switch (index) {
+					case 0:
+						this.posterObj1.background='#fff';
+						this.posterObj1.backgroundType='rgb'
+						this.posterObj1.width='606rpx',
+						this.posterObj1.height='341rpx',
+						this.posterObj1.titleCssColor='#1A2033'
+						this.posterObj1.title1CssColor='#1A2033'
+						this.posterObj1.title2CssColor='#4070FF'
+						break;
+					case 1:
+						this.posterObj1.backgroundType='image'
+						this.posterObj1.background='https://s.yun-live.com/images/20210201/39ae4d9d8ad0b1acac7c224e845c641f.png';
+						this.posterObj1.width='606rpx',
+						this.posterObj1.height='341rpx',
+						this.posterObj1.titleCssColor='#fff'
+						this.posterObj1.title1CssColor='#D8AB87'
+						this.posterObj1.title2CssColor='#fff'
+						break;
+					case 2:
+						this.posterObj1.backgroundType='image'
+						this.posterObj1.background='https://s.yun-live.com/images/20210201/78f227bd701da20676c9da9166ce3144.png';
+						this.posterObj1.width='540rpx',
+						this.posterObj1.height='303rpx',
+						this.posterObj1.titleCssColor='#1D1D25'
+						this.posterObj1.title1CssColor='#1D1D25'
+						this.posterObj1.title2CssColor='#6CB37A'
+						break;
+					case 3:
+						this.posterObj1.backgroundType='image'
+						this.posterObj1.background='https://s.yun-live.com/images/20210201/524ab6a41fe8c7eb57b35df9a547d388.png';
+						this.posterObj1.width='606rpx',
+						this.posterObj1.height='341rpx',
+						this.posterObj1.titleCssColor='#1D1D25'
+						this.posterObj1.title1CssColor='#1D1D25'
+						this.posterObj1.title2CssColor='#6CB37A'
+						break;
+					default:
+						break;
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	page{
+		background-color: #F2F4F8;
+	}
+	.page{
+		/deep/ .uni-transition{
+			background-color:rgba(0, 0, 0, 0.6)
+		}
+		.poster-img{
+			width: 670rpx;
+			height: 928rpx;
+			position: relative;
+			text{
+				background: url('https://s.yun-live.com/images/20210201/5c4ef9d86bc5eec90f2f915683d9db08.png') no-repeat;
+				background-size: 100% 100%;
+				display: inline-block;
+				width: 50rpx;
+				height: 50rpx;
+				position: absolute;
+				top: -60rpx;
+				right: 0;
+			}
+			image{
+				width: 100%;
+				height: 100%;
+			}
+			view{
+				font-size: 32rpx;
+				font-family: PingFang-SC-Bold, PingFang-SC;
+				font-weight: bold;
+				color: #FFFFFF;
+				line-height: 32rpx;
+				text-align: center;
+				margin-top: 28rpx;
+			}
+		}
+		.poster{
+			padding: 24rpx 40rpx 410rpx;
+			
+			.footer-btn{
+				margin-top: 24rpx;
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+				view{
+					width: 319rpx;
+					height: 66rpx;
+					border-radius: 40rpx;
+					border: 1px solid #4070FF;
+					font-size: 26rpx;
+					font-family: PingFangSC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #4070FF;
+					line-height: 66rpx;
+					text-align: center;
+				}
+				.save{
+					background: #4070FF;
+					color: #FFFFFF;
+				}
+			}
+		}
+		.poster-btn{
+			position: fixed;
+			bottom: 0;
+			width: 100%;
+			height: 364rpx;
+			background: #FFFFFF;
+			border-radius: 32rpx 32rpx 0px 0px;
+			>.check_bg{
+				display: flex;
+				align-items: center;
+				justify-content: space-around;
+				padding-top:25rpx;
+				>view{
+					width: 80rpx;
+					height: 80rpx;
+					position: relative;
+					border-radius: 10rpx;
+					border: 4rpx solid #fff;
+					&.is-check{
+						border: 4rpx solid #4070FF;
+					}
+					image{
+						width: 100%;
+						height: 100%;
+					}
+					view{
+						width: 30rpx;
+						height: 30rpx;
+						position: absolute;
+						right: -15rpx;
+						bottom: -15rpx;
+						background: url('https://s.yun-live.com/images/20210201/311c01265c1aa508418f6bae10d67602.png') no-repeat;
+						background-size: 100% 100%;
+					}
+				}
+			}
+			/deep/.u-radio{
+				margin-left: 10px;
+			}
+			.diy_title,.diy_image{
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+				padding: 20rpx 20rpx 0;
+				font-size:28rpx;
+			}
+			.diy_title{
+				padding: 10rpx 20rpx 0;
+			}
+		}
+	}
+	.poster{
+		position:relative;
+	}
+	.poster .title{
+		text-align:left;
+		margin-top:20rpx;
+		word-break: break-word;
+	}
+	.poster .title1{
+		margin-bottom:10rpx;
+	}
+</style>

BIN
xiaochengxu/static/comment.png


BIN
xiaochengxu/static/imgs/card/share1.png


BIN
xiaochengxu/static/imgs/mySet/hb.png


BIN
xiaochengxu/static/imgs/qrcode.jpg


BIN
xiaochengxu/static/love-fill.png


BIN
xiaochengxu/static/love.png


BIN
xiaochengxu/static/play.png


+ 69 - 0
xiaochengxu/uni_modules/cl-upload/changelog.md

@@ -0,0 +1,69 @@
+## 1.3.6(2023-05-22)
+添加自定义图片资源配置
+## 1.3.5(2023-05-04)
+修复小程序cloudType为other的时候获取不到视频删除按钮焦点问题
+## 1.3.4(2023-04-27)
+修复小程序unicloud自动上传不自动同步数据问题
+## 1.3.3(2023-04-27)
+修复删除按钮层级问题
+## 1.3.2(2023-04-24)
+修复安卓APP配置cloudType:other 无法显示http封面问题
+## 1.3.1(2023-04-19)
+修改版本信息
+## 1.3.0(2023-04-19)
+修复手动上传已知问题; 新增删除前,上传前钩子函数
+## 1.2.9(2023-03-22)
+修复已知问题
+## 1.2.8(2023-03-22)
+修复已知问题
+## 1.2.7(2023-03-21)
+修复部分视频封面白屏问题
+## 1.2.6(2023-03-21)
+修复部分视频封面白屏问题
+## 1.2.5(2023-03-08)
+兼容vue3中v-model数据绑定
+## 1.2.4(2023-03-06)
+1.修复低版本微信小程序video图层兼容问题;
+2.修复unicloud上传v-model不同步问题;
+## 1.2.3(2023-02-02)
+添加限制图片视频大小功能
+## 1.2.2(2023-02-01)
+兼容支付宝小程序手动上传视频
+## 1.2.1(2023-01-31)
+更新图片资源地址
+## 1.2.0(2022-12-12)
+兼容uniCloud上传
+## 1.1.9(2022-12-12)
+1. 添加最大视频限制
+2. 优化部分功能
+## 1.1.8(2022-12-09)
+修复部分设备上传视频第一帧黑屏问题
+## 1.1.7(2022-12-01)
+修复h5上传开启压缩后一直显示“正在压缩中”问题
+## 1.1.6(2022-11-24)
+兼容支付宝小程序手动上传
+## 1.1.5(2022-11-07)
+添加加载配置
+## 1.1.4(2022-11-07)
+修复图片限制数量后可以多次选择最大数量图片问题
+## 1.1.3(2022-10-28)
+添加提示弹窗配置
+## 1.1.1(2022-10-28)
+修复已知问题
+## 1.1.0(2022-08-25)
+修复base64压缩问题
+## 1.0.9(2022-08-25)
+修复服务器返回数据格式问题
+## 1.0.8(2022-08-01)
+优化代码格式、逻辑
+## 1.0.6(2022-07-30)
+修改已知问题
+## 1.0.5(2022-07-30)
+1. aspect-ratio兼容问题,添加height属性保底
+2. 添加监听上传进度变化事件
+## 1.0.3(2022-07-30)
+添加按钮控制,事件说明
+## 1.0.2(2022-07-30)
+优化服务器接口返回数据上传
+## 1.0.1(2022-07-29)
+初始化组件

+ 54 - 0
xiaochengxu/uni_modules/cl-upload/components/cl-image/cl-image.vue

@@ -0,0 +1,54 @@
+<template>
+	<image class="image" :src="imgSrc" mode="aspectFill" :disabled="false" :controls='false' @error="imgerror"></image>
+</template>
+
+<script>
+	export default {
+		props: {
+			src: {
+				type: String,
+				default: ''
+			},
+			cloudType: {
+				type: String,
+				default: 'oss'
+			},
+		},
+		data() {
+			return {
+				imgSrc: ''
+			};
+		},
+		mounted() {
+			this.setCloudFunction()
+		},
+		methods: {
+			imgerror(even) {
+				this.imgSrc =  `https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/887c60f0-27f8-46d1-8769-2c45be0f3d7d.png`
+			},
+			setCloudFunction() {
+				switch (this.cloudType){
+					case 'oss':
+						this.imgSrc = this.src + '?x-oss-process=video/snapshot,t_0,f_jpg'
+						break;
+					case 'process':
+						this.imgSrc = this.src + '?ci-process=snapshot&time=0.01'
+						break;
+					case 'vframe':
+						this.imgSrc = this.src + '?vframe/jpg/offset/0'
+						break;
+					default:
+						this.imgSrc = this.src
+						break;
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	image {
+		width: 100%;
+		height: 100%;
+	}
+</style>

+ 981 - 0
xiaochengxu/uni_modules/cl-upload/components/cl-upload/cl-upload.vue

@@ -0,0 +1,981 @@
+<template>
+	<view class="cl-updata">
+		<view class="file-list" :style="[listRowStyle]">
+
+			<view v-for="(item, index) in previewList" @tap="onClickRow(item, index)" class="file-list-row"
+				:style="[rowStyle]" :key="index">
+
+				<image v-if="fileUrlType(item) === 'image'" :src="item.path || item" :style="[imgStyle]"
+					mode="aspectFill">
+				</image>
+
+				<view v-else class="_video" :style="[imgStyle]">
+
+					<!-- #ifdef MP-WEIXIN || MP-ALIPAY -->
+					<video v-if="!autoUpload || cloudType === 'other'" class="_video" :style="[imgStyle]"
+						:src="item.url || item" :show-center-play-btn="false" :show-fullscreen-btn="false"
+						:show-play-btn="false" :show-loading="false" :enable-progress-gesture="false" :controls="false">
+						<!-- <cover-view class="video-fixed" ></cover-view> -->
+						<cover-view @tap="onPlay(item, index)" class="play">
+							<image style="width: 100%;" :src="playImg" mode="widthFix"></image>
+						</cover-view>
+					</video>
+
+					<!-- #endif -->
+
+					<!-- #ifdef APP-PLUS -->
+					<video v-if="cloudType === 'other'" class="_video" :style="[imgStyle]" :src="item" :poster="item"
+						:controls="false" :show-center-play-btn="false" :show-fullscreen-btn="false"
+						:show-play-btn="false" :show-loading="false" :enable-progress-gesture="false">
+						<cover-image class="app_play" :src="playImg" @tap="onPlay(item, index)"></cover-image>
+						<cover-view class="remove" v-if="remove" @tap="onRemove(item, index)">
+							<cover-image class="image" :src="deleteImg" mode="widthFix"
+								@tap="onRemove(item, index)"></cover-image>
+						</cover-view>
+					</video>
+					<!-- #endif -->
+
+					<!-- #ifndef MP-WEIXIN || MP-ALIPAY || APP-PLUS -->
+					<video v-if="cloudType === 'other'" class="_video" :autoplay="false" :style="[imgStyle]" :src="item"
+						:controls="false" :show-center-play-btn="false" :show-fullscreen-btn="false"
+						:show-play-btn="false" :show-loading="false" :enable-progress-gesture="false">
+						<!-- <cover-view class="video-fixed" @tap="onPlay(item, index)"></cover-view> -->
+						<cover-view @tap="onPlay(item, index)" class="play">
+							<image style="width: 100%;" :src="playImg" mode="widthFix"></image>
+						</cover-view>
+					</video>
+
+					<!-- #endif -->
+
+					<cl-image v-else class="pay" :style="[imgStyle]" :cloudType="cloudType"
+						:src="(item.path || item)"></cl-image>
+
+					<view class="play" @tap="onPlay(item, index)">
+						<image class="play-img" :src="playImg" mode="widthFix"></image>
+					</view>
+
+				</view>
+
+				<view class="remove" v-if="remove" @tap.stop="onRemove(item, index)">
+					<image class="image" :src="deleteImg" mode="widthFix"></image>
+				</view>
+			</view>
+
+			<view v-if="add && FileList.length < max" @tap="onClickAdd" :style="[rowStyle]"
+				class="file-list-row add-image">
+				<image :src="addImg" mode="widthFix"></image>
+			</view>
+		</view>
+
+
+		<view v-if="tempVideoUrl" class="mask">
+			<image @tap="tempVideoUrl = ''" class="_root" :src="closeImg" mode="widthFix"></image>
+
+			<view class="block" @tap.stop>
+				<video autoplay :src="tempVideoUrl"></video>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import uploadImage from '@/components/ossutil/uploadFile.js';
+	import ClImage from '../cl-image/cl-image.vue'
+	export default {
+		name: "cl-upload",
+		components: {
+			ClImage
+		},
+		props: {
+			//受控图片列表
+			// #ifdef VUE2
+			value: {
+				type: Array,
+				default: () => [],
+			},
+			// #endif
+
+			// #ifdef VUE3
+			modelValue: {
+				type: Array,
+				default: () => [],
+			},
+			// #endif
+
+			// 存储云类型 oss阿里云  vframe七牛云   process腾讯云  other其他
+			cloudType: {
+				type: String,
+				default: 'oss'
+			},
+			// 标识符,即后端接口参数名
+			fileName: {
+				type: String,
+				default: 'file'
+			},
+			// // 文件类型 'image', 'video', 'all'
+			// fileType: {
+			// 	type: String,
+			// 	default: 'all'
+			// },
+			// 上传图片参数
+			imageFormData: {
+				type: Object,
+				default: () => {}
+			},
+			// 上传视频参数
+			videoFromData: {
+				type: Object,
+				default: () => {}
+			},
+
+			// 必选参数,上传的地址
+			action: {
+				type: String,
+				default: ''
+			},
+
+			// 设置上传的请求头部
+			headers: {
+				type: Object,
+				default: () => {}
+			},
+
+			// 上传时附带的额外参数
+			data: {
+				type: Object,
+				default: () => {}
+			},
+
+			// 是否开启预览图片
+			isPreviewImage: {
+				type: Boolean,
+				default: true
+			},
+
+			// 图片指示器样式,可取值:"default" - 底部圆点指示器; "number" - 顶部数字指示器; "none" - 不显示指示器。
+			indicator: {
+				type: String,
+				default: 'none'
+			},
+			// 是否在选取文件后立即进行上传	
+			autoUpload: {
+				type: Boolean,
+				default: true
+			},
+			// 是否显示删除按钮
+			remove: {
+				type: Boolean,
+				default: true
+			},
+			// 是否添加按钮
+			// add: {
+			// 	type: Boolean,
+			// 	default: true
+			// },
+			// 最多显示数量
+			max: {
+				type: Number,
+				default: 9
+			},
+			// 视频最大上传数量
+			maxVideo: {
+				type: Number,
+				default: 0
+			},
+			// 列表样式
+			listStyle: {
+				type: Object,
+				default: () => {}
+			},
+			// 删除提示弹窗标题
+			deleteTitle: {
+				type: String,
+				default: '提示'
+			},
+			// 删除提示弹窗文案
+			deleteText: {
+				type: String,
+				default: '您确认要删除吗?'
+			},
+			// 是否开启删除前钩子
+			useBeforeDelete: {
+				type: Boolean,
+				default: false
+			},
+			// 是否开启上传前钩子
+			useBeforeUpload: {
+				type: Boolean,
+				default: false
+			},
+			// 添加按钮图片
+			addImg: {
+				type: String,
+				default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/bb1550b3-e0a8-4a90-a86f-00f8c6afa9fb.png'
+			},
+			// 播放按钮图片
+			playImg: {
+				type: String,
+				default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/ae40402f-aa53-4344-b553-2322799bebd6.png'
+			},
+			// 删除按钮图片
+			deleteImg: {
+				type: String,
+				default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/d20177a5-417e-4c5d-a266-1988361c543d.png'
+			},
+			// 关闭视频按钮图片
+			closeImg: {
+				type: String,
+				default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/cde4362d-7ec7-4cac-a692-12e1f576be1e.png'
+			},
+		},
+		data() {
+			return {
+				add:true,
+				// 文件类型 'image', 'video', 'all'
+				fileType: "all",
+				// 渲染列表
+				FileList: [],
+
+				// 预览视频地址
+				tempVideoUrl: '',
+
+				// 临时文件列表
+				tempFile_paths: [],
+
+			};
+		},
+		watch: {
+			// #ifdef VUE2
+			'value': {
+				handler: function(newVal, oldVal) {
+					this.FileList = newVal;
+				},
+				deep: true,
+				immediate: true
+			},
+			// #endif
+
+			// #ifdef VUE3
+			'modelValue': {
+				handler: function(newVal, oldVal) {
+					this.FileList = newVal;
+				},
+				deep: true,
+				immediate: true
+			},
+			// #endif
+		},
+		computed: {
+			previewList() {
+				return this.FileList.map(item => item.path || item)
+			},
+			listRowStyle() {
+				const style = {
+					'grid-template-columns': `repeat(${this.listStyle?.columns || 4}, 1fr)`, // 每行数量	
+					'grid-column-gap': this.listStyle?.columnGap || '40rpx', // 行间距	
+					'grid-row-gap': this.listStyle?.rowGap || '40rpx', // 列间距
+					'padding': this.listStyle?.padding || '0rpx' // 列表内边距
+				}
+
+				return style;
+			},
+			rowStyle() {
+				const style = {
+					'aspect-ratio': this.listStyle?.height ? '' : this.listStyle?.ratio || '1/1', // 图片比例	
+					'height': this.listStyle?.height || '140rpx',
+				}
+
+				return style;
+			},
+			imgStyle() {
+				const style = {
+					'border-radius': this.listStyle?.radius || '6rpx', // 图片圆角
+				}
+
+				return style;
+			}
+		},
+		methods: {
+			/**
+			 * 删除已选择文件
+			 * @param {object} item 文件信息
+			 * @param {number} index 文件索引
+			 * */
+			onRemove(item, index) {
+				if(this.FileList.length==1){
+					this.fileType = "all"
+					this.add = true
+				}
+				const imageItem = this.FileList[index];
+
+				if (this.useBeforeDelete) {
+					this.$emit('beforeDelete', imageItem, index, () => {
+						return deleteItem()
+					})
+				}
+
+				if (!this.useBeforeDelete) {
+					uni.showModal({
+						title: this.deleteTitle,
+						content: this.deleteText,
+						success: (res) => {
+							if (res.confirm) {
+								deleteItem()
+							}
+						}
+					});
+				}
+
+				const deleteItem = () => {
+					const tempFileIndex = this.tempFile_paths.indexOf(item || item.path);
+
+					if (tempFileIndex > -1) {
+						this.tempFile_paths.splice(tempFileIndex, 1)
+					}
+
+					this.FileList.splice(index, 1)
+
+					// #ifdef VUE2 
+					this.$emit('input', this.FileList)
+					// #endif
+
+					// #ifdef VUE3
+					this.$emit("update:modelValue", this.FileList);
+					// #endif
+				}
+
+			},
+
+			/**
+			 * 点击已选择文件
+			 * @param {object} item 文件信息
+			 * @param {number} index 文件索引 
+			 * */
+			onClickRow(item, index) {
+				this.previewImage(item?.url ?? item, index);
+				this.$emit('onImage', {
+					item,
+					index
+				})
+			},
+
+			/**
+			 * 点击选择图片按钮
+			 * */
+			onClickAdd() {
+
+				switch (this.fileType) {
+					case 'image':
+						this.onChooseFile(1);
+						break;
+					case 'video':
+						this.onChooseFile(2);
+						break;
+					case 'all':
+						uni.showActionSheet({
+							itemList: ['照片', '视频'],
+							success: (res) => {
+								const tapIndex = res.tapIndex;
+								if (tapIndex === 0) {
+									this.onChooseFile(1);
+								} else {
+									this.onChooseFile(2);
+								}
+							},
+							fail: (res) => {
+								console.error(res.errMsg);
+							}
+						});
+						break;
+					default:
+						this.onChooseFile(1);
+						break;
+				}
+			},
+
+
+			/**
+			 * 从本地选择文件。
+			 * @param { number } updataType 选择类型 1:图片 2视频
+			 * */
+			async onChooseFile(updataType) {
+				const that = this;
+				if (updataType === 1) {
+					this.fileType = 'image'
+					this.$emit('showType', "1")
+					const data = Object.assign({}, {
+						// 最多可以选择的图片张数,默认9
+						count: 9,
+						// 仅对 mediaType 为 image 时有效,是否压缩所选文件
+						// #ifndef MP-TOUTIAO
+						sizeType: ['original', 'compressed'],
+						// #endif
+						// album 从相册选图,camera 使用相机,默认二者都有。
+						sourceType: ['camera', 'album'],
+
+						compress: false
+					}, this.imageFormData)
+
+					data['count'] = this.max - this.FileList.length
+
+					uni.chooseImage({
+						...data,
+						success: async (res) => {
+							let tempFiles = res.tempFiles
+							const compress = that.imageFormData?.compress || false;
+
+							// 限制图片上传尺寸
+							if (that.imageFormData?.size ?? false) {
+								tempFiles.map((imgInfo, index) => {
+									const maxSize = that.imageFormData.size * 1024 * 1024
+									if (imgInfo.size > maxSize) {
+										tempFiles.splice(index, 1)
+										that.$emit('onImageSize', imgInfo)
+										return uni.showToast({
+											title: `图片最大上传${that.imageFormData.size}MB`,
+											duration: 2000,
+											icon: 'none'
+										});
+									}
+								})
+							}
+
+							// 开启压缩图片
+							if (compress) {
+								const compressImage = tempFiles.map(imageItem => {
+									return that.compressImage(imageItem.path)
+								})
+
+								Promise.all(compressImage).then(result => {
+									upload(result);
+								})
+
+							} else {
+								upload(tempFiles);
+							}
+
+							function upload(tempImages) {
+								if (that.autoUpload) {
+									tempImages.map(item => {
+										that.onBeforeUploadFile(item, 'image')
+									})
+								} else {
+									that.FileList = [...that.FileList, ...tempImages]
+									tempImages.map(item => {
+										that.tempFile_paths.push(item)
+									})
+								}
+							}
+
+						},
+						fail(err) {
+							console.error('选择图片失败', err)
+							that.$emit('onError', err)
+						}
+
+					})
+				}
+
+				if (updataType === 2) {
+					console.log("上传视频")
+					this.fileType = "video"
+					this.$emit('showType', "2")
+					this.add = false
+					// 限制视频最大上传数量
+					const VIDEO_REGEXP = /\.(mp4|flv|avi)/i
+					const videoList = await that.FileList.filter(item => {
+						const fileUrl = item?.url ?? item
+						return VIDEO_REGEXP.test(fileUrl)
+					})
+
+					if (that.maxVideo > 0 && videoList.length >= that.maxVideo) {
+						that.$emit('onVideoMax', that.maxVideo, videoList.length)
+						return uni.showToast({
+							title: '视频数量已超出',
+							duration: 2000,
+							icon: 'none'
+						});
+					}
+
+					const data = Object.assign({}, {
+						// 	拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。
+						maxDuration: 60,
+						// #ifndef MP-TOUTIAO
+						// 'front'、'back',默认'back'
+						camera: "back",
+						// #endif
+
+						// album 从相册选视频,camera 使用相机拍摄,默认二者都有。
+						sourceType: ['camera', 'album'],
+						// 是否压缩所选的视频源文件,默认值为 true,需要压缩。
+						compressed: true,
+						// 'front'、'back',默认'back'
+					}, this.videoFromData)
+
+					uni.chooseVideo({
+						...data,
+						success: (res) => {
+							let tempFilePath = {
+								...res
+							}
+							tempFilePath['path'] = res.tempFilePath
+
+							// 限制视频上传尺寸
+							if (that.videoFromData?.size ?? false) {
+								const maxSize = that.videoFromData.size * 1024 * 1024
+
+								if (tempFilePath.size > maxSize) {
+									uni.showToast({
+										title: `视频最大上传${that.videoFromData.size}MB`,
+										duration: 2000,
+										icon: 'none'
+									});
+									return false;
+								}
+
+							}
+							if (that.autoUpload) {
+								that.onBeforeUploadFile(tempFilePath, 'video')
+							} else {
+								that.FileList.push(tempFilePath)
+								that.tempFile_paths.push(tempFilePath)
+							}
+						},
+						fail(err) {
+							console.error('选择视频失败', err)
+						}
+
+					})
+				}
+			},
+
+			onBeforeUploadFile(tempFile) {
+				if (this.useBeforeUpload) {
+					return this.$emit('beforeUpload', tempFile, () => {
+						return this.updataFile(tempFile);
+					})
+				}
+				return this.updataFile(tempFile);
+			},
+
+			/**
+			 * 上传文件到服务器
+			 * @param { tempFile } 临时文件
+			 * */
+			updataFile(tempFile) {
+				const that = this;
+				const filePath = tempFile.path || tempFile;
+				const fileType = this.fileUrlType(filePath) == 'image' ? '.png' : '.mp4';
+				const fileName = tempFile.name || Date.now() + fileType;
+
+				uni.showLoading({
+					title: '正在上传中...',
+					icon: 'loading'
+				})
+
+				return new Promise((resolve, reject) => {
+					// uniCloud上传
+					if (that.action === 'uniCloud') {
+
+						uniCloud.uploadFile({
+							cloudPath: String(fileName),
+							filePath: filePath,
+							// #ifdef MP-ALIPAY 
+							fileType: fileType,
+							// #endif
+							onUploadProgress: (progressEvent) => {
+								const percentCompleted = Math.round(
+									(progressEvent.loaded * 100) / progressEvent.total
+								);
+								that.$emit('onProgress', percentCompleted)
+							},
+							success(result) {
+								if (that.autoUpload) {
+									that.FileList.push(result.fileID)
+								} else {
+									that.FileList.map((item, index) => {
+										if (item === filePath || item.path === filePath) {
+											that.FileList.splice(index, 1, result.fileID)
+										}
+									})
+								}
+
+								// #ifdef VUE2
+								that.$emit('input', that.FileList)
+								// #endif
+								// #ifdef VUE3
+								that.$emit("update:modelValue", that.FileList);
+								// #endif
+
+								resolve(result.fileID)
+								uni.hideLoading();
+								that.$emit('onProgress', {
+									...result
+								})
+							},
+							fail: (error) => {
+								uni.hideLoading();
+								console.error('error', error);
+								that.$emit('onError', error)
+								reject(error)
+							}
+						})
+						return false;
+					}
+					const uploadTask = uploadImage(filePath, 'cardImages/',
+						result => {
+							const data = result
+							uni.hideLoading();
+							that.success(data)
+
+							if (!this.autoUpload) {
+								that.FileList.map((item, index) => {
+									if (item === filePath || item.path === filePath) {
+										that.FileList.splice(index, 1)
+									}
+								})
+							}
+							resolve(data)
+						}, "", that.fileUrlType(filePath)
+					);
+					// 接口服务上传
+					// const uploadTask = uni.uploadFile({
+					// 	url: that.action,
+					// 	filePath: filePath,
+					// 	name: that.fileName,
+					// 	formData: that.data,
+					// 	header: that.headers,
+					// 	success: (uploadFileRes) => {
+					// 		const data = JSON.parse(uploadFileRes.data)
+					// 		uni.hideLoading();
+					// 		that.success(data)
+
+					// 		if (!this.autoUpload) {
+					// 			that.FileList.map((item, index) => {
+					// 				if (item === filePath || item.path === filePath) {
+					// 					that.FileList.splice(index, 1)
+					// 				}
+					// 			})
+					// 		}
+
+					// 		resolve(data)
+					// 	},
+					// 	fail: (error) => {
+					// 		uni.hideLoading();
+					// 		console.error('error', error);
+					// 		that.$emit('onError', error)
+					// 		reject(error)
+					// 	}
+					// });
+
+					// uploadTask.onProgressUpdate((res) => {
+					// 	that.$emit('onProgress', {
+					// 		...res,
+					// 		...tempFile
+					// 	})
+					// });
+				})
+			},
+
+			/**
+			 * 手动上传
+			 * */
+			submit() {
+
+				return new Promise((resolve, reject) => {
+					if (this.tempFile_paths.length <= 0) {
+						return console.error('没有可上传文件');
+					}
+
+					const result = this.tempFile_paths.map(item => {
+						return this.onBeforeUploadFile(item || item.path)
+					})
+
+					Promise.all(result).then(res => {
+						this.tempFile_paths = []
+						resolve(res)
+					}).catch(err => {
+						reject(err)
+					})
+				})
+
+			},
+
+			/**
+			 * 返回数据
+			 * */
+			success(data) {
+				this.$emit('onSuccess', data);
+
+				// 自定义数据结构-选择性开启
+				// const list = data.map(item=> {
+				// 	return JSON.parse(item).data.link;
+				// })
+				// this.$emit('input', [...this.FileList, ...list]);
+			},
+			/**
+			 * 压缩图片
+			 * @param {array} tempFilePaths 临时路径数组
+			 * @return {array} 被压缩过的路径数组
+			 * */
+			async compressImage(tempFilePaths) {
+				const that = this;
+
+				return new Promise((resolve, reject) => {
+
+					if (typeof tempFilePaths !== 'string') {
+						console.error('压缩路径错误')
+						reject([])
+					}
+
+					uni.showLoading({
+						title: '压缩中...',
+						icon: 'loading',
+					})
+
+					// #ifdef H5
+					this.canvasDataURL(tempFilePaths, {
+						quality: that.imageFormData.quality / 100
+					}, (base64Codes) => {
+						resolve(base64Codes);
+						uni.hideLoading();
+					})
+					// #endif
+
+					// #ifndef H5
+					uni.compressImage({
+						src: tempFilePaths,
+						quality: that.imageFormData.quality || 80,
+						success: res => {
+							resolve(res.tempFilePath);
+							uni.hideLoading();
+						},
+						fail(err) {
+							reject(err);
+							uni.hideLoading();
+						}
+					})
+					// #endif
+
+				})
+			},
+
+			/**
+			 * H5压缩图片质量
+			 * */
+			canvasDataURL(path, obj, callback) {
+				var img = new Image();
+				img.src = path;
+				img.onload = function() {
+					var that = this;
+					// 默认按比例压缩
+					var w = that.width,
+						h = that.height,
+						scale = w / h;
+					w = obj.width || w;
+					h = obj.height || (w / scale);
+					var quality = 0.8; // 默认图片质量为0.8
+					//生成canvas
+					var canvas = document.createElement('canvas');
+					var ctx = canvas.getContext('2d');
+					// 创建属性节点
+					var anw = document.createAttribute("width");
+					anw.nodeValue = w;
+					var anh = document.createAttribute("height");
+					anh.nodeValue = h;
+					canvas.setAttributeNode(anw);
+					canvas.setAttributeNode(anh);
+					ctx.drawImage(that, 0, 0, w, h);
+					// 图像质量
+					if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
+						quality = obj.quality;
+					}
+					// quality值越小,所绘制出的图像越模糊
+					var base64 = canvas.toDataURL('image/jpeg', quality);
+					// 回调函数返回base64的值
+					callback(base64);
+				}
+			},
+
+			/**
+			 * 预览图片
+			 * @param {string, object} item 文件信息
+			 * */
+			previewImage(item) {
+				if (this.fileUrlType(item) === 'video') return false;
+				if (!this.isPreviewImage) return false;
+
+				const imgs = this.FileList.filter(item => {
+					return this.fileUrlType(item) !== 'video'
+				}).map(item => item?.path ?? item)
+				const current = imgs.indexOf(item || item.path);
+
+				uni.previewImage({
+					current: current,
+					urls: imgs,
+					success() {},
+					fail(err) {
+						console.log(err);
+					}
+				})
+			},
+
+			/**
+			 * 预览视频
+			 * @param {string, object} item 文件信息
+			 * */
+			onPlay(item, index) {
+				this.$emit('onVideo', {
+					item,
+					index
+				})
+				this.tempVideoUrl = item.url || item;
+			},
+
+			/**
+			 * 是否img类型
+			 * @param {string, object} item 文件信息
+			 * */
+			fileUrlType(file) {
+				const filePath = file.path || file;
+
+				if (this.isBase64(filePath)) return 'image'
+
+				const fileType = filePath.split('.').pop();
+
+				const IMAGE_REGEXP = /(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg|image)/i
+				if (IMAGE_REGEXP.test(fileType)) {
+					return 'image';
+				} else {
+					return 'video';
+				}
+			},
+			isBase64(str) {
+				if (str === '' || typeof str !== 'string') return console.error('文件路径错误, base64', str);
+				return str.includes('blob:') || str.includes('data:image');
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.cl-updata {
+
+		.file-list {
+			display: grid;
+
+			&-row {
+				display: inline-flex;
+				align-items: center;
+				position: relative;
+
+				image {
+					height: 100%;
+					width: 100%;
+				}
+
+				._video {
+					position: relative;
+					width: 100%;
+					height: 100%;
+					overflow: hidden;
+				}
+
+				.video-fixed {
+					position: absolute;
+					top: 0;
+					left: 0;
+					bottom: 0;
+					width: 100%;
+					height: 100%;
+					border-radius: 10rpx;
+					z-index: 96;
+				}
+
+				.play {
+					position: absolute;
+					top: 50%;
+					left: 50%;
+					transform: translate(-50%, -50%);
+					width: 30%;
+					z-index: 95;
+				}
+
+				.app_play {
+					position: absolute;
+					top: 50%;
+					left: 50%;
+					transform: translate(-50%, -50%);
+					width: 50rpx;
+					height: 50rpx;
+				}
+
+				.remove {
+					position: absolute;
+					top: 0;
+					right: 0;
+					background-color: #373737;
+					height: 50rpx;
+					width: 50rpx;
+					border-bottom-left-radius: 200rpx;
+					z-index: 97;
+
+					.image {
+						width: 20rpx;
+						height: 20rpx;
+						position: absolute;
+						right: 12rpx;
+						top: 12rpx;
+					}
+				}
+			}
+
+			.add-image {
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				border: 2rpx dashed #ccc;
+
+				&:active {
+					opacity: 0.8;
+				}
+
+				image {
+					width: 40%;
+				}
+			}
+		}
+
+		.mask {
+			background-color: #000;
+			position: fixed;
+			top: 0;
+			right: 0;
+			bottom: 0;
+			left: 0;
+			z-index: 99;
+
+			.block {
+				padding: 0 30rpx;
+				position: absolute;
+				top: 50%;
+				left: 50%;
+				transform: translate(-50%, -50%);
+				width: 100%;
+
+				video {
+					width: 100%;
+					height: 78vh;
+				}
+			}
+
+			._root {
+				width: 60rpx;
+				height: 60rpx;
+				position: absolute;
+				left: 40rpx;
+				top: 5vh
+			}
+		}
+	}
+</style>

+ 240 - 0
xiaochengxu/uni_modules/cl-upload/readme.md

@@ -0,0 +1,240 @@
+### cl-upload 上传组件
+
+> 支持手动自动上传,样式调整,参数配置,预览,删除等功能
+
+
+> `注意:每次上传都需要回调函数接收参数并且添加到与组件绑定的数组中以保持数据一致,这样做是因为组件不知道服务端返回的数据格式,也可以在组件中修改promise格式 一劳永逸`
+
+### 注意事项
+1. ratio 图片比例属性部分手机不支持,可选用height属性代替
+2. 自定义播放按钮部分平台有兼容性问题,可选择性关闭
+3. 开启压缩图片返回的临时路径没有尾缀,官方api的问题。真机上没问题,也可以在上传的时候手动添加尾缀
+4. **视频地址必须`https`, http可能导致无法显示封面图**
+5. 如果没条件用`https`那就配置`cloudType: other`
+
+### H5体验地址
+![image](https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/eff364bc-65f7-47e0-ae4b-7d5f19b9f094.png)
+
+#### list数据格式
+
+1. 数组格式
+```
+['地址1','地址2']
+```
+2. JSON格式
+```
+[
+    {
+        path: '地址1',
+        // 其他信息
+    },
+    {
+        path: '地址2',
+        // 其他信息
+    },
+]
+```
+
+#### 基础使用
+
+```
+<cl-upload v-model="list" action="请求地址" @onSuccess="onSuccess"></cl-upload>
+
+methods: {
+    /**
+	 * 自动上传返回的是一张图片信息
+	 * 手动上传返回的是已选中所有图片或者视频信息
+	 * */ 
+	onSuccess(reslut) {
+		// 把服务端返回的图片地址添加到list中与组件数据同步
+		this.list.push(reslut.url)
+	},
+}
+```
+### uniCloud上传
+> 一句代码实现上传,就是这么简单
+
+```
+<cl-upload v-model="list" action="uniCloud"></cl-upload>
+```
+
+### 自定义样式
+> 通过 listStyle 控制每行数量、比例、行间距、列间距等常用样式
+
+```
+<cl-upload v-model="list" :listStyle="{
+	columns: 2,
+	columnGap: '20rpx',
+	rowGap:'20rpx',
+	padding:'10rpx',
+	height:'300rpx',
+	radius:'20rpx'
+}"></cl-upload>
+```
+
+### 预览模式
+> 关闭显示添加按钮和删除按钮
+```
+<cl-upload v-model="list" :add="false" :remove="false"></cl-upload>
+```
+
+### 手动上传
+
+> 通过 autoUpload 关闭掉自动上传,提交的时候通过 refs 主动调用组件上传方法,返回本次提交成功服务器返回数据
+
+```
+<cl-upload 
+	ref="upload2" 
+	v-model="list2" 
+	:autoUpload="false"></cl-upload>
+	
+<button @tap="submit">手动提交</button>
+
+methods: {
+    submit() {
+    	/**
+    	 * 主动调用组件上传方法
+    	 * */ 
+    	this.$refs.upload2.submit().then(reslut=>{
+    		console.log(reslut); // 本次提交成功服务器返回数据
+    		
+    		// 上传第三方服务器需要手动同步数据
+    		// 上传uniCloud则不需要
+    		const imgUrls = reslut.list.map(imgInfo=> imgInfo.url);
+    		this.list2 = [...this.list2, ...imgUrls]
+    	})
+    },
+}
+```
+
+### 配置删除前和上传前钩子 `1.3.0`
+```
+/ **
+* 开启删除前钩子 useBeforeDelete
+* 开启上传前钩子 useBeforeUpload
+*/
+<cl-upload v-model="list" 
+    useBeforeDelete 
+    useBeforeUpload
+    @beforeDelete="beforeDelete"
+    @beforeUpload="beforeUpload"></cl-upload>
+    
+
+methods: {
+    /**
+	 * 删除前钩子
+	 * @param {Object} item 当前删除的图片或者视频信息
+	 * @param {Number} index 当前删除的图片或视频索引
+	 * @param {Function} next 调用此函数继续执行组件删除逻辑
+	 * */ 
+	beforeDelete(item, index, next) {
+		uni.showModal({
+			title: '提示信息',
+			content: '确定要删除这个文件嘛?',
+			success: res => {
+				if (res.confirm) {
+					// 模拟服务器接口
+					setTimeout(() => {
+						next();
+					}, 1000);
+				}
+			}
+		});
+	},
+	/**
+	 * 上传前钩子
+	 * @param {Object} tempFile 当前上传文件信息
+	 * @param {Function} next 调用此函数继续执行组件上传逻辑
+	 * */
+	beforeUpload(tempFile, next) {
+		// 自己的上传逻辑
+		// 如果不需要走组件的上传逻辑就不用调用next(), 但是上传完要同步到list
+	}
+}
+```
+
+## API 
+
+| 参数 | 说明 | 类型 | 默认值 | 可选值 |
+| --- | --- | --- | --- | --- |
+| action | 上传地址 | String |-| uniCloud |
+| cloudType | 存储云类型(各个云服务截取封面方式不同。 other选项是video保底手段,部分平台有兼容性问题) | String |oss| 阿里云:oss  七牛云:vframe   腾讯云:process  其他:other |
+| headers | 设置上传的请求头部 | Object | - |-  |
+| data | 上传时附带的额外参数 | Object | - | - |
+| fileName| 标识符,即后端接口参数名 | String | file | - |
+| fileType | 文件类型 | String | all | 'image', 'video', 'all' |
+| imageFormData | 上传图片参数 | Object | - | - |
+| videoFromData | 上传视频参数 | Object  | - | - |
+| listStyle | 列表样式 |Object  | - | - |
+| isPreviewImage | 是否开启预览图片 | Boolean | true |false  |
+| remove | 是否显示删除按钮 | Boolean | true |false  |
+| add | 是否添加按钮 | Boolean | true |false  |
+| max | 最多显示数量 | Number | 9 | -  |
+| maxVideo | 视频最大上传数量 | Number | 不限制 |  - |
+| deleteTitle| 删除提示弹窗标题 | String | 提示 |  - |
+| deleteText| 删除提示弹窗文案 | String | 您确认要删除吗? | - |
+| loading| 是否显示加载 | Boolean | true | - |
+| loadingText| 加载文案 | String | 正在上传中... | - |
+| useBeforeDelete `1.3.0`| 是否开启删除前钩子  | Boolean | false  | true |
+| useBeforeUpload  `1.3.0`| 是否开启上传前钩子 | Boolean | false  | true |
+| addImg`1.3.6`| 添加按钮图片 | String | - |  - |
+| playImg`1.3.6`| 播放按钮图片 | String | - |  - |
+| deleteImg`1.3.6`| 删除按钮图片 | String | - |  - |
+| closeImg`1.3.6`| 关闭视频按钮图片 | String | - |  - |
+
+#### imageFormData
+
+| 参数 | 说明 | 类型 | 默认值 | 可选值 |
+| --- | --- | --- | --- | --- |
+| count | 最多可以选择的图片张数 | number |9| - |
+| sizeType | original 原图,compressed 压缩图 | array | 默认二者都有 |-  |
+| sourceType | 相册或者相机 | array |  ['camera ', 'album'] | ['camera ', 'album']  |
+| compress | 是否开启图片压缩 | Boolean | false | true  |
+| quality | 压缩质量 | number | 80 | -  |
+| size | 图片大小 | number | - | 单位MB |
+
+#### videoFromData
+
+| 参数 | 说明 | 类型 | 默认值 | 可选值 |
+| --- | --- | --- | --- | --- |
+| maxDuration | 拍摄视频最长拍摄时间 | number |60| 最多60秒 |
+| camera | 前摄像头后摄像头 | array | - |-  |
+| compressed | 是否压缩所选的视频源文件。 | Boolean | true |-  |
+| sourceType | 相册或者相机 | array |  ['camera ', 'album'] | ['camera ', 'album']  |
+| size | 视频大小 | number | - | 单位MB |
+
+#### listStyle
+
+| 参数 | 说明 | 类型 | 默认值 | 可选值 |
+| --- | --- | --- | --- | --- |
+| columns | 每行数量 | number |4| - |
+| columnGap | 行间距 | string | '40rpx' |-  |
+| rowGap | 列间距 | string | '40rpx' |-  |
+| padding | 列表内边距 | string | '0 0rpx' |-  |
+| ratio | 图片比例 | string | '1/1' | 低版本手机不支持,可以选择height属性  |
+| height | 图片高度 | string | '140rpx' |-  |
+| radius | 图片圆角 | string | '6rpx' |-  |
+
+#### Events
+
+| 事件名 | 说明 | 回调参数 |
+| --- | --- | --- |
+| onSuccess | 上传成功 | data: 服务器返回数据 |
+| onError | 上传失败 | error:错误信息 |
+| onImage | 点击图片 | item: 图片信息 index: 列表索引 |
+| onVideo | 点击视频 | item: 视频信息 index: 列表索引 |
+| onProgress | 上传进程 | onProgress参数说明|
+| onVideoMax | 触发视频最大数量限制 | maxVideo, fileLength|
+| onImageSize | 触发图片最大尺寸限制 | 图片信息 |
+| beforeDelete`1.3.0` | 删除前钩子 | item: 文件信息 index:文件索引 next:继续执行删除逻辑 |
+| beforeUpload `1.3.0`| 上传前钩子 | tempFile: 文件信息 next:继续执行删除逻辑 |
+
+#### onProgress参数说明
+| 事件名 | 说明 |
+| --- | --- |
+| progress | 上传进度百分比 |
+| totalBytesSent | 已经上传的数据长度 |
+| totalBytesExpectedToSend | 预期需要上传的数据总长度 |
+
+
+### [遇到问题或者讨论 uniapp 加入QQ群  553291781](https://jq.qq.com/?_wv=1027&k=5UkMN1QX)

+ 411 - 1
xiaochengxu/util/request.js

@@ -181,11 +181,421 @@ const syncInfo = (userInfo) => {
 	})
 	return promise
 }
+
+// 处理海报数据
+const makeCanvasData = (check_idx,height,posterObj1,theight) => {
+		console.log(height,theight)
+		var dth = (theight - 22) * 2
+		var h=((height+40)*2)
+		console.log(posterObj1)
+		var posterObj= {}
+		switch (check_idx) {
+			case 0:
+		posterObj={
+			width: '670rpx',
+			height: h+'rpx',
+			background: '#fff',
+			borderRadius: '16rpx',
+			views:[
+			
+						{
+							type: 'text',
+							text: posterObj1.title,
+							use:'title',
+							css: {
+								fontSize: '32rpx',
+								color: '#1A2033',
+								fontWeight: 'bold',
+								lineHeight: '45rpx',
+								left: '32rpx',
+								top: '410rpx',
+								width: '606rpx'
+							}
+						},
+						{
+							type: 'text',
+							text:  posterObj1.title1,
+							use:'title1',
+							css: {
+								fontSize: '28rpx',
+								fontWeight: 'bold',
+								color: '#1A2033',
+								lineHeight: '28rpx',
+								left: '32rpx',
+								top: 535+dth+'rpx'
+							}
+						},
+						{
+							type: 'text',
+							text: '长按或扫描识别二维码',
+							use:'title2',
+							css: {
+								fontSize: '24rpx',
+								color: '#4070FF',
+								lineHeight: '24rpx',
+								left: '32rpx',
+								top: 579+dth+'rpx'
+							}
+						},
+						{
+							type: 'image',
+							src:  posterObj1.image,
+							use:'dt',
+							css: {
+								left: '32rpx',
+								top: '44rpx',
+								width: '606rpx',
+								height: '341rpx',
+								borderRadius: '16rpx'
+							}
+						},
+						{
+							type: 'image',
+							src: posterObj1.qrcode,
+							use:'qrcode',
+							css: {
+								left: '455rpx',
+								top: 520+dth+'rpx',
+								// transform: 'translate(-50%,0)',
+								width: '240rpx',
+								height: '240rpx',
+							}
+						},
+						// {
+						// 	type: 'text',
+						// 	text: '————  由云现场提供技术支持  ————',
+						// 	css: {
+						// 		left: '0',
+						// 		top: '863rpx',
+						// 		width: '100%',
+						// 		textAlign: 'center',
+						// 		fontSize: '24rpx',
+						// 		color: '#989FB3',
+						// 		lineHeight: '33rpx'
+						// 	}
+						// }
+					],
+					}
+				break;
+			case 1:
+				posterObj={
+					width: '670rpx',
+					height: h+'rpx',
+					background: '#fff',
+					borderRadius: '16rpx',
+					views: [{
+						type: 'image',
+						use:'bg',
+						src: 'https://s.yun-live.com/images/20210201/39ae4d9d8ad0b1acac7c224e845c641f.png',
+						css: {
+							left: '0',
+							top: '0',
+							width: '100%',
+							height: '100%'
+						}
+					},
+					{
+						type: 'image',
+						src: posterObj1.image,
+						css: {
+							left: '32rpx',
+							top: '44rpx',
+							width: '606rpx',
+							height: '341rpx',
+							borderRadius: '16rpx'
+						}
+					},
+					{
+						type: 'text',
+						text: posterObj1.title,
+						use:'title',
+						css: {
+							fontSize: '32rpx',
+							color: '#fff',
+							fontWeight: 'bold',
+							lineHeight: '45rpx',
+							left: '32rpx',
+							top: '420rpx',
+							width: '590rpx'
+						}
+					},
+					{
+						type: 'text',
+						text:posterObj1.title1,
+						use:'title1',
+						css: {
+							fontSize: '28rpx',
+							fontWeight: 'bold',
+							color: '#D8AB87',
+							lineHeight: '28rpx',
+							left: '32rpx',
+							top: 535+dth+'rpx'
+						}
+					},
+					{
+						type: 'text',
+						text: '长按或扫描识别二维码',
+						use:'title2',
+						css: {
+							fontSize: '24rpx',
+							color: '#FFFFFF',
+							lineHeight: '24rpx',
+							left: '32rpx',
+							top: 579+dth+'rpx'
+						}
+					},
+					
+					{
+						type: 'image',
+						src: 'https://s.yun-live.com/images/20210201/d88d56843d43b917e2a28550b2a62723.png',
+						css: {
+							left: '0rpx',
+							top: '90%',
+							width: '103rpx',
+							height: '103rpx',
+						}
+					},
+					{
+						type: 'image',
+						src: 'https://s.yun-live.com/images/20210201/247736ffd279276b891ec14db8ed0fd0.png',
+						css: {
+							left: '600rpx',
+							top: '50%',
+							width: '56.4rpx',
+							height: '56.4rpx',
+						}
+					},
+					{
+						type: 'view',
+						use:'qrcode-view',
+						css: {
+							left: '445rpx',
+							top: 520+dth+'rpx',
+							width: '240rpx',
+							height: '240rpx',
+							background: '#fff',
+						}
+					},
+					{
+						type: 'image',
+						src: posterObj1.qrcode,
+						use:'qrcode',
+						css: {
+							left: '455rpx',
+							top: 530+dth+'rpx',
+							// transform: 'translate(-50%,0)',
+							width: '220rpx',
+							height: '220rpx',
+						}
+					},
+				]
+			}
+				break;
+			case 2:
+						posterObj={
+							width: '670rpx',
+							height: h+'rpx',
+							background: '#fff',
+							borderRadius: '16rpx',
+							views: [{
+								type: 'image',
+								use:'bg',
+								src: 'https://s.yun-live.com/images/20210201/78f227bd701da20676c9da9166ce3144.png',
+								css: {
+									left: '0',
+									top: '0',
+									width: '100%',
+									height: '100%'
+								}
+							},
+							{
+								type: 'image',
+								src: posterObj1.image,
+								css: {
+									left: '62rpx',
+									top: '84rpx',
+									width: '540rpx',
+									height: '304rpx',
+									borderRadius: '16rpx'
+								}
+							},
+							{
+								type: 'text',
+								text: posterObj1.title,
+								use:'title',
+								css: {
+									fontSize: '32rpx',
+									color: '#1D1D25',
+									fontWeight: 'bold',
+									lineHeight: '45rpx',
+									left: '62rpx',
+									top: '420rpx',
+									width: '530rpx'
+								}
+							},
+							{
+								type: 'text',
+								text:posterObj1.title1,
+								use:'title1',
+								css: {
+									fontSize: '28rpx',
+									fontWeight: 'bold',
+									color: '#1D1D25',
+									lineHeight: '28rpx',
+									left: '62rpx',
+									top: 515+dth+'rpx'
+								}
+							},
+							{
+								type: 'text',
+								text: '长按或扫描识别二维码',
+								use:'title2',
+								css: {
+									fontSize: '24rpx',
+									color: '#6CB37A',
+									lineHeight: '24rpx',
+									left: '62rpx',
+									top: 559+dth+'rpx'
+								}
+							},
+
+							{
+								type: 'view',
+								use:'qrcode-view',
+								css: {
+									left: '405rpx',
+									top: 500+dth+'rpx',
+									width: '240rpx',
+									height: '240rpx',
+									background: '#fff',
+								}
+							},
+							{
+								type: 'image',
+								src: posterObj1.qrcode,
+								use:'qrcode',
+								css: {
+									left: '415rpx',
+									top: 510+dth+'rpx',
+									// transform: 'translate(-50%,0)',
+									width: '220rpx',
+									height: '220rpx',
+								}
+							},]
+						}
+				break;
+			case 3:
+				posterObj={
+					width: '670rpx',
+					height: h+'rpx',
+					background: '#fff',
+					borderRadius: '16rpx',
+					views: [{
+						type: 'image',
+						use:'bg',
+						src: 'https://s.yun-live.com/images/20210201/524ab6a41fe8c7eb57b35df9a547d388.png',
+						css: {
+							left: '0',
+							top: '0',
+							width: '100%',
+							height: '100%'
+						}
+					},
+					{
+						type: 'image',
+						src: posterObj1.image,
+						css: {
+							left: '32rpx',
+							top: '44rpx',
+							width: '606rpx',
+							height: '341rpx',
+							borderRadius: '16rpx'
+						}
+					},
+					{
+						type: 'text',
+						text: posterObj1.title,
+						use:'title',
+						css: {
+							fontSize: '32rpx',
+							color: '#1D1D25',
+							fontWeight: 'bold',
+							lineHeight: '45rpx',
+							left: '32rpx',
+							top: '410rpx',
+							width: '530rpx'
+						}
+					},
+					{
+						type: 'text',
+						text:posterObj1.title1,
+						use:'title1',
+						css: {
+							fontSize: '28rpx',
+							fontWeight: 'bold',
+							color: '#1D1D25',
+							lineHeight: '28rpx',
+							left: '32rpx',
+							top: 525+dth+'rpx'
+						}
+					},
+					{
+						type: 'text',
+						text: '长按或扫描识别二维码',
+						use:'title2',
+						css: {
+							fontSize: '24rpx',
+							color: '#6CB37A',
+							lineHeight: '24rpx',
+							left: '32rpx',
+							top: 569+dth+'rpx'
+						}
+					},
+				
+					{
+						type: 'view',
+						use:'qrcode-view',
+						css: {
+							left: '440rpx',
+							top: 500+dth+'rpx',
+							width: '240rpx',
+							height: '240rpx',
+							background: '#fff',
+						}
+					},
+					{
+						type: 'image',
+						src: posterObj1.qrcode,
+						use:'qrcode',
+						css: {
+							left: '450rpx',
+							top: 510+dth+'rpx',
+							// transform: 'translate(-50%,0)',
+							width: '220rpx',
+							height: '220rpx',
+						}
+					},]
+				}
+				// this.posterObj1.backgroundType='image'
+				// this.posterObj1.background='https://s.yun-live.com/images/20210201/524ab6a41fe8c7eb57b35df9a547d388.png';
+				// this.posterObj1.width='606rpx',
+				// this.posterObj1.height='341rpx',
+				// this.posterObj1.titleCssColor='#1D1D25'
+				// this.posterObj1.title1CssColor='#1D1D25'
+				// this.posterObj1.title2CssColor='#6CB37A'
+				// break;
+			default:
+				break;
+		}
+
+	return posterObj
+}
 export default {
 	baseUrl,
 	baseRequest,
 	TokenRequest,
 	wxlogin,
 	getPhone,
-	syncInfo
+	syncInfo,
+	makeCanvasData
 }