puyao пре 4 месеци
родитељ
комит
d880417559
100 измењених фајлова са 17910 додато и 1399 уклоњено
  1. 258 0
      App.vue
  2. 248 3
      api/courseManage.js
  3. 4 0
      api/user.js
  4. 137 0
      assets/css/common.scss
  5. 31 0
      assets/css/theme.scss
  6. 7 3
      common/request.js
  7. 3 3
      manifest.json
  8. 1022 40
      package-lock.json
  9. 8 2
      package.json
  10. 126 1
      pages.json
  11. 2 2
      pages/auth/login.vue
  12. 101 0
      pages/courseManage/components/chart.vue
  13. 315 30
      pages/courseManage/components/courseItem.vue
  14. 291 241
      pages/courseManage/components/dropdownPanel.vue
  15. 250 36
      pages/courseManage/components/vipUserItem.vue
  16. 228 0
      pages/courseManage/course/becomeVip.vue
  17. 117 73
      pages/courseManage/course/index.vue
  18. 1814 0
      pages/courseManage/course/learning.vue
  19. 22 0
      pages/courseManage/course/living.vue
  20. 378 65
      pages/courseManage/dataIndex/index.vue
  21. 59 17
      pages/courseManage/index.vue
  22. 158 77
      pages/courseManage/live/index.vue
  23. 342 0
      pages/courseManage/manage/changeVip.vue
  24. 47 0
      pages/courseManage/manage/exprotList.vue
  25. 449 0
      pages/courseManage/manage/index.vue
  26. 188 0
      pages/courseManage/manage/lableSetup.vue
  27. 745 0
      pages/courseManage/manage/manageDetail.vue
  28. 54 0
      pages/courseManage/manage/setup.vue
  29. 515 0
      pages/courseManage/manage/userDataDetail.vue
  30. 643 0
      pages/courseManage/operation/index.vue
  31. 881 669
      pages/courseManage/statistics.vue
  32. 306 0
      pages/courseManage/vip/ManageDetail.vue
  33. 22 0
      pages/courseManage/vip/dropDownList.vue
  34. 781 137
      pages/courseManage/vip/index.vue
  35. 28 0
      project.config.json
  36. 7 0
      project.private.config.json
  37. BIN
      static/image/becomevip.png
  38. BIN
      static/image/changePlayer-icon.png
  39. BIN
      static/image/course_answer_img.png
  40. BIN
      static/image/course_answer_incorrectly_img.png
  41. BIN
      static/image/course_arrow_down_icon.png
  42. BIN
      static/image/course_arrow_up_icon.png
  43. BIN
      static/image/course_close_white_icon.png
  44. BIN
      static/image/downicon.png
  45. BIN
      static/image/point.png
  46. BIN
      static/image/red_envelope_btnimg.png
  47. BIN
      static/image/red_envelope_img.png
  48. BIN
      static/image/safe.png
  49. BIN
      static/image/tc_close_icon.png
  50. BIN
      static/image/tips_title_img.png
  51. BIN
      static/image/video_icon.png
  52. BIN
      static/image/wechat.png
  53. BIN
      static/image/wxmore.png
  54. BIN
      static/logo.png
  55. BIN
      static/manageTabIcon/data.png
  56. BIN
      static/manageTabIcon/data_on.png
  57. BIN
      static/manageTabIcon/liveclasses.png
  58. BIN
      static/manageTabIcon/liveclasses_on.png
  59. BIN
      static/manageTabIcon/manage.png
  60. BIN
      static/manageTabIcon/manage_on.png
  61. BIN
      static/manageTabIcon/training.png
  62. BIN
      static/manageTabIcon/training_on.png
  63. BIN
      static/manageTabIcon/vip.png
  64. BIN
      static/manageTabIcon/vip_on.png
  65. BIN
      static/manergevip/Refresh.png
  66. BIN
      static/manergevip/becomeTrue.png
  67. BIN
      static/manergevip/book.png
  68. BIN
      static/manergevip/no-vip.png
  69. BIN
      static/manergevip/phone.png
  70. BIN
      static/manergevip/vip.png
  71. 225 0
      uni_modules/lime-painter/changelog.md
  72. 150 0
      uni_modules/lime-painter/components/common/relation.js
  73. 28 0
      uni_modules/lime-painter/components/l-painter-image/l-painter-image.vue
  74. 27 0
      uni_modules/lime-painter/components/l-painter-qrcode/l-painter-qrcode.vue
  75. 33 0
      uni_modules/lime-painter/components/l-painter-text/l-painter-text.vue
  76. 34 0
      uni_modules/lime-painter/components/l-painter-view/l-painter-view.vue
  77. 461 0
      uni_modules/lime-painter/components/l-painter/l-painter.vue
  78. 214 0
      uni_modules/lime-painter/components/l-painter/nvue.js
  79. 0 0
      uni_modules/lime-painter/components/l-painter/painter.js
  80. 56 0
      uni_modules/lime-painter/components/l-painter/props.js
  81. 0 0
      uni_modules/lime-painter/components/l-painter/single.js
  82. 368 0
      uni_modules/lime-painter/components/l-painter/utils.js
  83. 166 0
      uni_modules/lime-painter/components/lime-painter/lime-painter.vue
  84. 119 0
      uni_modules/lime-painter/hybrid/html/index.html
  85. 0 0
      uni_modules/lime-painter/hybrid/html/painter.js
  86. 0 0
      uni_modules/lime-painter/hybrid/html/uni.webview.1.5.3.js
  87. 93 0
      uni_modules/lime-painter/package.json
  88. 388 0
      uni_modules/lime-painter/parser.js
  89. 961 0
      uni_modules/lime-painter/readme.md
  90. 320 0
      uni_modules/qiun-data-charts/changelog.md
  91. 1618 0
      uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue
  92. 42 0
      uni_modules/qiun-data-charts/components/qiun-error/qiun-error.vue
  93. 162 0
      uni_modules/qiun-data-charts/components/qiun-loading/loading1.vue
  94. 170 0
      uni_modules/qiun-data-charts/components/qiun-loading/loading2.vue
  95. 173 0
      uni_modules/qiun-data-charts/components/qiun-loading/loading3.vue
  96. 222 0
      uni_modules/qiun-data-charts/components/qiun-loading/loading4.vue
  97. 229 0
      uni_modules/qiun-data-charts/components/qiun-loading/loading5.vue
  98. 36 0
      uni_modules/qiun-data-charts/components/qiun-loading/qiun-loading.vue
  99. 422 0
      uni_modules/qiun-data-charts/js_sdk/u-charts/config-echarts.js
  100. 606 0
      uni_modules/qiun-data-charts/js_sdk/u-charts/config-ucharts.js

+ 258 - 0
App.vue

@@ -136,6 +136,8 @@
 <style lang="scss">
 	/*每个页面公共css */
 	@import "@/uni_modules/uview-ui/index.scss";
+	@import '@/assets/css/theme.scss';
+	@import '@/assets/css/common.scss';
 	view{
 		box-sizing: border-box;
 	}
@@ -171,6 +173,262 @@
 			 
 		}
 	}
+		
+	
+	
+		.w-calc-30 {
+			padding: 0 30rpx;
+			width: calc(100% - 60rpx);
+		}
+	
+		.hb {
+			height: 100%;
+			box-sizing: border-box;
+		}
+	
+		.hidden {
+			overflow: hidden;
+		}
+	
+		.base-color {
+			color: $--base-color;
+		}
+	
+		.base-color-2 {
+			color: $--base-color2;
+		}
+	
+		.base-color-3 {
+			color: $--base-color3;
+		}
+		.base-color-9 {
+			color: $--base-color-9;
+		}
+		.base-color-8 {
+			color: $--base-color-f8;
+		}
+		.base-color-6 {
+			color: $--base-color-6;
+		}
+		.base-color-gray {
+			color: $--base-color-gray;
+		}
+		.base-color-red{
+				color: $--base-color-red;
+		}
+		.base-color-dark {
+			color: $--base-color-dark;
+		}
+	
+		.base-color-dark2 {
+			color: $--base-color-dark2;
+		}
+	
+		.base-price {
+			color: $--base-color-price;
+		}
+	
+		.base-success {
+			color: $--base-color-success;
+		}
+	
+		.base-bg {
+			background: $--base-bg;
+		}
+	
+		.base-bg-2 {
+			background: $--base-bg2;
+		}
+		.base-bg-red{
+			background: $--base-bg-red;
+		}
+		.base-bg-f{
+			background-color: $--base-bg-f;
+		}
+		.base-bg-f8{
+			background-color: $--base-color-f8;
+		}
+		.base-bg-f5{
+			background-color: $--base-color-f5;
+		}
+		.base-bg-9{
+			background-color: $--base-color-9;
+		}
+		.base-bg-blue{
+			background:$--base-bg-blue;
+		}
+		.base-bg-sure{
+			background:$--base-sure-bg;
+		}
+		.base-bg-orange{
+			background:$--base-bg-orange;
+		}
+		.base-bg-false{
+			background:$--base-false-bg;
+		}
+		.bor-blue{
+			border: 2rpx solid $--base-bor-blue;
+		}
+		.bor-red{
+			border: 2rpx solid $--base-bor-red;
+		}
+		.colorf {
+			color: #fff;
+		}
+	
+		.bgf {
+			background: #fff;
+		}
+	
+		.fixed {
+			position: fixed;
+		}
+	
+		.absolute {
+			position: absolute;
+		}
+	
+		.relative {
+			position: relative;
+		}
+	
+		.w100 {
+			width: 100%;
+		}
+	
+		.h100 {
+			height: 100%;
+		}
+	
+		.card {
+			background: #fff;
+			border-radius: 15rpx;
+		}
+	
+		.cover-height {
+			height: 100%;
+			display: flex;
+			flex-direction: column;
+			box-sizing: border-box;
+		}
+	
+		.row {
+			display: flex;
+			flex-direction: row;
+		}
+	
+		.column {
+			display: flex;
+			flex-direction: column;
+		}
+	
+		.justify-start {
+			display: flex;
+			justify-content: flex-start;
+		}
+	
+		.justify-center {
+			display: flex;
+			justify-content: center;
+		}
+	
+		.justify-end {
+			display: flex;
+			justify-content: flex-end;
+		}
+	
+		.justify-around {
+			display: flex;
+			justify-content: space-around;
+		}
+		.justify-evenly {
+			display: flex;
+			justify-content: space-evenly;
+		}
+	
+		.justify-between {
+			display: flex;
+			justify-content: space-between;
+		}
+	
+		.align-start {
+			display: flex;
+			align-items: flex-start;
+		}
+	
+		.align-center {
+			display: flex;
+			align-items: center;
+		}
+	
+		.align-end {
+			display: flex;
+			align-items: flex-end;
+		}
+	
+		.center {
+			display: flex;
+			justify-content: center;
+			align-items: center;
+		}
+	
+		.centerV {
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			flex-direction: column;
+		}
+	
+		.wrap {
+			flex-wrap: wrap;
+		}
+	
+		.flex-1 {
+			flex: 1;
+		}
+	
+		.ellipsis {
+			overflow: hidden;
+			text-overflow: ellipsis;
+			display: -webkit-box;
+			-webkit-box-orient: vertical;
+			box-sizing: border-box;
+			width: 100%;
+			-webkit-line-clamp: 1;
+		}
+	
+		.lines-2 {
+			-webkit-line-clamp: 2 !important;
+		}
+	
+		.lines-3 {
+			-webkit-line-clamp: 3 !important;
+		}
+	
+		.bold {
+			font-weight: bold;
+		}
+	
+		.line-through {
+			text-decoration: line-through;
+		}
+	
+		.nowrap {
+			white-space: nowrap;
+		}
+	
+		.scrollx {
+			overflow-x: scroll;
+		}
+	
+		.scrolly {
+			overflow-y: scroll;
+		}
+	
+		.cvauto {
+			content-visibility: auto;
+		}
+
 </style>
 <style lang="less">
 /*每个页面公共css */

+ 248 - 3
api/courseManage.js

@@ -23,18 +23,18 @@ export function getRecPacketCount(data) {
 
 // 获取课程列表
 export function getFsCourseList(data) {
-	return request('/fsCourseRedPacket/getFsCourseList', data,'POST','application/json;charset=UTF-8');
+	return request('/fsCourse/getFsCourseList', data,'POST','application/json;charset=UTF-8');
 }
 
 // 根据训练营查询节目
 export function getCourseVdieoList(data) {
-	const url = '/fsCourseRedPacket/getCourseVdieoPageList?courseId='+data.courseId +'&pageNum='+data.pageNum + '&pageSize='+data.pageSize+'&status='+data.status
+	const url = '/fsCourse/getCourseVdieoPageList?courseId='+data.courseId +'&pageNum='+data.pageNum + '&pageSize='+data.pageSize+'&status='+data.status
 	return request(url, null,'POST','application/json;charset=UTF-8');
 }
 
 // 根据观看记录查询用户
 export function getUserLogListByCourseId(data) {
-	const url = '/fsCourseRedPacket/getUserLogListByCourseId'
+	const url = '/fsCourse/getUserLogListByCourseId'
 	return request(url, data,'POST','application/json;charset=UTF-8');
 }
 
@@ -42,4 +42,249 @@ export function getUserLogListByCourseId(data) {
 export function updateFsUser(data) {
 	const url = '/fsUser/updateFsUser'
 	return request(url, data,'POST','application/json;charset=UTF-8');
+}
+
+//根据经销商或群管查询会员列表
+export function getfsuserList(data){
+	const url = '/fsUser/getFsUserList'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+}
+
+//查询公司标签列表
+ export function getcompanyTaglist(data) {
+ 	 return request('/company/companyTag/list',data,'GET');
+ }
+  
+  //添加黑名单
+  export function Addblacklist(data){
+  	const url = '/black'
+  	return request(url, data, 'POST','application/json;charset=UTF-8')
+  }
+  
+  //修改公司会员打标签
+  export function changeLable(data){
+  	const url = '/company/companyTagUser/edit'
+  	return request(url, data, 'POST','application/json;charset=UTF-8')
+  }
+  
+  //修改公司会员打标签
+  export function getBlackvipNumber(data){
+  	const url = '/black/blackCount'
+  	return request(url, data, 'POST','application/json;charset=UTF-8')
+  }
+  
+  //查询被拉黑的用户列表
+  export function getBlackvipList(data){
+  	const url = '/fsUser/getBlackUserList'
+  	return request(url, data, 'POST','application/json;charset=UTF-8')
+  }
+  
+  //查询答题数据(会员详情)
+  export function getanswerlist(data){
+  	const url = '/fsAnswerLog/getUserAnswerCount'
+  	return request(url, data, 'POST','application/json;charset=UTF-8')
+  }
+  
+  //移除黑名单(会员详情)
+  export function removebalcklist(data){
+  	const url = '/black/delete'
+  	return request(url, data, 'GET','application/json;charset=UTF-8')
+  }
+  
+  
+  //生成小节课程 (链接)
+  export function sharecourselink(data){
+  	const url = '/fsCourseWatchLog/createLinkUrl'
+  	return request(url, data, 'POST','application/json;charset=UTF-8')
+  }
+  
+  //修改用户名称
+  export function changeUserName(data){
+  	const url = '/fsUser/updateFsUser'
+  	return request(url, data, 'POST','application/json;charset=UTF-8')
+  }
+  
+  //查询用户详情
+  export function getuserdetail(data){
+  	const url = '/fsUser'
+  	return request(url, data, 'GET','application/json;charset=UTF-8')
+  } 
+  
+  //查询看课转化率
+  export function getcourseRate(data){
+  	const url = '/fsCourse/getConversion'
+  	return request(url, data, 'POST','application/json;charset=UTF-8')
+  } 
+  
+  //标签统计
+  export function getlableNum(data){
+  	const url = '/company/companyTag/getTagCount'
+  	return request(url, data, 'GET','application/json;charset=UTF-8')
+  } 
+  
+//根据经销商或群管查询会员总数
+  export function getvipNum(data){
+  	const url = '/fsUser/getFsUserListCount'
+  	return request(url, data, 'POST','application/json;charset=UTF-8')
+  } 
+  
+//根据经销商 运营:查询课程数,群管数,参与会员
+export function getshopCoursenum(data){
+	const url = '/fsCourse/getCourseVdieoCount'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+} 
+
+//根据经销商 获取群管排名
+export function getGroupRanklist(data){
+	const url = '/companyUser/getCompanyUserTOP20'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+} 
+
+//根据经销商 获取课程排名
+export function getCourseRanklist(data){
+	const url = '/fsCourse/getCourseTOP20'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+} 
+
+//根据经销商 获取红包领取记录
+export function getCourseRedPacklist(data){
+	const url = '/fsCourseRedPacket/getRedPacketLogList'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+} 
+
+//根据经销商 获取账户可用余额
+export function getuserbalance(data){
+	const url = '/companyUser/getCompanyBalance'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+} 
+
+//根据经销商 查询所属群管
+export function getusergroup(data){
+	const url = '/companyUser/getCompanyUserList'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+} 
+
+//根据经销商 新增公司标签
+export function addCompanyLabel(data){
+	const url = '/company/companyTag'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+} 
+
+//根据经销商 删除公司标签
+export function deleteCompanyLabel(data){
+	const url = '/company/companyTag/delete'
+	return request(url, data, 'GET','application/json;charset=UTF-8')
+}
+
+//根据经销商 获取红包价格表
+export function getredPrice(data){
+	const url = '/fsCourseRedPacket/getRedPacketList'
+	return request(url, data, 'GET','application/json;charset=UTF-8')
+}
+
+//根据经销商 查询所属群管列表
+export function getgroupList(data){
+	const url = '/companyUser/CompanyUserListByCompanyId'
+	return request(url, data, 'GET','application/json;charset=UTF-8')
+}
+
+//根据经销商 查询群管的课程
+export function getcourseList(data){
+	const url = '/fsCourse/courseList'
+	return request(url, data, 'GET','application/json;charset=UTF-8')
+}
+
+//根据经销商 修改群管信息
+export function updategroupinfo(data){
+	const url = '/companyUser/updateCompanyUser'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+}
+
+//根据经销商 获取群管详情
+export function getGroupDetail(data){
+	const url = '/companyUser/getCompanyUser'
+	return request(url, data, 'GET','application/json;charset=UTF-8')
+}
+
+//根据经销商 批量更换会员归属
+export function changevipUser(data){
+	const url = '/companyUser/updateCompanyUseruser'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+}
+
+//根据经销商  修改全部会员群管
+export function changevipUserAll(data){
+	const url = '/companyUser/updateAllCompanyUser'
+	return request(url, data, 'GET','application/json;charset=UTF-8')
+}
+
+//根据经销商  查询标签下会员
+export function gettagsUser(data){
+	const url = '/company/companyTagUser/getUserList'
+	return request(url, data, 'POST','application/json;charset=UTF-8')
+}
+
+// h5课程简介
+export function getH5CourseByVideoId(data) {
+	return request('/fsCourse/getH5CourseByVideoId', data, 'GET');
+}
+
+// h5课程详情加问答
+export function getH5CourseVideoDetails(data) {
+	return request('/fsCourse/getH5CourseVideoDetails', data, 'GET');
+}
+
+// 答题发红包
+export function courseAnswer(data) {
+	return request('/fsCourse/courseAnswer', data, 'POST', 'application/json;charset=UTF-8');
+}
+
+// 发送奖励
+export function sendReward(data) {
+	return request('/fsCourse/sendReward', data, 'POST', 'application/json;charset=UTF-8');
+}
+
+// 课程完成
+export function getFinishCourseVideo(data) {
+	return request('/fsCourse/getFinishCourseVideo', data, 'POST', 'application/json;charset=UTF-8');
+}
+
+// 课程完成
+export function getaddcourseLog(data) {
+	return request('/fsCourseWatchLog/addCourseLog', data, 'POST', 'application/json;charset=UTF-8');
+}
+
+// 生成海报和看图二维码
+export function buildCode(data) {
+	return request('/fsCourse/generatePostAndQrCode',data, 'POST',  'application/json;charset=UTF-8');
+}
+
+//是否可以看课
+export function idlookCourse(data) {
+	return request('/fsCourse/isWatch', data, 'POST', 'application/json;charset=UTF-8');
+}
+
+//点击链接后添加课程记录
+export function clickCourse(data) {
+	return request('/fsCourseWatchLog/addCourseLog', data, 'POST', 'application/json;charset=UTF-8');
+}
+
+//用户成为会员
+export function becomeVip(data) {
+	return request('/fsUser/addFsUserByCompanyUserAndCompany', data, 'POST', 'application/json;charset=UTF-8');
+}
+
+//生成分享链接,新建会员
+export function becomeVipuser(data) {
+	return request('/companyUser/generateLink', data, 'POST', 'application/json;charset=UTF-8');
+}
+
+//生成分享海报,新建会员
+export function becomeVipuserImg(data) {
+	return request('/companyUser/generatePostAndQrCode', data, 'POST', 'application/json;charset=UTF-8');
+}
+
+//获取jssdk
+export function getSDK(data){
+	return request('/app/wx/mp/authPage', data, 'GET', 'application/json;charset=UTF-8');
 }

+ 4 - 0
api/user.js

@@ -23,5 +23,9 @@ let request = new Request().http
  	 return request('/app/user/getUserInfo',data,'GET');
  }
  
+ export function loginByMp(data) {
+ 	 return request('/app/user/loginByMp',data,'POST','application/json;charset=UTF-8');
+ }
+ 
  
  

+ 137 - 0
assets/css/common.scss

@@ -0,0 +1,137 @@
+// 字体
+@for $i from 20 through 100{
+    .fs#{$i} {
+			font-size: #{$i}rpx;
+    }
+}
+// 颜色
+@for $i from 0 through 9{
+    .color#{$i} {
+			color: #{$i}#{$i}#{$i};
+    }
+}
+// padding
+@for $i from 1 through 100{
+    .p#{$i} {
+			padding: #{$i}rpx;
+    }
+}
+@for $i from 1 through 100{
+    .ptb#{$i} {
+			padding-top: #{$i}rpx ;
+			padding-bottom: #{$i}rpx ;
+    }
+}
+@for $i from 1 through 100{
+    .plr#{$i} {
+			padding-left: #{$i}rpx;
+			padding-right: #{$i}rpx;
+    }
+}
+@for $i from 1 through 200{
+    .pt#{$i} {
+			padding-top: #{$i}rpx;
+    }
+}
+@for $i from 1 through 100{
+    .pl#{$i} {
+			padding-left: #{$i}rpx;
+    }
+}
+@for $i from 1 through 100{
+    .pr#{$i} {
+			padding-right: #{$i}rpx;
+    }
+}
+@for $i from 1 through 200{
+    .pb#{$i} {
+			padding-bottom: #{$i}rpx;
+    }
+}
+// margin
+@for $i from 1 through 100{
+    .m#{$i} {
+			margin: #{$i}rpx;
+    }
+}
+@for $i from 1 through 100{
+    .mtb#{$i} {
+			margin-top: #{$i}rpx ;
+			margin-bottom: #{$i}rpx ;
+    }
+}
+@for $i from 1 through 100{
+    .mlr#{$i} {
+			margin-left: #{$i}rpx;
+			margin-right: #{$i}rpx;
+    }
+}
+@for $i from 1 through 100{
+    .mt#{$i} {
+			margin-top: #{$i}rpx;
+    }
+}
+@for $i from 1 through 100{
+    .ml#{$i} {
+			margin-left: #{$i}rpx;
+    }
+}
+@for $i from 1 through 100{
+    .mr#{$i} {
+			margin-right: #{$i}rpx;
+    }
+}
+@for $i from 1 through 100{
+    .mb#{$i} {
+			margin-bottom: #{$i}rpx;
+    }
+}
+// 圆角
+@for $i from 0 through 100{
+    .radius#{$i} {
+			border-radius: #{$i}rpx;
+    }
+}
+// padding 左右+width自动计算
+@for $i from 10 through 60{
+    .w-calc-#{$i} {
+			width: calc(100% - #{$i*2}rpx);
+			padding-left:  #{$i}rpx;
+			padding-right:  #{$i}rpx;
+    }
+}
+
+// gap
+@for $i from 1 through 100{
+	.gap#{$i} {
+		gap: #{$i}rpx;
+	}
+}
+
+// 高
+@for $i from 1 through 900{
+	.h#{$i} {
+		height: #{$i}rpx;
+	}
+}
+// 宽
+@for $i from 1 through 900{
+	.w#{$i} {
+		width: #{$i}rpx;
+	}
+}
+// 宽
+@for $i from 1 through 900{
+	.lh#{$i} {
+		line-height: #{$i}rpx;
+	}
+}
+
+
+// font-weight
+$steps: 100 200 300 400 500 600 700 800 bold;
+@each $i in $steps {
+	.weight-#{$i} {
+		font-weight: $i;
+	}
+}

+ 31 - 0
assets/css/theme.scss

@@ -0,0 +1,31 @@
+/*自定义主题色 */
+$--base-color:#1773ff;
+$--base-color2:#e7f1fe;
+$--base-color3:#425034;
+$--base-color-price:#FF1212;
+$--base-color-gray:#DEDFE4;
+$--base-color-dark:#313131;
+$--base-color-red:#ef4c50;
+$--base-color-dark2:#3E3E3E;
+$--base-color-success:#a3db42;
+$--base-color-false:#f93e3e;
+$--base-color-0:#000;
+$--base-color-1:#111;
+$--base-color-3:#333;
+$--base-color-6:#666;
+$--base-color-9:#999;
+$--base-color-f8:#f8f8f8;
+$--base-color-f5:#f5f5f5;
+$--base-bg: #1773ff;
+$--base-bg2:#e7f1fe;
+$--base-bor-red:#f7a1a1;
+$--base-bor-blue:#c9e1fb;
+$--base-cont-bg:#F3F5F9;
+$--base-false-bg:#fae7e7;
+$--base-sure-bg:#e7f2fe;
+$--base-bg-f:#fff;
+$--base-bg-red:#ee0a25;
+$--base-bg-blue:#1677ff;
+$--base-bg-orange:#FF7F00;
+
+

+ 7 - 3
common/request.js

@@ -3,13 +3,16 @@ export default class Request {
 	http(router, data = {}, method,contentType) {
 		let that = this;
 		// let path = 'http://42.194.245.189:8007';
-		let path = 'https://43893ep95pe7.vicp.fun'
+		// let path = 'http://42.194.245.189:8007';
+		// let path = 'http://192.168.10.155:8007'
+		let path = 'http://h5api.wxcourse.cdwjyyh.com'
 		uni.setStorageSync('requestPath',path)
 		// uni.showLoading({
 		// 	title: '加载中'
 		// });
 		return new Promise((resolve, reject) => {
 			let token = uni.getStorageSync('AppToken');
+			let usertoken = uni.getStorageSync("UserAppToken")
 			var httpContentType='application/x-www-form-urlencoded';
 			if(contentType!=undefined){
 				//application/json;charset=UTF-8
@@ -21,7 +24,8 @@ export default class Request {
 				header: {
 					// 'Content-Type': 'application/x-www-form-urlencoded',
 					'Content-Type': httpContentType,
-					'AppToken': token
+					'AppToken': token,
+					'UserAppToken':usertoken
 				},
 				url: `${path}${router}`,
 				data: data,
@@ -49,7 +53,7 @@ export default class Request {
 						return;
 					}
 					if (res.token) {
-						uni.setStorageSync('AppToken',res.token)
+						uni.setStorageSync('UserAppToken',res.token)
 					}
 					resolve(res.data)
 				},

+ 3 - 3
manifest.json

@@ -1,6 +1,6 @@
 {
-    "name" : "AI客服",
-    "appid" : "__UNI__8A0233D",
+    "name" : "御君方管理",
+    "appid" : "__UNI__7F50713",
     "description" : "",
     "versionName" : "1.0.8",
     "versionCode" : 108,
@@ -109,7 +109,7 @@
     "quickapp" : {},
     /* 小程序特有相关 */
     "mp-weixin" : {
-        "appid" : "wxece08028880b35e1",
+        "appid" : "wx93ce67750e3cfba3",
         "setting" : {
             "urlCheck" : true
         },

+ 1022 - 40
package-lock.json

@@ -1,74 +1,1056 @@
 {
   "name": "stoer",
   "version": "1.0.0",
-  "lockfileVersion": 1,
+  "lockfileVersion": 3,
   "requires": true,
-  "dependencies": {
-    "decode-uri-component": {
+  "packages": {
+    "": {
+      "name": "stoer",
+      "version": "1.0.0",
+      "dependencies": {
+        "version": "^0.0.1",
+        "dayjs": "^1.11.13",
+        "html2canvas": "^1.4.1",
+        "jweixin-module": "^1.6.0",
+        "nativeshare": "^2.1.5",
+        "uni-read-pages": "^1.0.5",
+        "uni-simple-router": "^1.5.5",
+        "weixin-js-sdk": "^1.6.5",
+        "yarn": "^1.22.22"
+      },
+      "devDependencies": {
+        "sass-loader": "^13.3.3"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.5",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/set-array": "^1.2.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/set-array": {
+      "version": "1.2.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/source-map": {
+      "version": "0.3.6",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.25",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@types/eslint": {
+      "version": "9.6.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@types/estree": "*",
+        "@types/json-schema": "*"
+      }
+    },
+    "node_modules/@types/eslint-scope": {
+      "version": "3.7.7",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@types/eslint": "*",
+        "@types/estree": "*"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.6",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/@types/json-schema": {
+      "version": "7.0.15",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/@types/node": {
+      "version": "22.10.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "undici-types": "~6.20.0"
+      }
+    },
+    "node_modules/@webassemblyjs/ast": {
+      "version": "1.14.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/helper-numbers": "1.13.2",
+        "@webassemblyjs/helper-wasm-bytecode": "1.13.2"
+      }
+    },
+    "node_modules/@webassemblyjs/floating-point-hex-parser": {
+      "version": "1.13.2",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/@webassemblyjs/helper-api-error": {
+      "version": "1.13.2",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/@webassemblyjs/helper-buffer": {
+      "version": "1.14.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/@webassemblyjs/helper-numbers": {
+      "version": "1.13.2",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/floating-point-hex-parser": "1.13.2",
+        "@webassemblyjs/helper-api-error": "1.13.2",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+      "version": "1.13.2",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/@webassemblyjs/helper-wasm-section": {
+      "version": "1.14.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@webassemblyjs/helper-buffer": "1.14.1",
+        "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+        "@webassemblyjs/wasm-gen": "1.14.1"
+      }
+    },
+    "node_modules/@webassemblyjs/ieee754": {
+      "version": "1.13.2",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@xtuc/ieee754": "^1.2.0"
+      }
+    },
+    "node_modules/@webassemblyjs/leb128": {
+      "version": "1.13.2",
+      "dev": true,
+      "license": "Apache-2.0",
+      "peer": true,
+      "dependencies": {
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "node_modules/@webassemblyjs/utf8": {
+      "version": "1.13.2",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/@webassemblyjs/wasm-edit": {
+      "version": "1.14.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@webassemblyjs/helper-buffer": "1.14.1",
+        "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+        "@webassemblyjs/helper-wasm-section": "1.14.1",
+        "@webassemblyjs/wasm-gen": "1.14.1",
+        "@webassemblyjs/wasm-opt": "1.14.1",
+        "@webassemblyjs/wasm-parser": "1.14.1",
+        "@webassemblyjs/wast-printer": "1.14.1"
+      }
+    },
+    "node_modules/@webassemblyjs/wasm-gen": {
+      "version": "1.14.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+        "@webassemblyjs/ieee754": "1.13.2",
+        "@webassemblyjs/leb128": "1.13.2",
+        "@webassemblyjs/utf8": "1.13.2"
+      }
+    },
+    "node_modules/@webassemblyjs/wasm-opt": {
+      "version": "1.14.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@webassemblyjs/helper-buffer": "1.14.1",
+        "@webassemblyjs/wasm-gen": "1.14.1",
+        "@webassemblyjs/wasm-parser": "1.14.1"
+      }
+    },
+    "node_modules/@webassemblyjs/wasm-parser": {
+      "version": "1.14.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@webassemblyjs/helper-api-error": "1.13.2",
+        "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+        "@webassemblyjs/ieee754": "1.13.2",
+        "@webassemblyjs/leb128": "1.13.2",
+        "@webassemblyjs/utf8": "1.13.2"
+      }
+    },
+    "node_modules/@webassemblyjs/wast-printer": {
+      "version": "1.14.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "node_modules/@xtuc/ieee754": {
+      "version": "1.2.0",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "peer": true
+    },
+    "node_modules/@xtuc/long": {
+      "version": "4.2.2",
+      "dev": true,
+      "license": "Apache-2.0",
+      "peer": true
+    },
+    "node_modules/acorn": {
+      "version": "8.14.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "6.12.6",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ajv-keywords": {
+      "version": "3.5.2",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "peerDependencies": {
+        "ajv": "^6.9.1"
+      }
+    },
+    "node_modules/base64-arraybuffer": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.24.2",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "caniuse-lite": "^1.0.30001669",
+        "electron-to-chromium": "^1.5.41",
+        "node-releases": "^2.0.18",
+        "update-browserslist-db": "^1.1.1"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/buffer-from": {
+      "version": "1.1.2",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001687",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "CC-BY-4.0",
+      "peer": true
+    },
+    "node_modules/chrome-trace-event": {
+      "version": "1.0.4",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=6.0"
+      }
+    },
+    "node_modules/css-line-break": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
+      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+      "license": "MIT",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
+    "node_modules/dayjs": {
+      "version": "1.11.13",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+      "license": "MIT"
+    },
+    "node_modules/decode-uri-component": {
       "version": "0.2.2",
-      "resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
-      "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.5.72",
+      "dev": true,
+      "license": "ISC",
+      "peer": true
+    },
+    "node_modules/enhanced-resolve": {
+      "version": "5.17.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "graceful-fs": "^4.2.4",
+        "tapable": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/es-module-lexer": {
+      "version": "1.5.4",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/eslint-scope": {
+      "version": "5.1.1",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "peer": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^4.1.1"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
     },
-    "filter-obj": {
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "peer": true,
+      "dependencies": {
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/esrecurse/node_modules/estraverse": {
+      "version": "5.3.0",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "peer": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estraverse": {
+      "version": "4.3.0",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "peer": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/events": {
+      "version": "3.3.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=0.8.x"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/filter-obj": {
       "version": "1.1.0",
-      "resolved": "https://registry.npmmirror.com/filter-obj/-/filter-obj-1.1.0.tgz",
-      "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/glob-to-regexp": {
+      "version": "0.4.1",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "peer": true
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "dev": true,
+      "license": "ISC",
+      "peer": true
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=8"
+      }
     },
-    "klona": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz",
-      "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
-      "dev": true
+    "node_modules/html2canvas": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
+      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+      "license": "MIT",
+      "dependencies": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
     },
-    "neo-async": {
+    "node_modules/jest-worker": {
+      "version": "27.5.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@types/node": "*",
+        "merge-stream": "^2.0.0",
+        "supports-color": "^8.0.0"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      }
+    },
+    "node_modules/json-parse-even-better-errors": {
+      "version": "2.3.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/jweixin-module": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/jweixin-module/-/jweixin-module-1.6.0.tgz",
+      "integrity": "sha512-dGk9cf+ipipHmtzYmKZs5B2toX+p4hLyllGLF6xuC8t+B05oYxd8fYoaRz0T30U2n3RUv8a4iwvjhA+OcYz52w==",
+      "license": "ISC"
+    },
+    "node_modules/loader-runner": {
+      "version": "4.3.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=6.11.5"
+      }
+    },
+    "node_modules/merge-stream": {
+      "version": "2.0.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/nativeshare": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmmirror.com/nativeshare/-/nativeshare-2.1.5.tgz",
+      "integrity": "sha512-fuk7rjpWxAGXjbi8ROJDaf/+6N2WYGfAiZDT+f0pRnOsC3+ame+KvWguQmbNGj2826SgU3XJeNR3IAq1Td324A==",
+      "license": "ISC"
+    },
+    "node_modules/neo-async": {
       "version": "2.6.2",
-      "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz",
-      "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
-      "dev": true
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.19",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "dev": true,
+      "license": "ISC",
+      "peer": true
+    },
+    "node_modules/punycode": {
+      "version": "2.3.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=6"
+      }
     },
-    "query-string": {
+    "node_modules/query-string": {
       "version": "6.14.1",
-      "resolved": "https://registry.npmmirror.com/query-string/-/query-string-6.14.1.tgz",
-      "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==",
-      "requires": {
+      "license": "MIT",
+      "dependencies": {
         "decode-uri-component": "^0.2.0",
         "filter-obj": "^1.1.0",
         "split-on-first": "^1.0.0",
         "strict-uri-encode": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/randombytes": {
+      "version": "2.1.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "safe-buffer": "^5.1.0"
       }
     },
-    "sass-loader": {
-      "version": "13.2.0",
-      "resolved": "https://registry.npmmirror.com/sass-loader/-/sass-loader-13.2.0.tgz",
-      "integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==",
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
       "dev": true,
-      "requires": {
-        "klona": "^2.0.4",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/sass-loader": {
+      "version": "13.3.3",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz",
+      "integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
         "neo-async": "^2.6.2"
+      },
+      "engines": {
+        "node": ">= 14.15.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      },
+      "peerDependencies": {
+        "fibers": ">= 3.1.0",
+        "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
+        "sass": "^1.3.0",
+        "sass-embedded": "*",
+        "webpack": "^5.0.0"
+      },
+      "peerDependenciesMeta": {
+        "fibers": {
+          "optional": true
+        },
+        "node-sass": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/schema-utils": {
+      "version": "3.3.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@types/json-schema": "^7.0.8",
+        "ajv": "^6.12.5",
+        "ajv-keywords": "^3.5.2"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      }
+    },
+    "node_modules/serialize-javascript": {
+      "version": "6.0.2",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "peer": true,
+      "dependencies": {
+        "randombytes": "^2.1.0"
+      }
+    },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "peer": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-support": {
+      "version": "0.5.21",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
       }
     },
-    "split-on-first": {
+    "node_modules/split-on-first": {
       "version": "1.1.0",
-      "resolved": "https://registry.npmmirror.com/split-on-first/-/split-on-first-1.1.0.tgz",
-      "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
     },
-    "strict-uri-encode": {
+    "node_modules/strict-uri-encode": {
       "version": "2.0.0",
-      "resolved": "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
-      "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "8.1.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/supports-color?sponsor=1"
+      }
+    },
+    "node_modules/tapable": {
+      "version": "2.2.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/terser": {
+      "version": "5.37.0",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/source-map": "^0.3.3",
+        "acorn": "^8.8.2",
+        "commander": "^2.20.0",
+        "source-map-support": "~0.5.20"
+      },
+      "bin": {
+        "terser": "bin/terser"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/terser-webpack-plugin": {
+      "version": "5.3.10",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/trace-mapping": "^0.3.20",
+        "jest-worker": "^27.4.5",
+        "schema-utils": "^3.1.1",
+        "serialize-javascript": "^6.0.1",
+        "terser": "^5.26.0"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      },
+      "peerDependencies": {
+        "webpack": "^5.1.0"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "esbuild": {
+          "optional": true
+        },
+        "uglify-js": {
+          "optional": true
+        }
+      }
     },
-    "uni-read-pages": {
+    "node_modules/terser/node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/text-segmentation": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
+      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+      "license": "MIT",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.20.0",
+      "dev": true,
+      "license": "MIT",
+      "peer": true
+    },
+    "node_modules/uni-read-pages": {
       "version": "1.0.5",
-      "resolved": "https://registry.npmmirror.com/uni-read-pages/-/uni-read-pages-1.0.5.tgz",
-      "integrity": "sha512-GkrrZ0LX0vn9R5k6RKEi0Ez3Q3e2vUpjXQ8Z6/K/d28KudI9ajqgt8WEjQFlG5EPm1K6uTArN8LlqmZTEixDUA=="
+      "hasInstallScript": true,
+      "license": "ISC"
     },
-    "uni-simple-router": {
+    "node_modules/uni-simple-router": {
       "version": "1.5.5",
-      "resolved": "https://registry.npmmirror.com/uni-simple-router/-/uni-simple-router-1.5.5.tgz",
-      "integrity": "sha512-VjBnwhvmWYHVNsj2zcPjYBwb9TqG7miR87qLBBLI4gHOnJVYmCyjZK/bj06f9slvTMbWXrze7LJ9/Hi/8DB0ag==",
-      "requires": {
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
         "query-string": "^6.12.1"
       }
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.1.1",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.0"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "peer": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/utrie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
+      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+      "license": "MIT",
+      "dependencies": {
+        "base64-arraybuffer": "^1.0.2"
+      }
+    },
+    "node_modules/version": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/version/-/version-0.0.1.tgz",
+      "integrity": "sha512-b/A3k41FxbvgyNHCylsy4jWFiCpFisNBcmCZ7lc7zqa42TXzzb5CakONTQL4Q+HUjP3kKt95zO1gcpovLDV9rA==",
+      "engines": {
+        "node": ">= 0.4.11 < 0.7.0"
+      }
+    },
+    "node_modules/watchpack": {
+      "version": "2.4.2",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "glob-to-regexp": "^0.4.1",
+        "graceful-fs": "^4.1.2"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/webpack": {
+      "version": "5.97.1",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "@types/eslint-scope": "^3.7.7",
+        "@types/estree": "^1.0.6",
+        "@webassemblyjs/ast": "^1.14.1",
+        "@webassemblyjs/wasm-edit": "^1.14.1",
+        "@webassemblyjs/wasm-parser": "^1.14.1",
+        "acorn": "^8.14.0",
+        "browserslist": "^4.24.0",
+        "chrome-trace-event": "^1.0.2",
+        "enhanced-resolve": "^5.17.1",
+        "es-module-lexer": "^1.2.1",
+        "eslint-scope": "5.1.1",
+        "events": "^3.2.0",
+        "glob-to-regexp": "^0.4.1",
+        "graceful-fs": "^4.2.11",
+        "json-parse-even-better-errors": "^2.3.1",
+        "loader-runner": "^4.2.0",
+        "mime-types": "^2.1.27",
+        "neo-async": "^2.6.2",
+        "schema-utils": "^3.2.0",
+        "tapable": "^2.1.1",
+        "terser-webpack-plugin": "^5.3.10",
+        "watchpack": "^2.4.1",
+        "webpack-sources": "^3.2.3"
+      },
+      "bin": {
+        "webpack": "bin/webpack.js"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      },
+      "peerDependenciesMeta": {
+        "webpack-cli": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/webpack-sources": {
+      "version": "3.2.3",
+      "dev": true,
+      "license": "MIT",
+      "peer": true,
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/weixin-js-sdk": {
+      "version": "1.6.5",
+      "resolved": "https://registry.npmmirror.com/weixin-js-sdk/-/weixin-js-sdk-1.6.5.tgz",
+      "integrity": "sha512-Gph1WAWB2YN/lMOFB/ymb+hbU/wYazzJgu6PMMktCy9cSCeW5wA6Zwt0dpahJbJ+RJEwtTv2x9iIu0U4enuVSQ==",
+      "license": "MIT"
+    },
+    "node_modules/yarn": {
+      "version": "1.22.22",
+      "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz",
+      "integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==",
+      "hasInstallScript": true,
+      "license": "BSD-2-Clause",
+      "bin": {
+        "yarn": "bin/yarn.js",
+        "yarnpkg": "bin/yarn.js"
+      },
+      "engines": {
+        "node": ">=4.0.0"
+      }
     }
   }
 }

+ 8 - 2
package.json

@@ -14,11 +14,17 @@
   "license": "",
   "keywords": [],
   "dependencies": {
+    "dayjs": "^1.11.13",
+    "html2canvas": "^1.4.1",
+    "jweixin-module": "^1.6.0",
+    "nativeshare": "^2.1.5",
     "uni-read-pages": "^1.0.5",
     "uni-simple-router": "^1.5.5",
-    "version": "1.11.7"
+    "version": "^0.0.1",
+    "weixin-js-sdk": "^1.6.5",
+    "yarn": "^1.22.22"
   },
   "devDependencies": {
-    "sass-loader": "^13.2.0"
+    "sass-loader": "^13.3.3"
   }
 }

+ 126 - 1
pages.json

@@ -19,7 +19,6 @@
 					"titleNView":{
 						"autoBackButton":false 
 					}
-					
 				}
 				
 			}
@@ -277,6 +276,132 @@
 					"bounce": "none"
 				}
 		    } 
+		},
+		{
+			"path" : "pages/courseManage/vip/ManageDetail",
+			"style" : 
+			{
+				"navigationBarTitleText" : "会员详情",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom",
+				"app-plus": {
+					"bounce": "none"
+				}
+			}
+		},
+		{
+			"path" : "pages/courseManage/course/learning",
+			"style" : 
+			{
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+				"navigationBarTitleText": "课程详情",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom",
+				"app-plus": {
+					"bounce": "none"
+				}
+			}
+		},
+		{
+			"path" : "pages/courseManage/manage/exprotList",
+			"style" : 
+			{
+				"navigationBarTitleText" : "导出列表",
+				"navigationBarBackgroundColor": "#ffffff"
+			}
+		},
+		{
+			"path" : "pages/courseManage/manage/lableSetup",
+			"style" : 
+			{
+				"navigationBarTitleText" : "标签设置",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom",
+				"app-plus": {
+					"bounce": "none"
+				}
+			}
+		},
+		{
+			"path" : "pages/courseManage/manage/userDataDetail",
+			"style" : 
+			{
+				"navigationBarTitleText" : "账户数据明细",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom",
+				"app-plus": {
+					"bounce": "none"
+				}
+			}
+		},
+		{
+			"path" : "pages/courseManage/manage/manageDetail",
+			"style" : 
+			{
+				"navigationBarTitleText" : "群管详情页",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom",
+				"app-plus": {
+					"bounce": "none"
+				}
+			}
+		},
+		{
+			"path" : "pages/courseManage/manage/changeVip",
+			"style" : 
+			{
+				"navigationBarTitleText" : "更换会员归属",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom",
+				"app-plus": {
+					"bounce": "none"
+				}
+			}
+		},
+		{
+			"path" : "pages/courseManage/course/becomeVip",
+			"style" : 
+			{
+				"navigationBarTitleText" : "注册",
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom",
+				"app-plus": {
+					"bounce": "none"
+				}
+			}
+		},
+		{
+			"path" : "pages/courseManage/manage/setup",
+			"style" : 
+			{
+				"navigationBarTitleText" : "经销商设置",
+				"navigationBarTextStyle": "black",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom",
+				"app-plus": {
+					"bounce": "none"
+				}
+			}
+		},
+		{
+			"path" : "pages/courseManage/course/living",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
 		}
 		 
     ],

+ 2 - 2
pages/auth/login.vue

@@ -1,6 +1,6 @@
 <template>
-	<view>
-		 <view class="content">
+	<view class="column" style="height: 100%;">
+		 <view class="content flex-1">
 			 <view class="login">
 				<view class="head">
 					 <image src="/static/logo.png" ></image>

+ 101 - 0
pages/courseManage/components/chart.vue

@@ -0,0 +1,101 @@
+<template>
+	<view class="charts-box">
+		<qiun-data-charts type="funnel" :opts="opts" :chartData="chartData" />
+	</view>
+</template>
+
+<script>
+	import {
+		getcourseRate
+	} from "@/api/courseManage.js"
+	export default {
+		props: {
+			getratelist: {
+				type: Object,
+				default: () => ([{
+					name: '掌声',
+					value: 10
+				}])
+			}
+		},
+		data() {
+			return {
+				chartData: {},
+				//您可以通过修改 config-ucharts.js 文件中下标为 ['funnel'] 的节点来配置全局默认参数,如都是默认参数,此处可以不传 opts 。实际应用过程中 opts 只需传入与全局默认参数中不一致的【某一个属性】即可实现同类型的图表显示不同的样式,达到页面简洁的需求。
+				opts: {
+					color: ["#1890FF", "#91CB74", "#FAC858", "#EE6666", "#73C0DE", "#3CA272", "#FC8452", "#9A60B4",
+						"#ea7ccc"
+					],
+					padding: [15, 15, 0, 15],
+					enableScroll: false,
+					extra: {
+						funnel: {
+							activeOpacity: 0.3,
+							activeWidth: 10,
+							border: true,
+							borderWidth: 2,
+							borderColor: "#FFFFFF",
+							fillOpacity: 1,
+							labelAlign: "right"
+						}
+					},
+					
+				}
+			};
+		},
+		mounted() {
+			this.getServerData();
+		},
+		methods: {
+			getServerData() {
+				//模拟从服务器获取数据时的延时
+				setTimeout(() => {
+					// 模拟服务器返回数据,如果数据格式和标准格式不同,需自行按下面的格式拼接
+					this.chartData = {
+						series: [{
+							name: '流量',
+							data: this.getratelist.data.map(item => {
+								return {
+									name: item.name,
+									value: item.value,
+									centerText: item.value,
+								};
+							})
+							// data: [{
+							// 	"name": "一班",
+							// 	"centerText": "50",
+							// 	"value": 50
+							// }, {
+							// 	"name": "二班",
+							// 	"centerText": "30",
+							// 	"value": 30
+							// }, {
+							// 	"name": "三班",
+							// 	"centerText": "20",
+							// 	"value": 20
+							// }, {
+							// 	"name": "四班",
+							// 	"centerText": "18",
+							// 	"value": 0
+							// }, {
+							// 	"name": "五班",
+							// 	"centerText": "8",
+							// 	"value": 0
+							// }]
+						}]
+					};
+					console.log(this.chartData)
+					// this.chartData = JSON.parse(JSON.stringify(this.getratelist));
+				}, 500);
+			},
+		}
+	};
+</script>
+
+<style scoped>
+	/* 请根据实际需求修改父元素尺寸,组件自动识别宽高 */
+	.charts-box {
+		width: 100%;
+		height: 300px;
+	}
+</style>

+ 315 - 30
pages/courseManage/components/courseItem.vue

@@ -1,12 +1,12 @@
 <template>
 	<view class="courselist-item">
-		<view class="courselist-con x-start">
+		<view class="courselist-con x-start" @click="toCourseDetail(info)">
 			<view class="courselist-img">
 				<!-- <view class="status">进行中</view> -->
 				<image :src="info.thumbnail" mode="aspectFill"></image>
 			</view>
 			<view class="courselist-con-r">
-				<view>
+				<view @click.passive.stop>
 					<text class="more-t">{{info.title}}</text>
 					<view class="btn_icon" style="margin-left: 5px;" @click="copyId">ID
 						<image src="@/static/images/copy_icon.png" mode="aspectFill"></image>
@@ -14,20 +14,25 @@
 				</view>
 				<view class="courselist-desc one-t" v-show="from != 'course'">{{info.courseName}}</view>
 				<view :class="from == 'course' ? 'courselist-con-timebox ':'courselist-con-timebox x-f'">
-					<view class="x-f acea-row"><u-icon class="icon" name="file-text" color="#999" size="20"></u-icon>{{info.createTime}}</view>
-					<view class="x-f acea-row"><u-icon class="icon" name="clock" color="#999" size="16"></u-icon>{{$formatSeconds(info.duration,1)}}</view>
+					<view class="x-f acea-row"><u-icon class="icon" name="file-text" color="#999"
+							size="20"></u-icon>{{info.createTime}}</view>
+					<view class="x-f acea-row"><u-icon class="icon" name="clock" color="#999"
+							size="16"></u-icon>{{$formatSeconds(info.duration,1)}}</view>
 				</view>
 			</view>
 		</view>
 		<view class="courselist-footer x-f">
-			<view class="courselist-footer-item x-c" v-show="activeTab !=2" @click="handleShare"><u-icon name="share-square" color="#1677ff" size="18"></u-icon>分享课程</view>
-			<view class="courselist-footer-item x-c shishi" v-show="activeTab == 1" @click="handleStatistics"><u-icon name="share-square" color="#1677ff" size="18"></u-icon>实时统计</view>
-			<view class="courselist-footer-item x-c shuju" v-show="activeTab == 2" @click="handleStatistics"><u-icon name="share-square" color="#1677ff" size="18"></u-icon>数据统计</view>
+			<view class="courselist-footer-item x-c" v-show="user.userType==1" @click="handleShare"><u-icon
+					name="share-square" color="#1677ff" size="18"></u-icon>分享课程</view>
+			<view class="courselist-footer-item x-c shishi" v-show="activeTab == 1" @click="handleStatistics"><u-icon
+					name="share-square" color="#1677ff" size="18"></u-icon>实时统计</view>
+			<view class="courselist-footer-item x-c shuju" v-show="activeTab == 2" @click="handleStatistics"><u-icon
+					name="share-square" color="#1677ff" size="18"></u-icon>数据统计</view>
 		</view>
 		<!-- 分享弹窗 -->
 		<u-popup :show="showShare" :closeOnClickOverlay="true" :round='20' @close="closeShare" @open="openShare">
 			<view class="sharePop x-ac">
-				<view class="sharePop-item y-f">
+				<view class="sharePop-item y-f" @click="buildimg">
 					<image src="@/static/images/poster_icon.png" mode="aspectFill"></image>
 					<view style="font-weight: bold;margin-bottom: 4px;">生成海报</view>
 					<view style="font-size: 12px;color: #888;">保存海报美观宣传</view>
@@ -40,30 +45,63 @@
 			</view>
 		</u-popup>
 		<!-- 设置链接有效时长弹窗 -->
-		<u-modal :show="setTimeShow" content='content' class="model" @confirm="confirmTime">
+		<u-modal :show="setTimeShow" content='content' class="model" @confirm="confirmTime" :closeOnClickOverlay='true'
+			@close="closetext">
 			<view class="setTimebox">
 				<view class="timetip">不传默认以系统参数为准</view>
 				<view class="x-f">
 					<text style="margin-right: 20px;">链接有效时长(分钟)</text>
-					<u-input
-						fontSize="14px"
-						placeholder="链接有效时长"
-						border="none"
-						v-model="time"
-						maxlength="5"
-					  ></u-input>
+					<u-input fontSize="14px" placeholder="链接有效时长" border="none" v-model="time" maxlength="5"></u-input>
 				</view>
 			</view>
 		</u-modal>
 		<u-notify ref="uNotify" message=""></u-notify>
+		<!-- 生成海报 -->
+		<u-popup :show="setImg" @close="closeimg" :round="12" style="z-index: 999;">
+			<view class="w100 h500">
+				<image :src="codeLink.url" class="codeimg w660" mode="widthFix"></image>
+			</view>
+			<view class="justify-around mtb40">
+				<view class="column justify-center align-center" @click="shareimg">
+					<image src='@/static/image/wechat.png' class="w80 h80"></image>
+					<view class="mt10">微信好友</view>
+				</view>
+				<view class="column justify-center align-center" @click="downimg">
+					<image src='@/static/image/downicon.png' class="w80 h80"></image>
+					<view class="mt10">长按海报保存</view>
+				</view>
+			</view>
+		</u-popup>
+		<u-overlay :show="showzhidao" @click="showzhidao = false" style="z-index: 9999;">
+			<view class="point-box">
+				<view class="imgshe" >
+					<image src='@/static/image/point.png' class="w300 h300"></image>
+				</view>
+				<view class="column colorf fs32 xu-box fs40
+				align-center justify-center">
+					<view class="justify-center">点击右上角
+					<image src="../../../static/image/wxmore.png"
+					class="w50 h50 mlr10"></image>
+					</view>
+					<view class="mt20">选择 “转发给朋友”</view>
+				</view>
+			</view>
+		</u-overlay>
 	</view>
 </template>
 
 <script>
+	import {
+		sharecourselink,
+		buildCode,
+		getSDK
+	} from '@/api/courseManage'
+	import html2canvas from 'html2canvas'
+	import wx from 'weixin-js-sdk'
 	export default {
 		props: {
 			activeTab: {
-				type: [Number,String],
+				type: [Number, String],
 				default: 0
 			},
 			// 来源
@@ -73,36 +111,237 @@
 			},
 			info: {
 				type: Object,
-				default: ()=>{
+				default: () => {
 					return {}
 				}
 			},
 		},
 		data() {
 			return {
+				cavansimg: false,
 				showShare: false,
 				setTimeShow: false,
 				time: "",
+				user: [],
+				type: 1,
+				copylink: '',
+				setImg: '',
+				codeLink: '',
+				setImg: false,
+				painterId: 'myPainter',
+				isLongPress: false,
+				painterSrc: '',
+				showzhidao:false,
 			}
 		},
+		onLoad() {
+		},
+		mounted() {
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {},
+			this.getjssdklist()
+		},
 		methods: {
+			// 获取jssdk
+			getjssdklist() {
+				const param = {
+					url: window.location.href
+				}
+				getSDK(param).then(res => {
+					wx.config({
+						appId: res.data.appId, // 必填,公众号的唯一标识
+						timestamp: res.data.timestamp, // 必填,生成签名的时间戳
+						nonceStr: res.data.nonceStr, // 必填,生成签名的随机串
+						signature: res.data.signature, // 必填,签名
+						jsApiList: ["updateAppMessageShareData"] // 必填,需要使用的JS接口列表
+					});
+				})
+			},
+			
+			shareimg() {
+				  let self = this
+				  //分享好友
+				  // 配置--你到时候把配置全局 --就是这些东西  调接口拿
+				  wx.ready(function() { //需在用户可能点击分享按钮前就先调用
+				  	wx.updateAppMessageShareData({
+				  		title: self.info.courseName+self.info.title, // 分享标题
+				  		desc: self.info.title, // 分享描述
+				  		link:self.copylink, 
+						// 分享链接,该链接域名或路径必须与当前页面对应的公众 号JS安全域名一致
+				  		imgUrl: self.info.thumbnail, // 分享图标
+				  		success: function(res) {
+							console.log(self.info,'456');
+							self.showzhidao=true
+							self.setImg=false
+							self.showShare=false
+				  			// 设置成功
+				  			uni.showToast({
+				  				title: '海报已生成',
+				  				icon: 'none',
+				  				duration: 1000
+				  			}); 
+				  		},
+				  		fail: function(err) {
+				  			console.log(err);
+				  			uni.showToast({
+				  				title: '海报生成失败,请重试',
+				  				icon: 'none',
+				  				duration: 2000
+				  			})
+				  		}
+				  	})
+				  });
+			},
+			downimg() {
+
+			},
+			handleLongPress() {
+				this.isLongPress = true;
+				// 延时执行保存操作,避免误触
+				setTimeout(() => {
+					if (this.isLongPress) {
+						this.saveImage();
+					}
+				}, 1000); // 1000毫秒后执行保存操作
+			},
+			saveImage() {
+				const painter = this.$painter.getPainter(this.painterId);
+				painter.saveImage('jpg', (path) => {
+					uni.saveImageToPhotosAlbum({
+						filePath: path,
+						success: () => {
+							uni.showToast({
+								title: '保存成功'
+							});
+						},
+						fail: () => {
+							uni.showToast({
+								title: '保存失败',
+								icon: 'none'
+							});
+						}
+					});
+				}, 'myCanvas');
+			},
+			closeimg() {
+				this.setImg = false
+				this.showShare = false
+			},
+			closetext() {
+				this.setTimeShow = false
+			},
+			buildimg() {
+				this.buildimgAcode()
+			},
+			//生成海报和二维码
+			buildimgAcode() {
+				uni.showLoading({
+					title: '正在生成中...'
+				})
+				buildCode({
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					courseId: this.info.courseId,
+					time: this.time,
+					type: this.type,
+					videoId: this.info.videoId,
+				}).then(res => {
+					if (res.code == 200) {
+						this.codeLink = res.data
+						this.setImg = true
+						this.getlink()
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			toCourseDetail(info) {
+
+				uni.navigateTo({
+					// url: '/pages/courseManage/course/learning?course='+JSON.stringify(info)
+					url: `/pages/courseManage/course/learning?course=${JSON.stringify(info)}&isvip=1`
+					// url:'/pages/courseManage/course/learning?course=JSON.stringify(info)&param2=2'
+				})
+
+			},
 			handleShare() {
 				this.showShare = true
 			},
 			closeShare() {
 				this.showShare = false
-			  // console.log('open');
+				// console.log('open');
 			},
 			openShare() {
-			  // this.showShare = false
-			  // console.log('close');
+				// this.showShare = false
+				// console.log('close');
 			},
-			copyLink(){
+			copyLink() {
 				this.setTimeShow = true
+
 			},
 			confirmTime() {
-				this.setTimeShow = false
-				this.showShare = false
+				this.setTimeShow = !this.setTimeShow
+				this.showShare = !this.showShare
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					courseId: this.info.courseId,
+					time: this.time,
+					type: this.type,
+					videoId: this.info.videoId,
+				}
+				sharecourselink(params).then(res => {
+					if (res.code == 200) {
+						this.copylink = res.data
+						setTimeout(()=>{
+							uni.setClipboardData({
+								data: this.copylink,
+								success: () => {
+									uni.showToast({
+										title: '链接已复制',
+										icon: 'none',
+										duration: 2000
+									});
+								},
+								fail: () => {
+									uni.showToast({
+										title: '复制失败',
+										icon: 'none'
+									});
+								}
+							});
+						},100)
+						
+						console.log(this.copylink)
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getlink() {
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					courseId: this.info.courseId,
+					time: this.time,
+					type: this.type,
+					videoId: this.info.videoId,
+				}
+				sharecourselink(params).then(res => {
+					if (res.code == 200) {
+						this.copylink = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
 			},
 			handleStatistics() {
 				const info = {
@@ -115,13 +354,13 @@
 					courseName: this.info.courseName,
 				}
 				uni.navigateTo({
-					url: '/pages/courseManage/statistics?info='+JSON.stringify(info)
+					url: '/pages/courseManage/statistics?info=' + JSON.stringify(info)
 				})
 			},
 			copyId() {
 				uni.setClipboardData({
 					data: this.info.fileId,
-					success: ()=> {
+					success: () => {
 						this.$refs.uNotify.show({
 							top: 0,
 							type: 'success',
@@ -130,7 +369,7 @@
 							message: '复制课程ID成功',
 							duration: 1000 * 2,
 							fontSize: 20,
-							safeAreaInsetTop:true
+							safeAreaInsetTop: true
 						})
 					}
 				});
@@ -140,15 +379,40 @@
 </script>
 
 <style scoped lang="scss">
+	.imgshe{
+		display: flex;
+		flex-direction: row-reverse
+	}
+	.point-box{
+		height: 100%;
+		width: 100%;
+		.xu-box{
+			border: #f5f5f5 4rpx dashed;
+			padding: 20rpx 20rpx;
+		}
+	}
+	.codeimg {
+		position: absolute;
+		z-index: 99999;
+		left: 40rpx;
+		top: 40rpx;
+	}
+
+	#codeurl {
+		position: relative;
+	}
+
 	::v-deep {
 		.model .u-fade-enter-active {
 			z-index: 10075 !important;
 		}
 	}
-	.sharePop{
+
+	.sharePop {
 		background-color: #fff;
 		padding: 50px 0;
 		border-radius: 20px 20px 0 0;
+
 		&-item {
 			padding: 0 10px;
 			box-sizing: border-box;
@@ -156,6 +420,7 @@
 			font-weight: 400;
 			font-size: 14px;
 			display: inline-flex !important;
+
 			image {
 				height: 48px;
 				width: 48px;
@@ -163,11 +428,13 @@
 			}
 		}
 	}
+
 	.setTimebox {
 		font-family: PingFang SC, PingFang SC;
 		font-weight: 400;
 		font-size: 14px;
 	}
+
 	.timetip {
 		font-family: PingFang SC, PingFang SC;
 		font-weight: 400;
@@ -176,10 +443,12 @@
 		text-align: center;
 		margin-bottom: 5px;
 	}
-	.courselist{
+
+	.courselist {
 		font-family: PingFang SC, PingFang SC;
 		font-weight: 400;
 		font-size: 14px;
+
 		&-item {
 			width: 100%;
 			border-radius: 14px;
@@ -187,24 +456,29 @@
 			overflow: hidden;
 			margin-bottom: 10px;
 		}
+
 		&-con {
 			padding: 10px 10px 5px 10px;
 			font-size: 12px;
 			color: #777;
 		}
-		&-con-r{
+
+		&-con-r {
 			flex: 1;
 			overflow: hidden;
+
 			.more-t {
 				flex: 1;
 				font-size: 14px;
 				color: #222;
 				display: inline;
 			}
+
 			image {
 				width: 20px;
 				height: 20px;
 			}
+
 			.btn_icon {
 				font-size: 14px;
 				color: #1677ff;
@@ -212,6 +486,7 @@
 				align-items: center;
 			}
 		}
+
 		&-img {
 			width: 110px;
 			height: 70px;
@@ -220,10 +495,12 @@
 			flex-shrink: 0;
 			margin-right: 10px;
 			position: relative;
+
 			image {
 				height: 100%;
 				width: 100%;
 			}
+
 			.status {
 				position: absolute;
 				top: 0;
@@ -239,25 +516,31 @@
 				background-color: #08ce36;
 			}
 		}
+
 		&-desc {
 			flex: 1;
 			margin-top: 7px;
 		}
+
 		&-con-timebox {
 			margin-top: 7px;
 			flex-wrap: wrap;
+
 			.acea-row {
 				margin-right: 12px;
 				margin-bottom: 5px;
 				flex-wrap: nowrap;
 			}
+
 			.icon {
 				margin-right: 5px;
 			}
 		}
+
 		&-footer {
 			padding: 5px;
 			font-size: 14px;
+
 			&-item {
 				flex: 1;
 				text-align: center;
@@ -265,9 +548,11 @@
 				padding: 6px;
 				box-sizing: border-box;
 			}
+
 			.shishi {
 				border-left: 1px solid #f5f5f5;
 			}
+
 			.shuju {
 				border-radius: 5px;
 				border: 1px solid #1677ff;

+ 291 - 241
pages/courseManage/components/dropdownPanel.vue

@@ -1,265 +1,315 @@
 <template>
-    <view class="filter-wrapper" :style="{ height: height + 'px'}" @touchmove.stop.prevent="discard">
-        <view class="inner-wrapper">
-            <view class="mask" :class="showMask ? 'show' : 'hide'" :style="{top: top}" @tap="tapMask"></view>
-            <view class="navs">
-                <view class="c-flex-center" :class="{ 'c-flex-center': index > 0, actNav: index === actNav }" v-for="(item, index) in navData" :key="index" @click="navClick(index)">
+	<view class="filter-wrapper" :style="{ height: height + 'px'}" @touchmove.stop.prevent="discard">
+		<view class="inner-wrapper">
+			<view class="mask" :class="showMask ? 'show' : 'hide'" :style="{top: top}" @tap="tapMask"></view>
+			<view class="navs">
+				<view class="c-flex-center" :class="{ 'c-flex-center': index > 0, actNav: index === actNav }"
+					v-for="(item, index) in navData" :key="index" @click="navClick(item,index)">
 					<view v-if="item.special&&item.name=='批量'" class="x-f" style="align-items: end;">
 						<u-icon name="file-text" :color="index === actNav ?'#2979ff':'#222'" size="20"></u-icon>
 						{{index === actNav ? '取消批量':'批量'}}
 					</view>
 					<template v-else>
-						<text>{{item.name}}</text>
-						<image src="https://i.loli.net/2020/07/15/QsHxlr1gbSImvWt.png" mode="" class="icon-triangle" v-if="index === actNav"></image>
-						<image src="https://i.loli.net/2020/07/15/xjVSvzWcH9NO7al.png" mode="" class="icon-triangle" v-else></image>
+						<text style="width: max-content;">{{item.name}}</text>
+						<image src="https://i.loli.net/2020/07/15/QsHxlr1gbSImvWt.png" mode="" class="icon-triangle"
+							v-if="index === actNav"></image>
+						<image src="https://i.loli.net/2020/07/15/xjVSvzWcH9NO7al.png" mode="" class="icon-triangle"
+							v-else></image>
 					</template>
 				</view>
-            </view>
-            <view scroll-y="true" class="popup" :class="popupShow ? 'popupShow' : ''">
-                <scroll-view scroll-y="true" style="height: 30vh;"><slot></slot></scroll-view>
+			</view>
+			<view scroll-y="true" class="popup" :class="popupShow ? 'popupShow' : ''">
+				<scroll-view scroll-y="true" style="height: 30vh;">
+					<slot></slot>
+				</scroll-view>
 				<view class="footer x-bc">
 					<view class="footer-btn" @click="reset">重置</view>
 					<view class="footer-btn footer-blue" @click="confirm">确认</view>
 				</view>
-            </view>
-        </view>
-    </view>
+			</view>
+		</view>
+	</view>
 </template>
 
 <script>
-// import { getCurDateTime } from '@/libs/utils.js';
-export default {
-    props: {
-        height: {
-            type: Number,
-            default: 40
-        },
-        // top: {
-        //     type: String,
-        //     default: 'calc(var(--window-statsu-bar) + 44px)'
-        // },
-        border: {
-            type: Boolean,
-            default: false
-        },
-        filterData: {
-            //必填
-            type: Array,
-            default: () => {
-                return []
-            }
-        },
-        defaultIndex: {
-            //默认选中条件索引,超出一类时必填
-            type: Array,
-            default: () => {
-                return [0];
-            }
-        }
-    },
-    data() {
-        return {
-			top: 0,
-            navData: [],
-            popupShow: false,
-            showMask: false,
-            actNav: null,
-         
-            selIndex: [] //选中条件索引
-        };
-    },
-    created() {
-        this.navData = this.filterData;
-        this.selIndex = this.defaultIndex;
-        // this.keepStatus();
-    },
-    mounted() {
-		const query = uni.createSelectorQuery().in(this);
-		query
-		  .select(".filter-wrapper")
-		  .boundingClientRect((data) => {
-			console.log(data)
-			this.top = data.top + 'px'
-		  })
-		  .exec();
-    },
-    methods: {
-		reset() {
-			this.$emit('reset');
+	// import { getCurDateTime } from '@/libs/utils.js';
+	export default {
+		props: {
+			height: {
+				type: Number,
+				default: 40
+			},
+			// top: {
+			//     type: String,
+			//     default: 'calc(var(--window-statsu-bar) + 44px)'
+			// },
+			border: {
+				type: Boolean,
+				default: false
+			},
+			filterData: {
+				//必填
+				type: Array,
+				default: () => {
+					return []
+				}
+			},
+			ispopshow: {
+				type: Boolean,
+				default: false
+			},
+			itemname: {
+				type: String,
+				default: ''
+			},
+			defaultIndex: {
+				//默认选中条件索引,超出一类时必填
+				type: Array,
+				default: () => {
+					return [0];
+				}
+			}
 		},
-		confirm() {
-			this.showMask = false;
-			this.popupShow = false;
-			this.$emit('confirm');
+		data() {
+			return {
+				top: 0,
+				navData: [],
+				popupShow: false,
+				showMask: false,
+				actNav: null,
+				selIndex: [] //选中条件索引
+			};
 		},
-        keepStatus() {
-            // this.navData.forEach(itemnavData => {
-            //     itemnavData.map(child => {
-            //         child.select = false;
-            //     });
-            //     return itemnavData;
-            // });
-            // for (let i = 0; i < this.selIndex.length; i++) {
-            //     let selindex = this.selIndex[i];
-            //     this.navData[i][selindex].select = true;
-            // }
-        },
-        navClick(index) {
-            if (index === this.actNav) {
-                this.tapMask();
-                return;
-            }
-            this.popupShow = true;
-            this.showMask = true;
-            this.actNav = index;
-			this.$emit('onChange', this.actNav);
-        },
-        handleOpt(index) {
-            // this.selIndex[this.actNav] = index;
-            // this.keepStatus();
-            // setTimeout(() => {
-            //     this.tapMask();
-            // }, 100);
-            // let data = [];
-            // let res = this.navData.forEach(item => {
-            //     let sel = item.filter(child => child.select);
-            //     data.push(sel);
-            // });
-            // console.log(data);
-            // this.$emit('onSelected', data);
-        },
-        tapMask() {
-            this.showMask = false;
-            this.popupShow = false;
-            this.actNav = null;
-        },
-        discard() {}
-    }
-};
+		watch: {
+			// titname(newVal,oldVal){
+			// 	console.log(newVal,oldVal)
+			// 	if(newVal==2){
+			// 		console.log(newVal,oldVal)
+			// 		this.popupShow=false
+			// 		this.showMask=false
+			// 	}
+			// }
+		},
+		created() {
+			this.navData = this.filterData;
+			this.selIndex = this.defaultIndex;
+			// this.keepStatus();
+		},
+		mounted() {
+			const query = uni.createSelectorQuery().in(this);
+			query
+				.select(".filter-wrapper")
+				.boundingClientRect((data) => {
+					// console.log(data)
+					this.top = data.top + 'px'
+				})
+				.exec();
+		},
+		methods: {
+			reset() {
+				this.$emit('reset');
+				this.showMask = false;
+				this.popupShow = false;
+			},
+			confirm() {
+				this.showMask = false;
+				this.popupShow = false;
+				this.$emit('confirm');
+			},
+			keepStatus() {
+				// this.navData.forEach(itemnavData => {
+				//     itemnavData.map(child => {
+				//         child.select = false;
+				//     });
+				//     return itemnavData;
+				// });
+				// for (let i = 0; i < this.selIndex.length; i++) {
+				//     let selindex = this.selIndex[i];
+				//     this.navData[i][selindex].select = true;
+				// }
+			},
+			navClick(item, index) {
+				if (this.showMask && item.type == 'piliang') {
+					this.popupShow = false
+					this.showMask = false
+					this.actNav = null
+					return
+				}
+
+				this.titname = index
+				this.$emit('onClick', item);
+				if (index === this.actNav) return this.tapMask();
+				this.actNav = index;
+				if (item.stopPrevent) return
+				this.popupShow = true;
+				this.showMask = true;
+				this.$emit('onChange', this.actNav);
+
+			},
+			handleOpt(index) {
+				// this.selIndex[this.actNav] = index;
+				// this.keepStatus();
+				// setTimeout(() => {
+				//     this.tapMask();
+				// }, 100);
+				// let data = [];
+				// let res = this.navData.forEach(item => {
+				//     let sel = item.filter(child => child.select);
+				//     data.push(sel);
+				// });
+				// console.log(data);
+				// this.$emit('onSelected', data);
+			},
+			tapMask() {
+				this.showMask = false;
+				this.popupShow = false;
+				this.actNav = null;
+			},
+			discard() {}
+		}
+	};
 </script>
 
 <style lang="scss" scoped>
-.c-flex-align {
-    display: flex;
-    align-items: center;
-}
-.c-flex-center {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    flex-direction: column;
-}
-.footer {
-	font-family: PingFang SC, PingFang SC;
-	font-weight: 400;
-	font-size: 14px;
-	color: #222;
-	padding: 10px;
-	&-btn {
-		flex: 1;
-		text-align: center;
-		background-color: #fff;
-		border: 1px solid #999;
-		border-radius: 50px;
-		padding: 10px 0;
+	.c-flex-align {
+		display: flex;
+		align-items: center;
 	}
-	&-blue {
-		background-color: #1677ff;
-		border: 1px solid #1677ff;
-		color: #fff;
-		margin-left: 10px;
+
+	.c-flex-center {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
 	}
-}
-.filter-wrapper {
-    // position: fixed;
-    // left: 0;
-    // width: 750rpx;
-    // z-index: 999;
-    .inner-wrapper {
-        // position: relative;
-        .navs {
-            position: relative;
-            font-family: PingFang SC, PingFang SC;
-            font-weight: 400;
-            font-size: 14px;
-            color: #222;
-            padding: 10px;
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            background-color: #fff;
-            z-index: 999;
-            box-sizing: border-box;
-            & > view {
-                flex: 1;
-                height: 100%;
-                flex-direction: row;
-                z-index: 999;
-            }
-            .date {
-                justify-content: flex-end;
-            }
-            .actNav {
-                color: #1677ff;
-                font-weight: bold;
-            }
-        }
-        .mask {
-            z-index: 666;
-            position: fixed;
-            top:0;
-            left: 0;
-            right: 0;
-            bottom: 0;
-            background-color: rgba(0, 0, 0, 0);
-            transition: background-color 0.15s linear;
-            &.show {
-                background-color: rgba(0, 0, 0, 0.4);
-            }
-            &.hide {
-                display: none;
-            }
-        }
-        .popup {
-            position: relative;
-            // max-height: 500rpx;
-            background-color: #fff;
-            border-bottom-left-radius: 20rpx;
-            border-bottom-right-radius: 20rpx;
-            overflow: scroll;
-            z-index: 999;
-            transition: all 1s linear;
-            opacity: 0;
-            display: none;
-            .item-opt {
-                height: 100rpx;
-                padding: 0 40rpx;
-                color: #8b9aae;
-                border-bottom: 2rpx solid #f5f6f9;
-            }
-            .actOpt {
-                color: #1677ff;
-                font-weight: bold;
-                position: relative;
-                &::after {
-                    content: '✓';
-                    font-weight: bold;
-                    font-size: 36rpx;
-                    position: absolute;
-                    right: 40rpx;
-                }
-            }
-        }
-        .popupShow {
-            display: block;
-            opacity: 1;
-        }
-    }
 
-    .icon-triangle {
-        width: 16rpx;
-        height: 16rpx;
-        margin-left: 10rpx;
-    }
-}
+	.footer {
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 400;
+		font-size: 14px;
+		color: #222;
+		padding: 10px;
+
+		&-btn {
+			flex: 1;
+			text-align: center;
+			background-color: #fff;
+			border: 1px solid #999;
+			border-radius: 50px;
+			padding: 10px 0;
+		}
+
+		&-blue {
+			background-color: #1677ff;
+			border: 1px solid #1677ff;
+			color: #fff;
+			margin-left: 10px;
+		}
+	}
+
+	.filter-wrapper {
+
+		// position: fixed;
+		// left: 0;
+		// width: 750rpx;
+		// z-index: 999;
+		.inner-wrapper {
+
+			// position: relative;
+			.navs {
+				position: relative;
+				font-family: PingFang SC, PingFang SC;
+				font-weight: 400;
+				font-size: 14px;
+				color: #222;
+				padding: 10px;
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+				background-color: #fff;
+				z-index: 999;
+				box-sizing: border-box;
+
+				&>view {
+					flex: 1;
+					height: 100%;
+					flex-direction: row;
+					z-index: 999;
+				}
+
+				.date {
+					justify-content: flex-end;
+				}
+
+				.actNav {
+					color: #1677ff;
+					font-weight: bold;
+				}
+			}
+
+			.mask {
+				z-index: 666;
+				position: fixed;
+				top: 0;
+				left: 0;
+				right: 0;
+				bottom: 0;
+				background-color: rgba(0, 0, 0, 0);
+				transition: background-color 0.15s linear;
+
+				&.show {
+					background-color: rgba(0, 0, 0, 0.4);
+				}
+
+				&.hide {
+					display: none;
+				}
+			}
+
+			.popup {
+				position: relative;
+				// max-height: 500rpx;
+				background-color: #fff;
+				border-bottom-left-radius: 20rpx;
+				border-bottom-right-radius: 20rpx;
+				overflow: scroll;
+				z-index: 999;
+				transition: all 1s linear;
+				opacity: 0;
+				display: none;
+
+				.item-opt {
+					height: 100rpx;
+					padding: 0 40rpx;
+					color: #8b9aae;
+					border-bottom: 2rpx solid #f5f6f9;
+				}
+
+				.actOpt {
+					color: #1677ff;
+					font-weight: bold;
+					position: relative;
+
+					&::after {
+						content: '✓';
+						font-weight: bold;
+						font-size: 36rpx;
+						position: absolute;
+						right: 40rpx;
+					}
+				}
+			}
+
+			.popupShow {
+				display: block;
+				opacity: 1;
+			}
+		}
+
+		.icon-triangle {
+			width: 16rpx;
+			height: 16rpx;
+			margin-left: 10rpx;
+		}
+	}
 </style>

+ 250 - 36
pages/courseManage/components/vipUserItem.vue

@@ -1,69 +1,272 @@
 <template>
-	<view class="list-item">
-		<view class="list-item-head x-bc">
-			<view class="x-f" style="flex: 1;overflow: hidden;">
-				<u-avatar :src='item.avatar'></u-avatar>
-				<view class="list-item-head-l">
-					<view style="flex: 1;overflow: hidden;display: flex;">
-						<text class="list-item-name one-t">{{item.nickName}}</text>
-						<image class="list-item-copy" src="@/static/images/copy_icon.png" mode="aspectFill"></image>
+	<view class="pb130 column flex-1 hb">
+		<scroll-view scroll-y="true" class="hb" :refresher-enabled="isEnabled" :refresher-triggered="triggered"
+			refresher-background="rgba(0,0,0,0)" @refresherrefresh="pullDownRefresh"
+			@refresherrestore="triggered = false" :upper-threshold="100" :lower-threshold="100"
+			@refresherabort="triggered = false" @scrolltolower="reachBottom">
+			<view v-for="(item,index) in viplist" :key="index" class="justify-start align-center">
+				<u-checkbox-group @change="changeitem(index)" v-if="isShowSelectAll">
+					<u-checkbox :checked="item.checked" shape="circle" activeColor="#FF6C47" :name="true"
+						labelColor="#FF6C47" />
+				</u-checkbox-group>
+				<view class="list-item w100">
+					<view class="list-item-head x-bc">
+						<view class="x-f" style="flex: 1;overflow: hidden;">
+							<u-avatar :src='item.avatar'></u-avatar>
+							<view class="list-item-head-l">
+								<view style="flex: 1;overflow: hidden;display: flex;">
+									<text class="list-item-name one-t">{{item.nickName}}</text>
+									<image class="list-item-copy" src="@/static/images/copy_icon.png" mode="aspectFill"
+										@click="copyId(item.userId)">
+									</image>
+								</view>
+								<view class="list-item-re">注册时间:{{item.createTime?item.createTime.substring(0,10):'--'}}
+								</view>
+							</view>
+						</view>
+						<image class="phone" src="@/static/manergevip/phone.png" mode="aspectFill"
+							v-if="!isShowSelectAll" @click="tophone(item.phone)"></image>
+					</view>
+					<view class="list-item-desc">
+						<view class="taglist">
+							<!-- <view><u-tag text="停止看课三天" size="mini" color="#1677ff" bgColor="#fff" borderColor="#fff"></u-tag></view>
+						
+						<view v-for="(tag,i) in item.tags ? item.tags.split(',') : []" >
+							<u-tag :text="tag" size="mini" color="#999" bgColor="#fff" borderColor="#fff"></u-tag>
+						</view> -->
+							<!-- <view class="base-color-red">停止看课 3 天</view>
+						<view class="u-border-right u-border-left plr10 base-color-9">
+							参与营期 <text class="bold pl6 base-color-3">1</text></view>
+						<view class="base-color-9">缺课数量 <text class="base-color-red pl8">2</text></view> -->
+						</view>
+						<!-- <view style="margin-top: 5px;">
+						<text class="label" style="color:red;margin-right: 20px;">停止看课{{item.watchCount || 0}}天</text>
+						<text class="label">参与营期</text><text class="value-num">{{item.watchCount || 0}}</text>
+						<text class="label">缺课数量</text><text class="value-num" style="color:red">{{item.watchComlpleteCount || 0}}</text>
+					</view> -->
+						<view class="justify-start">
+							<view class="base-color mr8 pr8 u-border-right" v-if="user.userType==0">
+								{{item.companyUsernickName}}
+							</view>
+							<view class="justify-around">
+								<view v-for="(item,index) in item.tagsName" :key="index" class="mlr4">{{item}}</view>
+							</view>
+						</view>
+					</view>
+					<view class="justify-between" v-if="!isShowSelectAll">
+						<view @click="morepage(item)">更多</view>
+						<view class="justify-start">
+							<view class="btn-box base-color base-bg-sure bor-blue" @click="openModel('label',item)"
+								v-if="item.isBlack==0">改标签</view>
+							<view class="btn-box base-color-red base-bg-false bor-red"
+								@click="openModel('disable',item)" v-if="item.isBlack==0">禁用</view>
+							<view class="btn-box base-color-red base-bg-false bor-red" @click="openModel('change',item)"
+								v-if="item.isBlack==0 && user.userType==0">更换归属</view>
+						</view>
+
 					</view>
-					<view class="list-item-re">注册时间:{{item.createTime?item.createTime.substring(0,10):'--'}}</view>
-				</view>
-			</view>
-			<image class="phone" src="@/static/logo.png" mode="aspectFill"></image>
-		</view>
-		<view class="list-item-desc">
-			<view class="taglist">
-				<view><u-tag text="归属消失殆尽" size="mini" color="#1677ff" bgColor="#fff" borderColor="#fff"></u-tag></view>
-				<view v-for="(tag,i) in item.tags ? item.tags.split(',') : []">
-					<u-tag :text="tag" size="mini" color="#999" bgColor="#f5f5f5" borderColor="#f5f5f5"></u-tag>
 				</view>
 			</view>
-			<!-- <view style="margin-top: 5px;">
-				<text class="label" style="color:red;margin-right: 20px;">停止看课{{item.watchCount || 0}}天</text>
-				<text class="label">参与营期</text><text class="value-num">{{item.watchCount || 0}}</text>
-				<text class="label">缺课数量</text><text class="value-num" style="color:red">{{item.watchComlpleteCount || 0}}</text>
-			</view> -->
-		</view>
-		<view class="list-item-footer x-f">
-			<view class="list-item-footer-btn footer-tagbtn x-f" @click="openModel('tag',item)">改标签</view>
-			<view class="list-item-footer-btn footer-red x-f" @click="openModel('tag',item)">禁用</view>
-			<view class="list-item-footer-btn footer-red x-f" @click="openModel('tag',item)">更换归属</view>
-		</view>
+			<u-loadmore :status="status" />
+		</scroll-view>
+		<u-notify ref="uNotify" message=""></u-notify>
 	</view>
 </template>
 
 <script>
 	export default {
-		
+		props: {
+			viplist: {
+				type: Array,
+				default: () => []
+			},
+			isShowSelectAll: {
+				type: Boolean,
+				default: false, //是否显示全选  由外部控制
+			},
+			pageOptions: {
+				type: Object,
+				default: () => ({
+					pageNum: 1,
+					pageSize: 10,
+				})
+			},
+			status: {
+				type: String,
+				default: 'loadmore'
+			}
+
+		},
 		data() {
 			return {
-				item: {
-					tags: '0000,小红,标签'
-				}
+				checkList: [],
+				cccc: [false],
+				user: [],
+				isEnabled: true,
+				triggered: false,
 			}
 		},
+		mounted() {
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+		},
 		methods: {
-			openModel() {
-				
+			tophone(phone){
+				console.log(phone)
+				if(phone==null){
+					uni.showToast({
+						title: '用户暂时没有录入电话',
+						icon: 'none',
+						duration: 2000
+					});
+				}else{
+					uni.setClipboardData({
+						data: String(phone),
+						success: () => {
+							// 拨号跳转
+							  uni.makePhoneCall({
+								phoneNumber: phone, // 电话号码
+								success: () => {
+								  console.log('拨号成功');
+								},
+								fail: () => {
+								  console.log('拨号失败');
+								}
+							  });
+						},
+					})
+				}
+			},
+			copyId(id) {
+				uni.setClipboardData({
+					data: String(id),
+					success: () => {
+						uni.showToast({
+							title: '复制成功',
+							icon: 'none',
+							duration: 2000
+						});
+					},
+				})
+			},
+			pullDownRefresh() {
+				// 下拉
+				this.triggered = true; //下拉了状态为true
+				setTimeout(() => {
+					this.triggered = false;
+					uni.stopPullDownRefresh()
+					this.$emit('changePageOptions', {
+						...this.pageOptions,
+						pageNum: 1
+					})
+					// listParmas.page = 1;//页码为1
+					// reSetList()
+					// 请求接口里面需要判断是不是最后一页   是最后一页 status赋值为‘loadmore’没有更多了
+					// 请求接口
+					this.$emit('pullDownRefresh')
+				}, 1000)
+			},
+			reachBottom() {
+				// status这个是加载状态
+				if (this.status === 'loadmore') {
+					this.$emit('changeStatus', 'loading')
+					uni.showNavigationBarLoading()
+					setTimeout(() => {
+						//触底页码+1 
+						// listParmas.page += 1;
+						this.$emit('changePageOptions', {
+							...this.pageOptions,
+							pageNum: this.pageOptions.pageNum + 1
+						})
+						//请求接口
+						this.$emit('reachBottom')
+						uni.hideNavigationBarLoading()
+					}, 1000);
+				}
+			},
+			morepage(item) {
+				uni.navigateTo({
+					url: '/pages/courseManage/vip/ManageDetail'
+				})
+				uni.setStorageSync('detailUser', item)
+			},
+			openModel(tag, item) {
+				if (tag == 'label') {
+					this.$emit('getlableId', item.userId)
+				} else if (tag == 'disable') {
+					this.$emit('getuserId', item.userId)
+					console.log(item.userId)
+				} else {
+					this.$emit('change', item.userId)
+				}
+			},
+			// 单选
+			changeitem(index) {
+				this.$emit('changeItem', index) //传参
 			}
+			// api请求参考
+			/* v3写法  你改一下
+			type=refresh表示刷新
+			 	const getListData = async (type = 'refresh') => {
+			 		loadStatus.value = 'loading'//加载状态变成正在加载中  ‘loading’
+					// 开始请求接口
+			 		const result = await getDataFn({
+			 			...parmas,
+			 			...listParmas.value,
+			 		})
+			 		if (result) {
+			 			returnData.value = result.data
+			 			const {
+			 				last_page,//最后一页是第几页
+			 				data,
+			 			} = result.data
+						// 如果下拉  数据为第一页的
+			 			if (type == 'refresh') {
+			 				listData.value = data
+			 			} else {
+							// 否则是触底  数据等于当前页然后追加下一页的
+			 				listData.value = [...listData.value, ...data]
+			 			}
+						// 如果当前页》=最后一页  nomore  没有更多了
+			 			if (listParmas.value.page >= last_page) {
+			 				loadStatus.value = 'nomore';
+			 			} else {//不然就是还有下一页   loadmore加载更多
+			 				loadStatus.value = 'loadmore';
+			 			}
+			 		}
+			 	}
+			 
+			 
+			 */
+
 		}
 	}
 </script>
 
 <style lang="scss" scoped>
-	.list-item{
+	.btn-box {
+		height: 26px;
+		padding: 0 10px;
+		margin-left: 10px;
+		border-radius: 25px;
+		font-weight: 400;
+		font-size: 10px;
+		line-height: 26px;
+		box-sizing: border-box;
+	}
+
+	.list-item {
 		padding: 10px;
 		margin-bottom: 10px;
 		background-color: #fff;
 		border-radius: 12px;
+
 		&-head {
 			.phone {
 				flex-shrink: 0;
 				width: 30px;
 				height: 30px;
 			}
+
 			&-l {
 				flex: 1;
 				overflow: hidden;
@@ -71,50 +274,61 @@
 				margin-right: 10px;
 			}
 		}
+
 		&-copy {
 			width: 20px;
 			height: 20px;
 		}
+
 		&-re {
 			font-size: 10px;
 			margin-top: 5px;
 		}
+
 		&-desc {
 			padding: 5px;
 			color: #999;
 			font-size: 12px;
 		}
+
 		.label {
 			margin-right: 4px;
 		}
+
 		.value-num {
 			margin-right: 18px;
 			color: #222;
 			font-weight: bold;
 		}
+
 		.taglist {
 			display: flex;
 			align-items: center;
 			flex-wrap: wrap;
 			color: #555;
 			padding-top: 5px;
+
 			view {
 				margin: 0 5px 5px 0;
 			}
 		}
+
 		&-footer {
 			justify-content: flex-end;
 			padding: 6px 0;
+
 			.footer-tagbtn {
 				color: #1677ff;
 				background-color: #e7f2fe;
 				border: 1px solid #c9e1fb;
 			}
+
 			.footer-red {
 				color: #f93e3e;
 				background-color: #fae7e7;
 				border: 1px solid #f7a1a1;
 			}
+
 			&-btn {
 				height: 26px;
 				padding: 0 10px;

+ 228 - 0
pages/courseManage/course/becomeVip.vue

@@ -0,0 +1,228 @@
+<template>
+	<view class="centerV hb base-bg-f">
+		<view class="w100 centerV" v-if="isH5vip!=true">
+			<image src="@/static/image/becomevip.png" class="h193 w193 mb40"></image>
+			<view class="bold">是否申请成为会员</view>
+			<view class="justify-center w100 mt40 mb40">
+				<view class="quxiao">取消</view>
+				<view class="sure" @click="becomeVipfun">确定</view>
+			</view>
+		</view>
+		<u-popup :show="showvip" @close="close" @open="open"
+		 mode='center' round='20' style="flex: 0;" >
+			<view class="VIPvie w600 h600 column justify-center align-center">
+				<image src="../../../static/manergevip/becomeTrue.png" class="h400 w400"></image>
+				
+				<view class="bold fs50 center mt80">{{tips}}!</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	import {
+		becomeVip
+	} from "@/api/courseManage.js"
+	import {
+		loginByMp
+	} from '@/api/user'
+	const isWechat = () => {
+		return String(navigator.userAgent.toLowerCase().match(/MicroMessenger/i)) === "micromessenger";
+	}
+	export default {
+		data() {
+			return {
+				userId: '',
+				companyId: '',
+				companyUserId: '',
+				code: '',
+				showvip: false,
+				user:'',
+				tips:'',
+				isH5vip:false,
+				becomeuser:{}
+			}
+		},
+		onLoad(option) {
+			if (option && option.user) {
+				// console.log('分享链接进入',option)
+				this.becomeuser = JSON.parse(option.user)
+				this.companyId = this.becomeuser.companyId
+				this.companyUserId = this.becomeuser.companyUserId
+				// console.log('分享链接进入',this.becomeuser)
+				// if(uni.getStorageSync("isH5vip")==true){
+				// 	this.becomeVipfun()
+				// }else{
+				// 	setTimeout(() => {
+				// 		this.getWechatCode()
+				// 		this.userId=this.user.userId
+				// 		console.log(this.userId)
+				// 	}, 200)
+				// }
+				
+			} else {
+				this.companyId = option.companyId
+				this.userId = option.userId
+				this.companyUserId = option.companyUserId
+				this.code = option.code
+			}
+		},
+		onShow() {
+			this.isH5vip = uni.getStorageSync("isH5vip")
+			console.log(this.isH5vip)
+			if (this.becomeuser&&JSON.stringify(this.becomeuser)!='{}'){
+				if(uni.getStorageSync("isH5vip")==true){
+					this.becomeVipfun()
+				}else{
+					setTimeout(() => {
+						this.getWechatCode()
+						this.userId=this.user.userId
+						console.log(this.userId)
+					}, 200)
+				}
+			}
+		},
+		methods: {
+			close() {},
+			open() {},
+			becomeVipfun() {
+				uni.showLoading({
+					title: '正在加载中...'
+				})
+				if (this.becomeuser!=''){
+					this.user = uni.getStorageSync("userInfo") ? JSON.parse(uni.getStorageSync("userInfo")) : {}
+					this.userId=this.user.userId
+				} 
+				const param = {
+					companyId: this.companyId,
+					userId: this.userId,
+					companyUserId: this.companyUserId,
+				}
+				console.log(param)
+				becomeVip(param).then(res => {
+					if (res.code == 200) {
+						console.log(res)
+						// if(this.becomeuser==''){
+						// 	setTimeout(()=>{
+								this.showvip = true
+								this.tips=res.msg
+						// 	},1000)
+						// }
+					} else {
+						uni.showToast({
+							title: res.msg,
+							icon: 'none'
+						});
+					}
+				})
+			},
+			// 获取code
+			getWechatCode() {
+				if (isWechat) {
+					let appid = "wx93ce67750e3cfba3"; //微信APPid
+					let code = this.getUrlCode().code; //是否存在code
+					let local = window.location.href;
+					if (code == null || code === "") {
+						let urlPaths = local.split("/courseh5");
+						uni.setStorageSync('beforLoginPage', urlPaths[1]);
+						window.location.href =
+							"https://open.weixin.qq.com/connect/oauth2/authorize?appid=" +
+							appid +
+							"&redirect_uri=" +
+							encodeURIComponent(local) +
+							"&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
+					} else {
+						this.code = code;
+						this.loginByMp()
+					}
+				}
+			},
+			getUrlCode() {
+				// 截取url中的code方法
+				var url = location.search;
+				var theRequest = new Object();
+				if (url.indexOf("?") != -1) {
+					var str = url.substr(1);
+					var strs = str.split("&");
+					for (var i = 0; i < strs.length; i++) {
+						theRequest[strs[i].split("=")[0]] = strs[i].split("=")[1];
+					}
+				}
+				return theRequest;
+			},
+			loginByMp() {
+				if (this.code == null) {
+					return;
+				}
+				uni.showLoading({
+					title: "处理中..."
+				});
+				// let that = this;
+				var data = {
+					code: this.code,
+					companyUserId:this.companyUserId
+				}
+				loginByMp(data).then(res => {
+						uni.hideLoading();
+						if (res.code == 200) {
+							// 登录后存token和用户信息
+							uni.setStorageSync('UserAppToken', res.token);
+							this.isH5vip = uni.setStorageSync("isH5vip",res.isH5Vip);
+							uni.setStorageSync('userInfo', JSON.stringify(res.user));
+							let beforLoginUrl = uni.getStorageSync('beforLoginPage');
+							// console.log("beforLoginUrl:"+beforLoginUrl);
+							console.log(`登录成功后跳转${beforLoginUrl}`);
+							uni.reLaunch({
+								url: beforLoginUrl
+							});
+						} else {
+							uni.showToast({
+								title: res.msg,
+								icon: 'none'
+							});
+						}
+					},
+					err => {}
+				);
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.VIPvie {
+		width: 580rpx;
+		background: linear-gradient(to bottom, #c3dbfe 2%, #f6fbfe 50%);
+		border-radius: 20rpx;
+		height: 600rpx;
+		position: relative;
+
+		image {
+			position: absolute;
+			top: -120rpx;
+			left: 16%;
+		}
+	}
+
+	.quxiao {
+		width: 30%;
+		height: 72rpx;
+		line-height: 72rpx;
+		text-align: center;
+		border: 2rpx solid #ccc;
+		border-radius: 8rpx;
+		color: #666;
+		margin-right: 10rpx;
+	}
+
+	.sure {
+		width: 30%;
+		background-color: #1777ff;
+		height: 72rpx;
+		line-height: 72rpx;
+		text-align: center;
+		border-radius: 8rpx;
+		color: #fff;
+		margin-left: 10rpx;
+	}
+</style>

+ 117 - 73
pages/courseManage/course/index.vue

@@ -3,29 +3,35 @@
 		<view class="training-camp">
 			<view class="training-camp-btn" @click="choose">{{title}}</view>
 		</view>
-		<u-picker ref="upicker" :show="show" :columns="columns" title="训练营选择" keyName="courseName" @confirm="confirm" @cancel="cancel"></u-picker>
-		<view class="container-body x-start" :style="{height: contentH}">
-			<!-- <view class="container-left">
-				<view class="classification active">全部</view>
-				<view class="classification">测试</view>
-			</view> -->
+		<u-picker ref="upicker" :show="show" :columns="columns" title="训练营选择" keyName="courseName" @confirm="confirm"
+			@cancel="cancel"></u-picker>
+		<view class="container-body x-start" :style="{height: contentH,width:'100%'}">
 			<view class="container-right">
-				<mescroll-body top="0"  bottom="0" ref="mescrollRef" @init="mescrollInit" :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
+				<scroll-view style="height:100%" :scroll-y="true" :refresher-enabled="true"
+					:refresher-triggered="triggered" refresher-background="rgba(0,0,0,0)"
+					@refresherrefresh="pullDownRefresh" @refresherrestore="triggered = false" :upper-threshold="100"
+					:lower-threshold="100" @refresherabort="triggered = false" @scrolltolower="reachBottom">
 					<view class="list">
-						<courseItem :from="'course'" :activeTab="0" v-for="(item,index) in dataList" :key="index" :info="item" />
+						<courseItem :from="'course'" :activeTab="0" v-for="(item,index) in dataList" :key="index"
+							:info="item" />
+						<u-loadmore :status="loadStatus" />
 					</view>
-				</mescroll-body>
+				</scroll-view>
 			</view>
 		</view>
 	</view>
 </template>
 
 <script>
-	import { getFsCourseList,getCourseVdieoList } from "@/api/courseManage.js"
+	import {
+		getFsCourseList,
+		getCourseVdieoList
+	} from "@/api/courseManage.js"
 	import courseItem from "../components/courseItem.vue"
 	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";
 	export default {
-		mixins: [MescrollMixin], 
+		mixins: [MescrollMixin, MescrollMoreItemMixin],
 		components: {
 			courseItem
 		},
@@ -34,32 +40,36 @@
 				title: '请选择训练营',
 				contentH: 0,
 				show: false,
-				columns: [[]],
-				mescroll:null,
+				columns: [
+					[]
+				],
 				downOption: {
-					use:true,
-					auto: false
+					auto: false // 不自动加载 (mixin已处理第一个tab触发downCallback)
 				},
 				upOption: {
-					onScroll:false,
-					use: true, // 是否启用上拉加载; 默认true
-					auto: false,
+					auto: false, // 不自动加载
 					page: {
-						pae: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
-						size: 10 // 每页数据的数量,默认10
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量
 					},
-					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
-					textNoMore:"已经到底了",
-					empty: {
-						icon:'https://cos.his.cdwjyyh.com/fs/20240423/cf4a86b913a04341bb44e34bb4d37aa2.png',
-						tip: '暂无数据'
+					noMoreSize: 4, //如果列表已无数据,可设置列表的总数量要大于半页才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看; 默认5
+					toTop: {
+						width: 0,
 					}
 				},
+				keyword: "",
 				dataList: [],
 				user: {},
 				courseList: [],
 				chooseIndex: [0],
-				courseId: ''
+				courseId: '',
+				mescroll: null,
+				params: {
+					pageNum: 1,
+					pageSize: 10
+				},
+				triggered: false,
+				loadStatus: 'loadmore',
 			}
 		},
 		mounted() {
@@ -82,7 +92,8 @@
 				this.courseId = e.value[0].courseId
 				this.title = e.value[0].courseName
 				this.show = false
-				this.mescroll.resetUpScroll()
+				// this.mescroll.resetUpScroll()
+				this.getListInit()
 			},
 			// 训练营
 			getFsCourseList() {
@@ -90,65 +101,91 @@
 				const param = {
 					companyId: this.user.companyId,
 					companyUserId: this.user.userId,
-					type: this.user.userType=='00' ? 0 : 1, // 0:经销商/1:群管
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
 				}
-				getFsCourseList(param).then(res=>{
-					if(res.code==200){
+				getFsCourseList(param).then(res => {
+					if (res.code == 200) {
 						this.courseList = res.data || []
 						this.columns = [this.courseList]
-						this.courseId = this.courseList && this.courseList.length > 0 ? this.courseList[0].courseId : ''
-						this.title = this.courseList && this.courseList.length > 0 ? this.courseList[0].courseName : '请选择训练营'
-						this.mescroll.resetUpScroll()
-					}else{
+						this.courseId = this.courseList && this.courseList.length > 0 ? this.courseList[0]
+							.courseId : ''
+						this.title = this.courseList && this.courseList.length > 0 ? this.courseList[0]
+							.courseName : '请选择训练营'
+						this.getListInit()
+						// this.mescroll.resetUpScroll()
+					} else {
 						uni.showToast({
-							icon:'none',
+							icon: 'none',
 							title: res.msg,
 						});
 					}
 				})
 			},
-			mescrollInit(mescroll) {
-				this.mescroll = mescroll;
-			},
-			/*下拉刷新的回调 */
-			downCallback(mescroll) {
-				mescroll.resetUpScroll()
+			getListInit() {
+				this.params.pageNum = 1
+				this.getListData('refresh')
 			},
-			upCallback(page) {
-				//联网加载数据
-				var that = this;
-				var data={
-					courseId:this.courseId,
-					status: '',
-					pageNum: page.num,
-					pageSize: page.size
-				};
+			async getListData(type = 'refresh') {
 				uni.showLoading({
-					title:"加载中..."
+					title: "加载中..."
 				})
-				getCourseVdieoList(data).then(res => {
-					uni.hideLoading()
-					if(res.code==200){
-						//设置列表数据
-						if (page.num == 1) {
-							that.dataList = res.data.list; 
-							
-						} else {
-							that.dataList = that.dataList.concat(res.data.list);
-							 
-						}
-						that.mescroll.endBySize(res.data.list.length, res.data.total);
-						
-					}else{
-						uni.showToast({
-							icon:'none',
-							title: "请求失败",
-						});
-						that.dataList = null;
-						that.mescroll.endErr();
+				this.loadStatus = 'loading'
+				const result = await getCourseVdieoList({
+					courseId: this.courseId,
+					status: '',
+					...this.params
+				})
+				if (result) {
+					const {
+						isLastPage,
+						total,
+						list,
+					} = result.data
+					if (type == 'refresh') {
+						this.dataList = list
+					} else {
+						this.dataList = [...this.dataList, ...list]
+					}
+					if (isLastPage) {
+						this.loadStatus = 'nomore';
+					} else {
+						this.loadStatus = 'loadmore';
 					}
-				});
+					uni.hideLoading()
+				} else {
+					uni.showToast({
+						icon: 'none',
+						title: "请求失败",
+					});
+					this.dataList = []
+				}
 			},
+			/**
+			 * 触底添加下一页
+			 */
+			reachBottom(options) {
+				if (this.loadStatus === 'loadmore') {
+					this.loadStatus = 'loading'
+					uni.showNavigationBarLoading()
+					setTimeout(() => {
+						this.params.pageNum += 1;
+						this.getListData('more')
+						uni.hideNavigationBarLoading()
+					}, 500);
+				}
+			},
+			/**
+			 * 下拉列表页
+			 */
+			pullDownRefresh(options) {
+				this.triggered = true;
+				setTimeout(() => {
+					this.triggered = false;
+					uni.stopPullDownRefresh()
+					this.params.pageNum = 1;
+					this.getListData('refresh')
+				}, 500)
+			}
 		}
 	}
 </script>
@@ -165,6 +202,7 @@
 		font-weight: 400;
 		font-size: 14px;
 		color: #222222;
+
 		&-btn {
 			font-size: 15px;
 			width: 100%;
@@ -176,6 +214,7 @@
 			border: 1px solid #ccc;
 		}
 	}
+
 	.container-left {
 		flex-shrink: 0;
 		background-color: #fff;
@@ -186,10 +225,12 @@
 		font-weight: 400;
 		font-size: 14px;
 		color: #222222;
+
 		.active {
 			background-color: #f5f4f5;
 			font-weight: bold;
 			position: relative;
+
 			&::after {
 				content: "";
 				height: 15px;
@@ -201,15 +242,18 @@
 				transform: translateY(-50%);
 			}
 		}
+
 		.classification {
 			padding: 20px 16px;
 			box-sizing: border-box;
 		}
 	}
+
 	.container-right {
 		flex: 1;
 		height: 100%;
-		overflow-y: auto;
+		overflow-y: scroll;
+
 		.list {
 			padding: 10px;
 			box-sizing: border-box;

+ 1814 - 0
pages/courseManage/course/learning.vue

@@ -0,0 +1,1814 @@
+<template>
+	<view class="content" v-show="!loading">
+		<view class="video-box">
+			<!-- <image v-if="!isLogin || isAddKf!=1" class="video-poster" :src="courseInfo.imgUrl" mode="aspectFill"></image> -->
+			<video :style="{'--progress': progress + '%'}" id="myVideo" :src="videoUrl" :show-center-play-btn="false"
+				:show-progress="true" :auto-pause-if-navigate="true" :auto-pause-if-open-native="true"
+				@timeupdate="videoTimeupdate" @error="videoErrorCallback" @play="getPlay" @pause="getPause"
+				@ended="getEnded" @fullscreenchange="fullscreenchange" @progress="progressChange"
+				:controls="!showPlay"></video>
+			<cover-view class="video-controls-box" @click="clickVideo" v-if="showPlay">
+				<cover-image class="video-play" src="@/static/image/video_icon.png" @click="play()"></cover-image>
+			</cover-view>
+		</view>
+
+		<view class="title-content" id="title-content">
+			<!-- 答题时展示小节课程名,其他展示课程名 -->
+			<!-- 课程名字 -->
+			<view class="miantitlebox">
+				{{courseInfo.title}} {{courseInfo.courseName}}
+			</view>
+		</view>
+		<scroll-view class="scroll-view" :style="{height: height}" :scroll-top="scrollTop" scroll-y="true">
+			<!-- 介绍 -->
+			<view class="descbox">
+				<template>
+					<view class="descbox-title">{{courseInfo.title}}</view>
+					<view class="descbox-info">
+						<view class="descbox-info-l">
+							<view>{{courseInfo.views}}次播放</view>
+							<view class="descbox-info-time">总时长:{{courseInfo.duration}}
+							</view>
+						</view>
+					</view>
+				</template>
+				<view class="descbox-desc" id="descbox-desc" :style="{height: isExpand ? 'auto': '42rpx'}">
+					<text>{{courseInfo.description}}</text>
+					<view :class="isExpand ? 'expand': 'expand expand-ab'" v-if="textHeight > 21">
+						<text @click="handleExpand">{{isExpand ? '收起简介' : '展开简介'}}</text>
+						<image src="@/static/image/course_arrow_up_icon.png" v-show="isExpand"></image>
+						<image src="@/static/image/course_arrow_down_icon.png" v-show="!isExpand"></image>
+					</view>
+				</view>
+			</view>
+			<!-- 问题 -->
+			<view class="ques-content">
+				<view class="ques-content-tit">问答题</view>
+				<view v-for="(item,index) in quesList" :key="index">
+					<view class="ques-title">
+						<text>{{index + 1}}.</text>
+						<view class="ques-type" v-show="item.type == 1 || item.type == 2">
+							{{item.type == 1 ? '单选' : item.type == 2 ? '多选' : ''}}
+						</view>
+						<text>{{item.title}}</text>
+					</view>
+					<view
+						:class="item.answer.indexOf(option.name) !== -1 ?'ques-option ques-option-active':'ques-option'"
+						v-for="(option,idx) in item.questionOption" :key="idx" @click="handleAnswer(item,option)">
+						<view>
+							{{numberToLetter(idx)}}.
+						</view>
+						<view>{{option.name}}</view>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+		<!-- 线路 -->
+		<!-- <view class="video-line" @click="openPop" v-if="isLogin&&isAddKf==1">
+			<image src="@/static/image/changePlayer-icon.png"></image>
+			<text>线路{{lineIndex + 1 | numberToChinese}}</text>
+		</view> -->
+		<!-- 线路弹窗 -->
+		<uni-popup ref="popup" type="bottom">
+			<view class="popupbox">
+				<view class="popupbox-head">
+					<text>线路选择</text>
+					<image class="close-icon" src="@/static/image/tc_close_icon.png" mode="aspectFill" @click="close">
+					</image>
+				</view>
+				<view class="popupbox-content">
+					<view :class="lineIndex == index ? 'line-item line-active': 'line-item'"
+						v-for="(it,index) in lineList" :key="index" @click="handleLine(index)">
+						线路{{index + 1 | numberToChinese}}</view>
+				</view>
+			</view>
+		</uni-popup>
+		<!-- 温馨提示弹窗 -->
+		<uni-popup ref="tipsPopup" type="center" :is-mask-click="false">
+			<view class="tipsPopup-mask">
+				<image class="red_envelope_top" src="@/static/image/red_envelope_img.png" mode="aspectFill"></image>
+				<view class="tipsPopup">
+					<image class="tipsPopup-close" src="@/static/image/course_close_white_icon.png" mode="aspectFill"
+						@click="closeTipsPop"></image>
+					<view class="tipsPopup-line">
+						<view class="tipsPopup-box">
+							<view class="tipsPopup-head">
+								<image class="tipsPopup-head-title" src="@/static/image/tips_title_img.png"
+									mode="widthFix"></image>
+							</view>
+							<view class="tipsPopup-content">
+								<view class="tipsPopup-content-title">亲爱的用户,</view>
+								<view>您已经观看课程一半的时间了,请注意休息并保持专注。</view>
+							</view>
+							<view class="tipsPopup-btn-box">
+								<view class="tipsPopup-btn" @click="closeTipsPop">继续观看领红包</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</uni-popup>
+		<!-- 答题弹窗 -->
+		<uni-popup ref="answerPopup" type="center">
+			<view :class="errTitle == '恭喜你,回答正确' ? 'answerPopup-box bg':'answerPopup-box'">
+				<!-- 正确 -->
+				<image class="tipimg" v-if="errTitle == '恭喜你,回答正确'" src="@/static/image/course_answer_img.png"
+					mode="aspectFill"></image>
+				<!-- 错误 -->
+				<image class="tipimg" v-else src="@/static/image/course_answer_incorrectly_img.png" mode="aspectFill">
+				</image>
+				<view class="answerPopup-title">{{errTitle}}</view>
+				<view class="answerPopup-desc" v-html="errDesc"></view>
+				<!-- 选择奖励 -->
+				<view class="reward-list">
+					<radio-group class="reward-list-group" @change="rewardChange">
+						<label class="reward-list-option" v-for="(item, index) in rewardType" :key="item.value">
+							<radio :value="item.value+ ''" :checked="item.value == currentReward"
+								activeBorderColor="#FF5C03" activeBackgroundColor="#FF5C03"
+								style="transform:scale(0.7)" />
+							<view :style="{color: item.value == currentReward ? '#FF5C03':''}">{{item.name}}</view>
+						</label>
+					</radio-group>
+				</view>
+				<!-- 错误题目 -->
+				<view class="errQuesbox">
+					<view class="errQuesbox-item textOne" v-for="(it,index) in errQues" :key="index">{{it.title}}</view>
+				</view>
+				<view class="answerPopup-btn" v-if="errTitle == '恭喜你,回答正确'" @click="closeAnswerPopup">确认</view>
+				<view class="tipsPopup-btn-box" v-else
+					:style="{marginTop: errQues&&errQues.length>0 ? '40rpx':'54rpx'}">
+					<view class="tipsPopup-btn" @click="closeAnswerPopup">{{remain > 0 ? '重新答题': '确认'}}</view>
+				</view>
+			</view>
+		</uni-popup>
+		<!-- 客服二维码弹窗 -->
+		<uni-popup ref="kfPopup" type="center" :mask-click="false">
+			<view class="kfqrcode-box">
+				<image class="kfqrcode" :src="qrcode"></image>
+				<view>长按识别二维码添加客服,立即看课</view>
+				<image class="kfqrcode-close" src="@/static/image/course_close_white_icon.png" mode="aspectFill"
+					@click="closeKFPop"></image>
+			</view>
+		</uni-popup>
+		<!-- 判断是否限制权限-->
+		<u-popup :show="showvip" @close="close" @open="open" mode='center' round='20'>
+			<view class="VIPvie">
+				<image src="@/static/image/safe.png" class="w400 h400"></image>
+				<view class="mt90 pt90">
+					<view class="fs50 center mb20">{{answermsg}}</view>
+					<view class="fs32 base-color-6 center">联系群管解除限制!</view>
+				</view>
+			</view>
+		</u-popup>
+		<!-- 判断是否是会员,不是会员申请成为会员-->
+		<u-popup :show="showbecome" @close="close" @open="open" mode='center' round='20' overlayStyle="height:100%">
+			<view class="centerV h800 w600 base-bg-f radius20">
+				<image src="@/static/image/becomevip.png" class="h193 w193 mb40"></image>
+				<view class="bold">是否申请成为会员</view>
+				<view class="justify-center w100 mt40 mb40">
+					<view class="quxiao" @click="">取消</view>
+					<view class="sure" @click="surevip">确定</view>
+				</view>
+			</view>
+		</u-popup>
+		<!-- footer -->
+		<view class="footer">
+			<view class="justify-between" v-if="isvip==1">
+				<!-- <view  class="justify-between" v-if="isvip==1"> -->
+				<view class="copybtn flex-1 mr40" @click="buildimg">生成海报</view>
+				<view class="copybtn flex-1" @click="setTimeShow=!setTimeShow">复制链接</view>
+			</view>
+			<view class='footer-btn footer-btn-border' @click="submit" v-else>
+				<image class="footer-btn-img" v-show="isLogin" src="@/static/image/red_envelope_btnimg.png"
+					mode="aspectFill"></image>
+				<text>提交答案领取红包</text>
+			</view>
+
+		</view>
+		<u-modal :show="setTimeShow" content='content' class="model" @confirm="confirmTime" :closeOnClickOverlay='true'
+			@close="closetext">
+			<view class="setTimebox">
+				<view class="timetip">不传默认以系统参数为准</view>
+				<view class="x-f">
+					<text style="margin-right: 20px;">链接有效时长(分钟)</text>
+					<u-input fontSize="14px" placeholder="链接有效时长" border="none" v-model="time" maxlength="5"></u-input>
+				</view>
+			</view>
+		</u-modal>
+		<u-popup :show="setImg" @close="closeimg" :round="12" style="z-index: 999;">
+			<view class="w100 h500">
+				<image :src="codeLink.url" class="codeimg w660" mode="widthFix"></image>
+			</view>
+			<view class="justify-around mtb40">
+				<view class="column justify-center align-center" @click="shareimg">
+					<image src='@/static/image/wechat.png' class="w80 h80"></image>
+					<view class="mt10">微信好友</view>
+				</view>
+				<view class="column justify-center align-center" @click="downimg">
+					<image src='@/static/image/downicon.png' class="w80 h80"></image>
+					<view class="mt10">长按海报保存</view>
+				</view>
+			</view>
+		</u-popup>
+		<u-overlay :show="showzhidao" @click="showzhidao = false" style="z-index: 9999;">
+			<view class="point-box">
+				<view class="imgshe">
+					<image src='@/static/image/point.png' class="w300 h300"></image>
+				</view>
+				<view class="column colorf fs32 xu-box fs40
+				align-center justify-center">
+					<view class="justify-center">点击右上角
+						<image src="../../../static/image/wxmore.png" class="w50 h50 mlr10"></image>
+					</view>
+					<view class="mt20">选择 “转发给朋友”</view>
+				</view>
+			</view>
+		</u-overlay>
+	</view>
+</template>
+
+<script>
+	import {
+		throttle
+	} from "@/utils/throttle.js"
+	import {
+		loginByMp
+	} from '@/api/user'
+	import {
+		generateRandomString
+	} from "@/utils/common.js"
+	import {
+		numberToLetter
+	} from "@/utils/tools.js"
+	import {
+		getH5CourseByVideoId,
+		getH5CourseVideoDetails, //下次不要同名
+		courseAnswer,
+		getFinishCourseVideo,
+		// getIsAddKf,
+		// getInternetTraffic,
+		idlookCourse,
+		buildCode,
+		sharecourselink,
+		getaddcourseLog,
+		clickCourse,
+		sendReward,
+		getSDK
+	} from "@/api/courseManage.js"
+	//       视频id       视频详情       课程答题      课程完成  获取客服     视频缓冲
+	import html2canvas from 'html2canvas'
+	import wx from 'weixin-js-sdk'
+	import {
+		isDayjs
+	} from "dayjs"
+	// import code from "../../../uni_modules/uview-ui/libs/config/props/code"
+	const isWechat = () => {
+		return String(navigator.userAgent.toLowerCase().match(/MicroMessenger/i)) === "micromessenger";
+	}
+
+	export default {
+		data() {
+			return {
+				setImg: false,
+				showbecome: false,
+				showvip: false,
+				time: '',
+				isvip: 1,
+				showzhidao: false,
+				showShare: false,
+				setTimeShow: false,
+				// 1 红包 2 积分
+				rewardType: [{
+					name: '红包奖励',
+					value: 1
+				}, {
+					name: '积分奖励',
+					value: 2
+				}],
+				currentReward: 1,
+				loading: true,
+				progress: 0,
+				code: null,
+				statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
+				scrollTop: 0,
+				height: '0px',
+				isLogin: true,
+				videoContext: null,
+				videoUrl: "",
+				videoId: "",
+				//现在的时长
+				playTime: 0,
+				//总时长
+				duration: 0,
+				playDuration: 0,
+				// 温馨提醒时间节点,
+				tipsTime: 0,
+				tipsOpen: false,
+				config: {},
+				courseInfo: {},
+				quesList: [],
+				lineList: [],
+				// 错题
+				errQues: [],
+				// 答题机会
+				remain: 0,
+				errTitle: "",
+				errDesc: "",
+				showPlay: true,
+				showControls: false,
+				playStatus: "",
+				isFullscreen: false,
+				isAddKf: 0,
+				lineIndex: 0,
+				// 是否展开
+				isExpand: true,
+				textHeight: 0, //文本高度
+				qwUserId: "",
+				qrcode: "",
+				corpId: "",
+				urlOption: {},
+				bufferRate: 0, // 缓冲时间
+				uuId: "",
+				codeLink: [],
+				copylink: '',
+				linkId: '',
+				companyId: '',
+				companyUserId: '',
+				userId: '',
+				codeState: '',
+				answermsg: '',
+				courselink: '',
+				type: 1,
+				isUservip: 1
+			}
+		},
+		filters: {
+			numberToChinese(number) {
+				const chineseNumber = ['一', '二', '三', '四', '五', '六', '七', '八', '九'];
+				return chineseNumber[number - 1];
+			},
+		},
+		onLoad(option) {
+			this.urlOption = option.course ? JSON.parse(option.course) : {},
+				// console.log(this.urlOption)
+				this.code = option.code
+			this.isvip = option.isvip
+			this.videoId = this.urlOption.videoId
+			this.companyId = this.urlOption.companyId
+			this.companyUserId = this.urlOption.companyUserId
+			this.linkId = this.urlOption.id
+			if (this.isvip == 1) {
+				this.isUservip = 0
+				this.urlOption = {
+					...this.urlOption,
+					isUser: this.isUservip
+				}
+			} else {
+				this.isUservip = 1
+				this.urlOption = {
+					...this.urlOption,
+					isUser: this.isUservip
+				}
+			}
+			console.log(this.urlOption, 111)
+			//获取到code和token和id直接自动登录
+			this.qwUserId = this.urlOption.qwUserId || ''
+			this.corpId = this.urlOption.corpId || ''
+			// 进入页面有code 就loginByMp
+			if (this.linkId != null && this.isvip != 1) {
+				if (!uni.getStorageSync('UserAppToken')) {
+					if (this.code) {
+						this.loginByMp()
+					} else {
+						this.getWechatCode()
+					}
+				}
+			}
+		},
+		onShow() {
+			console.log("onShow=====")
+			this.tipsOpen = false
+			this.isExpand = true
+			this.isLogin = this.utils.isLogin() //登录判断
+			this.uuId = generateRandomString(16)
+			this.users = uni.getStorageSync("userInfo") ? JSON.parse(uni.getStorageSync("userInfo")) : {}
+			this.userId = this.users.userId
+			console.log('===========');
+			console.log(uni.getStorageInfoSync());
+			console.log('UserAppToken:' + uni.getStorageSync('UserAppToken'));
+			console.log('AppToken:' + uni
+				.getStorageSync('AppToken'));
+			console.log('===========');
+			if (this.isvip == 1 || uni.getStorageSync('UserAppToken')) {
+				this.getH5CourseVideoDetailsM()
+				//就是这个地方,没有执行
+			}
+			if (this.videoId) {
+				this.getH5CourseByVideo()
+			}
+		},
+		mounted() {
+			this.getjssdklist()
+			this.getHeight()
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+		},
+		// onHide() {
+		// 	// 页面隐藏 如果登录就执行
+		// 	if (this.utils.isLogin()) {
+		// 		console.log("==onHide===")
+		// 		this.getFinishCourseVideo()
+		// 		this.getInternetTraffic()
+		// 	}
+		// },
+		// onUnload() {
+		// 	// 页面卸载  和隐藏执行一样的
+		// 	if (this.utils.isLogin()) {
+		// 		console.log("==onUnload===")
+		// 		this.getFinishCourseVideo()
+		// 		this.getInternetTraffic()
+		// 	}
+		// },
+		methods: {
+			// 获取jssdk
+			getjssdklist() {
+				const param = {
+					url: window.location.href
+				}
+				getSDK(param).then(res => {
+					wx.config({
+						appId: res.data.appId, // 必填,公众号的唯一标识
+						timestamp: res.data.timestamp, // 必填,生成签名的时间戳
+						nonceStr: res.data.nonceStr, // 必填,生成签名的随机串
+						signature: res.data.signature, // 必填,签名
+						jsApiList: ["updateAppMessageShareData"] // 必填,需要使用的JS接口列表
+					});
+				})
+			},
+			shareimg() {
+				let self = this
+				//分享好友
+				// 配置--你到时候把配置全局 --就是这些东西  调接口拿
+				wx.ready(function() { //需在用户可能点击分享按钮前就先调用
+					wx.updateAppMessageShareData({
+						title: self.courseInfo.courseName + self.courseInfo.title, // 分享标题
+						desc: self.courseInfo.title, // 分享描述
+						link: self.copylink,
+						// 分享链接,该链接域名或路径必须与当前页面对应的公众 号JS安全域名一致
+						imgUrl: self.courseInfo.thumbnail, // 分享图标
+						success: function(res) {
+							// console.log(self.copylink,'456');
+							self.showzhidao = true
+							self.setImg = false
+							self.showShare = false
+							// 设置成功
+							uni.showToast({
+								title: '海报已生成',
+								icon: 'none',
+								duration: 1000
+							});
+						},
+						fail: function(err) {
+							uni.showToast({
+								title: '海报生成失败,请重试',
+								icon: 'none',
+								duration: 2000
+							})
+						}
+					})
+				});
+			},
+			closeimg() {
+				this.setImg = false
+			},
+			closetext() {
+				this.setTimeShow = false
+			},
+			//是否可以看课
+			isLOOK() {
+				if (this.isvip == 1) {
+					return
+				}
+				const param = {
+					companyId: this.companyId,
+					companyUserId: this.companyUserId,
+					linkId: this.linkId,
+					userId: this.userId,
+					videoId: this.videoId,
+					code: this.code,
+					createTime: ''
+				}
+				idlookCourse(param).then(res => {
+					if (res.code == 200) {
+
+					} else if (res.code == 501) {
+						uni.showToast({
+							icon: 'none',
+							title: '链接已过期'
+						})
+						this.showvip = true
+						this.answermsg = res.data
+					} else if (res.code == 502) {
+						uni.showToast({
+							icon: 'none',
+							title: '请先成为会员'
+						})
+						setTimeout(() => {
+							uni.navigateTo({
+								url: `/pages/courseManage/course/becomeVip?
+								userId=${this.userId}&companyId=${this.companyId}&companyUserId=${this.companyUserId}&code=${this.code}`
+							})
+						}, 500)
+					} else if (res.code == 503) {
+						this.showvip = true
+						this.answermsg = res.data
+					} else if (res.code == 504) {
+						uni.showToast({
+							icon: 'none',
+							title: '请看课成为会员'
+						})
+					}
+				})
+			},
+			open() {
+
+			},
+			close() {
+
+			},
+			buildimg() {
+				this.buildimgAcode()
+				// console.log(this.urlOption.videoId)
+			},
+			//生成海报和二维码
+			buildimgAcode() {
+				uni.showLoading({
+					title: '正在生成中...'
+				})
+				buildCode({
+					videoId: this.urlOption.videoId
+				}).then(res => {
+					if (res.code == 200) {
+						this.codeLink = res.data
+						this.setImg = true
+						this.getlink()
+						// console.log(res)
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getlink() {
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					courseId: this.courseInfo.courseId,
+					time: this.time,
+					type: this.type,
+					videoId: this.courseInfo.videoId,
+				}
+				sharecourselink(params).then(res => {
+					if (res.code == 200) {
+						this.copylink = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			confirmTime() {
+				this.setTimeShow = !this.setTimeShow
+				this.showShare = !this.showShare
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					courseId: this.courseInfo.courseId,
+					time: this.time,
+					type: this.type,
+					videoId: this.courseInfo.videoId,
+				}
+				sharecourselink(params).then(res => {
+					if (res.code == 200) {
+						this.copylink = res.data
+						// console.log(res.data)
+						setTimeout(() => {
+							uni.setClipboardData({
+								data: this.copylink,
+								success: () => {
+									uni.showToast({
+										title: '链接已复制',
+										icon: 'success',
+										duration: 2000
+									});
+								},
+								fail: () => {
+									uni.showToast({
+										title: '复制失败',
+										icon: 'none'
+									});
+								}
+							});
+						}, 100)
+
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			// 建立选择
+			rewardChange(e) {
+				this.currentReward = e.detail.value
+			},
+			getHeight() {
+				this.$nextTick(() => {
+					const query = uni.createSelectorQuery().in(this);
+					query
+						.select("#title-content")
+						.boundingClientRect((data) => {
+							this.height =
+								`calc(100vh - ${data.height}px - 420rpx - ${this.statusBarHeight}px - 164rpx)`
+						})
+						.exec();
+				})
+			},
+			getDescHeight() {
+				this.$nextTick(() => {
+					const query = uni.createSelectorQuery().in(this);
+					query
+						.select("#descbox-desc")
+						.boundingClientRect((data) => {
+							this.textHeight = data.height
+						})
+						.exec();
+				})
+			},
+			numberToLetter(num) {
+				// 将数字转换为字母的 ASCII 码
+				let letterCode = num + 65;
+				// 将 ASCII 码转换为大写字母
+				let letter = String.fromCharCode(letterCode);
+				return letter;
+			},
+			videoTimeupdate(e) {
+				if (this.isvip == 1) {
+
+				} else {
+					let currentTime = Math.round(e.detail.currentTime)
+					// 禁止拖动进度条快进
+					if (!this.playDuration) {
+						if (currentTime - this.playTime > 2) {
+							uni.showToast({
+								title: '不能快进哦',
+								icon: 'none',
+							});
+							if (!this.videoContext) {
+								this.videoContext = uni.createVideoContext('myVideo');
+							}
+							this.videoContext.seek(this.playTime);
+						}
+						if (this.playTime - currentTime > 1) {
+							this.tipsOpen = false
+						}
+					} else {
+						this.$nextTick(() => {
+							this.playDuration = 0
+						})
+					}
+					this.progress = (currentTime / Math.round(e.detail.duration)) * 100
+					this.playTime = currentTime
+					if (!this.tipsOpen && this.tipsTime && this.playTime == this.tipsTime) {
+						this.openTipsPop()
+					}
+					if (this.playTime > 0) {
+						throttle(this.throttleFun, 10000, false)
+					}
+				}
+
+			},
+			throttleFun() {
+				this.getFinishCourseVideo()
+				this.getInternetTraffic()
+			},
+			videoErrorCallback(e) {
+				// console.log(e.target.errMsg)
+			},
+			clickVideo() {
+				this.showControls = !this.showControls
+				if (this.showControls) {
+					setTimeout(() => {
+						this.showControls = this.playStatus == "pause"
+					}, 3000)
+				}
+				// console.log(this.userId)
+				const param = {
+					code: this.code,
+					companyId: this.companyId,
+					companyUserId: this.companyUserId,
+					linkId: this.linkId,
+					userId: this.userId,
+					videoId: this.videoId
+				}
+				clickCourse(param).then(res => {
+					console.log(res)
+				})
+			},
+			play(initialTime) {
+				if (!this.videoContext) {
+					this.videoContext = uni.createVideoContext('myVideo')
+				}
+				this.$nextTick(() => {
+					if (initialTime) {
+						this.videoContext.seek(initialTime);
+					}
+					this.videoContext.play()
+				})
+				setTimeout(() => {
+					this.showControls = false
+				}, 3000)
+			},
+			pause() {
+				if (!this.videoContext) {
+					this.videoContext = uni.createVideoContext('myVideo')
+				}
+				this.videoContext.pause()
+			},
+			handleFullScreen() {
+				if (!this.videoContext) {
+					this.videoContext = uni.createVideoContext('myVideo')
+				}
+				if (this.isFullscreen) {
+					this.videoContext.exitFullScreen()
+				} else {
+					this.videoContext.requestFullScreen()
+				}
+				setTimeout(() => {
+					this.showControls = false
+				}, 3000)
+			},
+			// 当开始/继续播放时触发play事件
+			getPlay() {
+				this.showPlay = false
+				this.playStatus = "play"
+			},
+			// 暂停
+			getPause() {
+				this.playStatus = "pause"
+			},
+			// 结束
+			getEnded() {
+				this.playStatus = "end"
+			},
+			// 全屏
+			fullscreenchange(e) {
+				this.isFullscreen = e.detail.fullScreen
+			},
+			// 视频进度条更改
+			progressChange(e) {
+				this.bufferRate = Math.ceil(e.detail.buffered)
+			},
+			// 展开简介
+			handleExpand() {
+				this.isExpand = !this.isExpand
+			},
+			getH5CourseByVideo() {
+				this.loading = true
+				getH5CourseByVideoId({
+					videoId: this.videoId
+				}).then(res => {
+						this.loading = false
+						if (res.code == 200) {
+							// console.log(res)
+							this.courseInfo = res.data
+							uni.setNavigationBarTitle({
+								title: this.courseInfo && this.courseInfo.title ? this.courseInfo
+									.title : ''
+							});
+							this.$nextTick(() => {
+								this.playStatus = "start"
+								this.videoContext = uni.createVideoContext('myVideo')
+							})
+						}
+						this.getHeight()
+						this.getDescHeight()
+					},
+					rej => {
+						this.loading = false
+					}
+				).catch(() => {
+					this.loading = false
+				})
+			},
+			getH5CourseVideoDetailsM() {
+				console.log(1433223);
+				// 不要同名  方法名字喝接口名字不能这样搞
+				// {videoId: this.videoId}
+				console.log(this.isvip == 1)
+				getH5CourseVideoDetails(this.urlOption).then(res => {
+						console.log(res);
+						if (res.code == 200) {
+							// console.log(res.course.videoUrl)
+							this.videoUrl = res.course.videoUrl
+							// this.videoUrl = res.course && res.course.lineOne ? res.course.lineOne : ""
+							this.config = res.config || {}
+							this.duration = res.course && res.course.duration ? res.course.duration : 0
+							this.playDuration = res.playDuration || 0
+							let videoContext = uni.createVideoContext('myVideo');
+							videoContext.seek(this.playDuration);
+
+							this.tipsTime = res.tipsTime || 0
+							let lineList = []
+							if (res.course && res.course.lineOne) {
+								lineList.push(res.course.lineOne)
+							}
+							if (res.course && res.course.lineTwo) {
+								lineList.push(res.course.lineTwo)
+							}
+							if (res.course && res.course.lineThree) {
+								lineList.push(res.course.lineThree)
+							}
+							this.lineList = lineList
+							this.quesList = res.questions && res.questions.length > 0 ? res.questions : []
+							this.quesList = this.quesList.map(item => ({
+								...item,
+								questionOption: JSON.parse(item.question),
+								answer: '',
+
+							}))
+						}
+						this.getHeight()
+						this.getDescHeight()
+					},
+					rej => {}
+				)
+			},
+			handleAnswer(item, option, idx) {
+				const rate = (Number(this.playTime || 0) / Number(this.duration || 0)).toFixed(2)
+				if (Number(this.duration || 0) == 0 || rate * 100 < Number(this.config.answerRate || 0)) {
+					uni.showToast({
+						title: "请先观看完整课程再答题哦~",
+						icon: "none"
+					})
+					return
+				}
+
+				if (item.type == 1) {
+					// 单选option
+					item.answer = option.name
+				} else if (item.type == 2) {
+					// 多选
+					let answer = item.answer ? item.answer.split(',') : []
+					if (answer.indexOf(option.name) === -1) {
+						answer.push(option.name)
+						item.answer = answer.join(',')
+					} else {
+						answer.splice(answer.indexOf(option.name), 1)
+						item.answer = answer.join(',')
+					}
+				}
+			},
+			submit() {
+				// 登录
+				this.courseAnswer()
+			},
+			// 答题
+			courseAnswer() {
+				const rate = (this.playTime / this.duration).toFixed(2)
+				if (Number(this.duration || 0) == 0 || rate * 100 < Number(this.config.answerRate || 0)) {
+					uni.showToast({
+						title: "请先观看完整课程再答题哦~",
+						icon: "none"
+					})
+					return
+				}
+				if (this.quesList.some(item => !item.answer)) {
+					uni.showToast({
+						title: "请确认是否答完所有题目",
+						icon: "none"
+					})
+					return
+				}
+				const questions = this.quesList.map(obj => {
+					const {
+						questionOption,
+						...rest
+					} = obj;
+					return rest;
+				});
+				const param = {
+					questions: questions,
+					videoId: this.videoId,
+					userId: this.userId,
+					...this.urlOption
+				}
+				this.errTitle = ""
+				this.errDesc = ""
+				this.errQues = []
+				courseAnswer(param).then(res => {
+						if (res.code == 200) {
+							if (res.incorrectQuestions) {
+								// 答题失败
+								if (res.incorrectQuestions.length > 0) {
+									this.errQues = res.incorrectQuestions
+								}
+								this.remain = res.remain || 0
+								if (res.remain > 0) {
+									this.errTitle = "很遗憾答错了"
+									this.errDesc = `<span style="color:#FF5C03">还有${res.remain}次机会,继续加油</span>`
+									this.$refs.answerPopup.open("center")
+								}
+							} else {
+								// 答题成功
+								this.errTitle = "恭喜你,回答正确"
+								this.errDesc = `将发送奖励至您的微信账户`
+								this.$refs.answerPopup.open("center")
+							}
+						} else {
+							if (res.msg == "该课题到达答错次数限制") {
+								this.errTitle = "答题次数超过限制"
+								this.errDesc = "以后的课程要认真学习哦"
+								this.$refs.answerPopup.open("center")
+							} else {
+								uni.showToast({
+									title: res.msg,
+									icon: "none"
+								})
+							}
+						}
+					},
+					rej => {}
+				)
+			},
+			closeAnswerPopup() {
+				this.$refs.answerPopup.close()
+				if (this.errTitle == '恭喜你,回答正确') {
+					const param = {
+						...this.urlOption,
+						rewardType: Number(this.currentReward),
+						userId: this.userId
+					}
+					console.log(param)
+					sendReward(param).then(res => {
+						uni.showToast({
+							title: res.msg,
+							icon: 'none'
+						})
+					})
+				}
+			},
+			// 线路
+			openPop() {
+				this.$refs.popup.open('bottom')
+			},
+			close() {
+				this.$refs.popup.close()
+			},
+			handleLine(index) {
+				this.lineIndex = index
+				this.videoUrl = this.lineList[index]
+				this.$nextTick(() => {
+					this.tipsOpen = false
+					this.videoContext = null
+
+					this.play(this.playTime)
+				})
+				this.close()
+			},
+			// 温馨提示
+			openTipsPop() {
+				this.$refs.tipsPopup.open()
+				this.tipsOpen = true
+				this.pause()
+			},
+			closeTipsPop() {
+				this.$refs.tipsPopup.close()
+				this.$nextTick(() => {
+					if (!this.videoContext) {
+						this.videoContext = uni.createVideoContext('myVideo')
+					}
+					this.videoContext.play()
+				})
+			},
+			closeKFPop() {
+				this.$refs.kfPopup.close()
+			},
+			// 获取code 
+			getWechatCode() {
+				// 没有code
+				if (isWechat) {
+					let appid = "wx93ce67750e3cfba3"; //微信APPid
+					let code = this.getUrlCode().code; //是否存在code
+					// 换成本地就不行
+					let local = window.location.href; //这个是获取浏览器当前地址 有参数都会一起获取
+					if (code == null || code === "") {
+						let urlPaths = local.split("/courseh5");
+						uni.setStorageSync('beforLoginPage', urlPaths[1]);
+
+						//不存在就打开上面的地址进行授权 
+						// 跳转授权  redirect_uri:local是当前地址  当发布到线上就是线上那个地址  也就是用户打开浏览器的地址 
+						// 开发的时候是本地的地址8081  
+						window.location.href =
+							"https://open.weixin.qq.com/connect/oauth2/authorize?appid=" +
+							appid +
+							"&redirect_uri=" +
+							encodeURIComponent(local) +
+							"&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
+						// 后面的就是授权 snsapi_userinfo弹出授权弹窗     授权成功后  返回redirect_uri给的页面 
+						// 当前页面  在授权成功微信已经给了code了   由于替换成 http://h5.wxcourse.cdwjyyh.com 地址 所以跳转到线上  而不是本地  线上地址
+					} else {
+						this.code = code;
+						this.loginByMp()
+					}
+				}
+			},
+			getUrlCode() {
+				// 截取url中的code方法
+				var url = location.search;
+				var theRequest = new Object();
+				if (url.indexOf("?") != -1) {
+					var str = url.substr(1);
+					var strs = str.split("&");
+					for (var i = 0; i < strs.length; i++) {
+						theRequest[strs[i].split("=")[0]] = strs[i].split("=")[1];
+					}
+				}
+				return theRequest;
+			},
+			loginByMp() {
+				if (this.code == null) {
+					return;
+				}
+				uni.showLoading({
+					title: "处理中..."
+				});
+				let that = this;
+				var data = {
+					code: this.code
+				}
+				loginByMp(data).then(res => {
+						uni.hideLoading();
+						if (res.code == 200) {
+							// 登录后存token和用户信息
+							uni.setStorageSync('UserAppToken', res.token);
+							uni.setStorageSync('userInfo', JSON.stringify(res.user));
+							console.log(uni.getStorageSync('userInfo', JSON.stringify(res.user)))
+							this.users = uni.getStorageSync("userInfo") ? JSON.parse(uni.getStorageSync(
+								"userInfo")) : {}
+							this.userId = res.user.userId
+							let beforLoginUrl = uni.getStorageSync('beforLoginPage');
+							// console.log("beforLoginUrl:"+beforLoginUrl);
+							// console.log(`登录成功后跳转${beforLoginUrl}`);
+							uni.reLaunch({
+								url: beforLoginUrl
+							});
+							this.isLOOK()
+							window.location.href = window.location.href
+						} else {
+							uni.showToast({
+								title: res.msg,
+								icon: 'none'
+							});
+						}
+					},
+					err => {}
+				);
+			},
+			getFinishCourseVideo() {
+				// {videoId: this.videoId,duration:this.playTime}
+				const param = {
+					duration: this.playTime,
+					userId: this.userId,
+					...this.urlOption
+				}
+				getFinishCourseVideo(param)
+			},
+			getInternetTraffic() {
+				const playVideoTime = Math.ceil(this.playTime / this.duration * 100) // 播放百分比
+				if (this.bufferRate == 0 || this.bufferRate < playVideoTime) {
+					this.bufferRate = playVideoTime
+				}
+				if (this.bufferRate == 0) return
+				const param = {
+					uuId: this.uuId,
+					duration: this.playTime,
+					bufferRate: Number(this.bufferRate.toFixed(2)),
+					...this.urlOption
+				}
+				// this.getInternetTraffic(param)
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.imgshe {
+		display: flex;
+		flex-direction: row-reverse
+	}
+
+	.point-box {
+		height: 100%;
+		width: 100%;
+
+		.xu-box {
+			border: #f5f5f5 4rpx dashed;
+			padding: 20rpx 20rpx;
+		}
+	}
+
+	.uni-popup {
+		top: 10%;
+		left: 12%;
+	}
+
+	.quxiao {
+		width: 30%;
+		height: 72rpx;
+		line-height: 72rpx;
+		text-align: center;
+		border: 2rpx solid #ccc;
+		border-radius: 8rpx;
+		color: #666;
+		margin-right: 10rpx;
+	}
+
+	.sure {
+		width: 30%;
+		background-color: #1777ff;
+		height: 72rpx;
+		line-height: 72rpx;
+		text-align: center;
+		border-radius: 8rpx;
+		color: #fff;
+		margin-left: 10rpx;
+	}
+
+	@mixin u-flex($flexD, $alignI, $justifyC) {
+		display: flex;
+		flex-direction: $flexD;
+		align-items: $alignI;
+		justify-content: $justifyC;
+	}
+
+	::v-deep {
+		.uni-video-bar {
+			background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.5) 100%);
+			;
+		}
+
+		.uni-video-progress-buffered {
+			width: var(--progress) !important;
+			background-color: #FF5C03;
+		}
+
+		.uni-video-inner {
+			background-color: #FF5C03;
+		}
+	}
+
+	.textOne {
+		overflow: hidden;
+		white-space: nowrap;
+		text-overflow: ellipsis;
+	}
+
+	.textTwo {
+		overflow: hidden;
+		text-overflow: ellipsis;
+		display: -webkit-box;
+		-webkit-line-clamp: 2;
+		-webkit-box-orient: vertical;
+	}
+
+	.err {
+		color: #f56c6c !important;
+	}
+
+	.kfqrcode-box {
+		background-color: #fff;
+		border-radius: 16rpx;
+		max-width: 560rpx;
+		padding: 80rpx 32rpx;
+		margin-top: -100rpx;
+		box-sizing: border-box;
+		@include u-flex(column, center, flex-start);
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 400;
+		font-size: 30rpx;
+		color: #222;
+		position: relative;
+
+		.kfqrcode {
+			height: 320rpx;
+			width: 320rpx;
+			margin-bottom: 40rpx;
+		}
+	}
+
+	.kfqrcode-close {
+		width: 64rpx;
+		height: 64rpx;
+		position: absolute;
+		bottom: -100rpx;
+		left: 50%;
+		transform: translateX(-50%);
+	}
+
+	.tipsPopup-mask {
+		position: relative;
+		width: 560rpx;
+		background-color: transparent;
+
+		.red_envelope_top {
+			width: 480rpx;
+			height: 360rpx;
+			margin: 0 auto;
+			display: inherit;
+		}
+	}
+
+	.tipsPopup-btn-box {
+		width: 456rpx;
+		height: 104rpx;
+		padding: 4rpx;
+		box-sizing: border-box;
+		background: linear-gradient(180deg, rgba(252, 209, 94, 1), rgba(254, 253, 251, 1));
+		border-radius: 52rpx;
+	}
+
+	.tipsPopup-btn {
+		width: 100%;
+		height: 100%;
+		background: linear-gradient(180deg, #FF9F22 0%, #FA1E05 100%);
+		border-radius: 52rpx 52rpx 52rpx 52rpx;
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 500;
+		font-size: 36rpx;
+		color: #FFFFFF;
+		line-height: 96rpx;
+		text-align: center;
+	}
+
+	.tipsPopup {
+		width: 560rpx;
+		padding: 12rpx;
+		margin-top: -72rpx;
+		box-sizing: border-box;
+		background: linear-gradient(180deg, #FFFBEF 0%, #FFFFF5 43%, #F5EAC2 100%);
+		border-radius: 32rpx 32rpx 32rpx 32rpx;
+		position: relative;
+
+		&-close {
+			width: 64rpx;
+			height: 64rpx;
+			position: absolute;
+			right: 0;
+			top: -188rpx;
+		}
+
+		&-line {
+			padding: 3rpx;
+			box-sizing: border-box;
+			background: linear-gradient(180deg, rgba(247, 245, 220, 1), rgba(250, 220, 157, 1));
+			border-radius: 24rpx;
+		}
+
+		&-box {
+			padding: 0 40rpx 40rpx 40rpx;
+			box-sizing: border-box;
+			background: linear-gradient(180deg, #FFFBEF 0%, #FFFFF5 43%, #F5EAC2 100%);
+			border-radius: 24rpx;
+			@include u-flex(column, center, flex-start);
+		}
+
+		&-head {
+			@include u-flex(row, center, center);
+
+			&-title {
+				width: 364rpx;
+				height: auto;
+				margin-top: -22rpx;
+			}
+		}
+
+		&-content {
+			margin: 48rpx 0;
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 500;
+			font-size: 32rpx;
+			color: #222222;
+			text-align: center;
+
+			&-title {
+				margin-bottom: 26rpx;
+				font-weight: 600;
+				font-size: 40rpx;
+				color: #FF5C03;
+			}
+		}
+	}
+
+	.video-controls-box {
+		width: 100%;
+		height: 420rpx;
+		overflow: hidden;
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		z-index: 2;
+		background: rgba(0, 0, 0, 0.2);
+
+		.video-play {
+			height: 72rpx;
+			width: 72rpx;
+			position: absolute;
+			top: 50%;
+			left: 50%;
+			transform: translate(-50%, -50%);
+		}
+	}
+
+	.video-controls {
+		width: 100%;
+		height: 80rpx;
+		padding: 0 28rpx;
+		box-sizing: border-box;
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		background: linear-gradient(to top, #222 0%, transparent 80%);
+
+		.video-icon {
+			height: 44rpx;
+			width: 44rpx;
+		}
+	}
+
+	.errQuesbox {
+		width: 100%;
+		max-height: 260rpx;
+		overflow-y: auto;
+		margin-top: 24rpx;
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 500;
+		font-size: 30rpx;
+		color: #222222;
+
+		&-item {
+			width: 100%;
+			height: 128rpx;
+			line-height: 128rpx;
+			margin-bottom: 24rpx;
+			padding: 0 30rpx;
+			box-sizing: border-box;
+			overflow: hidden;
+			background: #fff;
+			border-radius: 16rpx 16rpx 16rpx 16rpx;
+			position: relative;
+
+			&::after {
+				content: "题目";
+				min-width: 64rpx;
+				height: 36rpx;
+				padding: 0 12rpx;
+				line-height: 36rpx;
+				background: #FF5C03;
+				box-sizing: border-box;
+				border-radius: 0rpx 0rpx 16rpx 0rpx;
+				text-align: center;
+				font-family: PingFang SC, PingFang SC;
+				font-weight: 500;
+				font-size: 20rpx;
+				color: #fff;
+				position: absolute;
+				left: 0;
+				top: 0;
+			}
+		}
+	}
+
+	.bg {
+		background: #fff !important;
+	}
+
+	.answerPopup {
+		&-box {
+			width: 560rpx;
+			background: linear-gradient(180deg, #FFFAF6 0%, #FEECD8 100%);
+			border-radius: 32rpx 32rpx 32rpx 32rpx;
+			background-color: #fff;
+			font-weight: 400;
+			padding: 32rpx;
+			box-sizing: border-box;
+			position: relative;
+			@include u-flex(column, center, flex-start);
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 400;
+
+			.tipimg {
+				width: 206rpx;
+				height: 206rpx;
+				margin-bottom: 16rpx;
+			}
+		}
+
+		&-title {
+			font-weight: 600;
+			font-size: 36rpx;
+			color: #222222;
+		}
+
+		&-desc {
+			margin-top: 10rpx;
+			font-size: 28rpx;
+			color: #757575;
+		}
+
+		&-btn {
+			width: 464rpx;
+			height: 84rpx;
+			margin-top: 54rpx;
+			margin-bottom: 16rpx;
+			background: #FF5C03;
+			border-radius: 42rpx;
+			font-weight: 500;
+			font-size: 32rpx;
+			color: #FFFFFF;
+			text-align: center;
+			line-height: 84rpx;
+		}
+	}
+
+	.popupbox {
+		background-color: #fff;
+		border-radius: 16rpx 16rpx 0 0;
+		padding: 24rpx 32rpx;
+		position: relative;
+
+		&-head {
+			height: 60rpx;
+			margin-bottom: 30rpx;
+			text-align: center;
+			overflow-y: auto;
+			color: #414858;
+			font-size: 32rpx;
+			font-weight: bold;
+			position: relative;
+
+			.close-icon {
+				position: absolute;
+				right: 0;
+				top: 0;
+				height: 40rpx;
+				width: 40rpx;
+			}
+		}
+
+		&-content {
+			height: 20vh;
+			overflow-y: auto;
+			display: flex;
+			align-items: flex-start;
+			flex-wrap: wrap;
+			gap: 32rpx;
+
+			.line-item {
+				display: inline-block;
+				min-width: 200rpx;
+				height: 60rpx;
+				padding: 0 20rpx;
+				box-sizing: border-box;
+				border-radius: 50rpx;
+				overflow: hidden;
+				background-color: #f7f7f7;
+				text-align: center;
+				color: #414858;
+				font-size: 28rpx;
+				line-height: 60rpx;
+			}
+
+			.line-active {
+				color: #f56c6c !important;
+				background-color: #fef0f0 !important;
+			}
+		}
+	}
+
+	.content {
+		padding-bottom: calc(var(--window-bottom) + 164rpx);
+
+		.video-box {
+			width: 100%;
+			height: 420rpx;
+			overflow: hidden;
+			position: relative;
+
+			#myVideo {
+				width: 100%;
+				height: 100%;
+			}
+		}
+
+		.video-poster {
+			width: 100%;
+			height: 420rpx;
+		}
+
+		.miantitlebox {
+			padding: 30rpx 0;
+			border-bottom: 2rpx solid #F5F7FA;
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 500;
+			font-size: 36rpx;
+			color: #222222;
+		}
+
+		.subtitlebox {
+			padding: 30rpx 0;
+			border-bottom: 2rpx solid #F5F7FA;
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 500;
+			font-size: 36rpx;
+			color: #222222;
+		}
+
+		.title-content {
+			padding: 0 32rpx;
+			background-color: #fff;
+			font-size: 28rpx;
+			line-height: 1.6;
+
+			.title {
+				font-size: 36rpx;
+				font-weight: 500;
+				color: #414858;
+			}
+
+			.time-or-subtitle {
+				margin-top: 12rpx;
+				color: #666666;
+			}
+		}
+
+		.descbox {
+			padding: 36rpx 32rpx;
+			margin-bottom: 20rpx;
+			background-color: #fff;
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 400;
+			font-size: 28rpx;
+			color: #222222;
+			line-height: 42rpx;
+			word-break: break-word;
+
+			&-title {
+				margin-bottom: 24rpx;
+				font-weight: 500;
+				font-size: 32rpx;
+			}
+
+			&-info {
+				margin-bottom: 24rpx;
+				@include u-flex(row, center, space-between);
+				font-size: 26rpx;
+				color: #757575;
+
+				&-l {
+					flex: 1;
+					@include u-flex(row, center, flex-start);
+				}
+
+				&-time {
+					margin-left: 18rpx;
+					padding-left: 18rpx;
+					position: relative;
+
+					&::after {
+						content: "";
+						width: 4rpx;
+						height: 4rpx;
+						background: #999999;
+						border-radius: 50%;
+						position: absolute;
+						left: 0;
+						top: 50%;
+						transform: translateY(-50%);
+					}
+				}
+
+				&-r {
+					background: transparent;
+				}
+			}
+
+			&-desc {
+				overflow: hidden;
+				position: relative;
+			}
+		}
+
+		.expand {
+			flex-shrink: 0;
+			@include u-flex(row, center, flex-end);
+			color: #FF5C03;
+			font-weight: 400;
+			font-size: 24rpx;
+
+			image {
+				width: 32rpx;
+				height: 32rpx;
+			}
+		}
+
+		.expand-ab {
+			position: absolute;
+			top: 0;
+			right: 0;
+			box-shadow: -50rpx 0 20rpx 8rpx #FFFFFF;
+			background-color: #fff;
+		}
+
+		.ques-content {
+			background-color: #fff;
+			padding: 40rpx 32rpx;
+			box-sizing: border-box;
+			ont-family: PingFang SC, PingFang SC;
+			font-weight: 400;
+			font-size: 28rpx;
+			color: #222222;
+		}
+
+		.ques-content-tit {
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 600;
+			font-size: 36rpx;
+			color: #222222;
+		}
+
+		.ques-title {
+			margin: 48rpx 0 34rpx 0;
+			font-weight: 500;
+			font-size: 32rpx;
+			white-space: normal;
+		}
+
+		.ques-type {
+			flex-shrink: 0;
+			min-width: 72rpx;
+			height: 40rpx;
+			padding: 0 12rpx;
+			margin: 0 12rpx;
+			box-sizing: border-box;
+			background: #FF5C03;
+			border-radius: 8rpx 8rpx 8rpx 8rpx;
+			line-height: 40rpx;
+			text-align: center;
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 400;
+			font-size: 24rpx;
+			color: #FFFFFF;
+			display: inline-block;
+		}
+
+		.ques-option {
+			min-height: 88rpx;
+			padding: 24rpx 32rpx;
+			box-sizing: border-box;
+			margin-bottom: 24rpx;
+			background: #F5F7FA;
+			border-radius: 16rpx 16rpx 16rpx 16rpx;
+			display: flex;
+			align-items: center;
+
+			&-active {
+				color: #FF5C03 !important;
+				background: #FCF0E7 !important;
+			}
+		}
+
+		.video-line {
+			min-width: 140rpx;
+			max-width: 200rpx;
+			height: 60rpx;
+			padding: 0 20rpx;
+			box-sizing: border-box;
+			border-radius: 50rpx 0 0 50rpx;
+			overflow: hidden;
+			background-color: #fff;
+			text-align: center;
+			color: #888;
+			font-size: 28rpx;
+			line-height: 60rpx;
+			display: inline-flex;
+			align-items: center;
+			justify-content: center;
+			position: fixed;
+			right: 0;
+			z-index: 9;
+			bottom: calc(var(--window-bottom) + 280rpx);
+			box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, .12);
+
+			image {
+				flex-shrink: 0;
+				height: 30rpx;
+				width: 30rpx;
+				margin-right: 6rpx;
+			}
+		}
+
+		.footer {
+			border-top: 1rpx solid #ededef;
+			background: #fff;
+			width: 100%;
+			position: fixed;
+			bottom: 0;
+			padding: 24rpx 32rpx;
+			// padding-bottom: calc(var(--window-bottom) + 32rpx);
+			box-sizing: border-box;
+			z-index: 9;
+
+			&-btn {
+				width: 100%;
+				height: 98rpx;
+				background: #FF5C03;
+				border-radius: 49rpx 49rpx 49rpx 49rpx;
+				line-height: 98rpx;
+				text-align: center;
+				font-family: PingFang SC, PingFang SC;
+				font-weight: 600;
+				font-size: 32rpx;
+				color: #FFFFFF;
+				@include u-flex(row, center, center);
+
+				&-img {
+					flex-shrink: 0;
+					width: 144rpx;
+					height: 144rpx;
+					margin-right: 8rpx;
+					margin-top: -24rpx;
+				}
+			}
+
+			&-btn-border {
+				position: relative;
+
+				&::after {
+					content: "";
+					background: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
+					position: absolute;
+					top: -2rpx;
+					left: 0;
+					height: 103rpx;
+					width: 100%;
+					z-index: -1;
+					border-radius: 49rpx 49rpx 49rpx 49rpx;
+					box-shadow: 0rpx 8rpx 11rpx 0rpx rgba(255, 92, 3, 0.3);
+					overflow: hidden;
+				}
+			}
+		}
+	}
+
+	.copybtn {
+		background: #FF5C03;
+		color: #fff;
+		text-align: center;
+		font-size: 28rpx;
+		border-radius: 80rpx;
+		padding: 20rpx 0;
+	}
+
+	.VIPvie {
+		width: 580rpx;
+		background: linear-gradient(to bottom, #c3dbfe 2%, #f6fbfe 50%);
+		border-radius: 20rpx;
+		height: 600rpx;
+		position: relative;
+
+		image {
+			position: absolute;
+			top: -220rpx;
+			left: 16%;
+		}
+	}
+
+	.codeimg {
+		position: absolute;
+		z-index: 9999;
+		left: 40rpx;
+		top: 40rpx;
+	}
+
+	#codeurl {
+		position: relative;
+	}
+</style>

+ 22 - 0
pages/courseManage/course/living.vue

@@ -0,0 +1,22 @@
+<template>
+	<view class="column">
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 378 - 65
pages/courseManage/dataIndex/index.vue

@@ -1,6 +1,6 @@
 <!-- 数据组件页面 -->
 <template>
-	<view class="container">
+	<view class="column flex-1 hb">
 		<view class="topdata">
 			<view class="topdata-box">
 				<view>数据汇总</view>
@@ -15,23 +15,53 @@
 					</view>
 				</view>
 			</view>
+			<view>
+				<u-collapse ref="puyaodameinvRef" :value="['0']" :border='false' @change='changelable'>
+					<u-collapse-item name="0" :border='false'>
+						<view slot="title">
+							<text class="bold fs32">标签统计</text>
+							<text class="fs24 base-color-9 ml12">左滑查看更多</text>
+						</view>
+						<text slot="value" class="statistics-slot-title">{{collapseLable?'展开':'收回'}}</text>
+						<view slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</view>
+						<view class="">
+							<view class=" mb20 morelable">
+								<view class="base-bg-f5 p20  radius20 boxlable mr40"
+									v-for="(item,index) in lablelistnum" :key="index" style="display: inline-block;">
+									<view class="base-color-3 ">{{item.tag}}</view>
+									<view class="base-color mt8">
+										<text class="fs40 bold">{{item.count}}</text>
+										人
+									</view>
+								</view>
+							</view>
+						</view>
+					</u-collapse-item>
+				</u-collapse>
+			</view>
 			<view class="searchbox">
 				<view class="searchbox-bar x-ac">
 					<view :class="queryParam.type == index ? 'searchbox-item searchbox-active':'searchbox-item'"
 						v-for="(item,index) in typeOption" :key="index" @click="handleType(index)">{{item.label}}</view>
 				</view>
 				<view class="x-ac ss" v-show="queryParam.type == 3">
-					<view class="calendar-day x-c" @click="showCalendar = true"><u-icon name="calendar" color="#999" size="20"></u-icon>{{startTime}}</view>
-					<view class="calendar-day x-c" @click="showCalendar = true"><u-icon name="calendar" color="#999" size="20"></u-icon>{{endTime}}</view>
+					<view class="calendar-day x-c" @click="showCalendar = true"><u-icon name="calendar" color="#999"
+							size="20"></u-icon>{{startTime}}</view>
+					<view class="calendar-day x-c" @click="showCalendar = true"><u-icon name="calendar" color="#999"
+							size="20"></u-icon>{{endTime}}</view>
 				</view>
 			</view>
 		</view>
-		<u-calendar :show="showCalendar" :mode="mode" @confirm="confirmCalendar" @close="closeCalendar"></u-calendar>
-		<view class="statistics" :style="{height: contentH}">
+		<u-calendar :show="showCalendar" :mode="mode" @confirm="confirmCalendar" style="flex: 0;"
+		 @close="closeCalendar"></u-calendar>
+		<view class="statistics pb120" :style="{height: contentH}">
 			<u-collapse :border='false' :value='collapseValue' @change="changeCollapse">
 				<u-collapse-item name="course">
 					<text slot="title" class="statistics-title">课程统计</text>
-					<text slot="value" class="statistics-slot-title">{{collapseValue.includes('course')?'收回':'展开'}}</text>
+					<text slot="value"
+						class="statistics-slot-title">{{collapseValue.includes('course')?'收回':'展开'}}</text>
 					<view slot="right-icon">
 						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
 					</view>
@@ -39,26 +69,32 @@
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">观看人数</view>
 							<view class="collapse-content-num"><text>{{courseCount.count || 0}}</text>人</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(courseCount.count,courseCount.yesterdayCount)}}</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">
+								较昨天持平{{compare(courseCount.count,courseCount.yesterdayCount)}}</view>
 							<view v-show="queryParam.type == 0">昨天{{courseCount.yesterdayCount || 0}}</view>
 						</view>
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">完播人数</view>
 							<view class="collapse-content-num"><text>{{courseCount.completeCount || 0}}</text>人</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(courseCount.completeCount,courseCount.yesterdayCountCompleteCount)}}</view>
-							<view v-show="queryParam.type == 0">昨天{{courseCount.yesterdayCountCompleteCount || 0}}</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">
+								较昨天持平{{compare(courseCount.completeCount,courseCount.yesterdayCountCompleteCount)}}
+							</view>
+							<view v-show="queryParam.type == 0">昨天{{courseCount.yesterdayCountCompleteCount || 0}}
+							</view>
 						</view>
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">完播率</view>
 							<view class="collapse-content-num"><text>{{courseCount.rate || 0}}</text>%</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(courseCount.rate,courseCount.yesterdayRate)}}</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">
+								较昨天持平{{compare(courseCount.rate,courseCount.yesterdayRate)}}</view>
 							<view v-show="queryParam.type == 0">昨天{{courseCount.yesterdayRate || 0}}</view>
 						</view>
 					</view>
 				</u-collapse-item>
 				<u-collapse-item name="questions">
 					<text slot="title" class="statistics-title">答题统计</text>
-					<text slot="value" class="statistics-slot-title">{{collapseValue.includes('questions')?'收回':'展开'}}</text>
+					<text slot="value"
+						class="statistics-slot-title">{{collapseValue.includes('questions')?'收回':'展开'}}</text>
 					<view slot="right-icon">
 						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
 					</view>
@@ -66,26 +102,30 @@
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">答题人数</view>
 							<view class="collapse-content-num"><text>{{quesCount.count || 0}}</text>人</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(quesCount.count,quesCount.yesterdayCount)}}</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">
+								较昨天持平{{compare(quesCount.count,quesCount.yesterdayCount)}}</view>
 							<view v-show="queryParam.type == 0">昨天{{quesCount.yesterdayCount || 0}}</view>
 						</view>
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">正确人数</view>
 							<view class="collapse-content-num"><text>{{quesCount.completeCount || 0}}</text>人</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(quesCount.completeCount,quesCount.yesterdayCountCompleteCount)}}</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">
+								较昨天持平{{compare(quesCount.completeCount,quesCount.yesterdayCountCompleteCount)}}</view>
 							<view v-show="queryParam.type == 0">昨天{{quesCount.yesterdayCountCompleteCount || 0}}</view>
 						</view>
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">正确率</view>
 							<view class="collapse-content-num"><text>{{quesCount.rate || 0}}</text>%</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(quesCount.rate,quesCount.yesterdayRate)}}</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">
+								较昨天持平{{compare(quesCount.rate,quesCount.yesterdayRate)}}</view>
 							<view v-show="queryParam.type == 0">昨天{{quesCount.yesterdayRate || 0}}</view>
 						</view>
 					</view>
 				</u-collapse-item>
 				<u-collapse-item name="redenvelope">
 					<text slot="title" class="statistics-title">红包统计</text>
-					<text slot="value" class="statistics-slot-title">{{collapseValue.includes('redenvelope')?'收回':'展开'}}</text>
+					<text slot="value"
+						class="statistics-slot-title">{{collapseValue.includes('redenvelope')?'收回':'展开'}}</text>
 					<view slot="right-icon">
 						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
 					</view>
@@ -94,42 +134,155 @@
 							<view class="collapse-content-item">
 								<view class="collapse-content-title">答题红包数</view>
 								<view class="collapse-content-num"><text>{{redPacketCount.count || 0}}</text>个</view>
-								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(redPacketCount.count,redPacketCount.yesterdayCount)}}</view>
+								<view style="color: #1677ff;" v-show="queryParam.type == 0">
+									较昨天持平{{compare(redPacketCount.count,redPacketCount.yesterdayCount)}}</view>
 								<view v-show="queryParam.type == 0">昨天{{redPacketCount.yesterdayCount || 0}}</view>
 							</view>
 							<view class="collapse-content-item">
 								<view class="collapse-content-title">答题红包金额</view>
-								<view class="collapse-content-num"><text>{{redPacketCount.amount || '0.00'}}</text>元</view>
-								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(redPacketCount.amount,redPacketCount.yesterdayAmount,1)}}</view>
-								<view v-show="queryParam.type == 0">昨天{{redPacketCount.yesterdayAmount || '0.00'}}</view>
+								<view class="collapse-content-num"><text>{{redPacketCount.amount || '0.00'}}</text>元
+								</view>
+								<view style="color: #1677ff;" v-show="queryParam.type == 0">
+									较昨天持平{{compare(redPacketCount.amount,redPacketCount.yesterdayAmount,1)}}</view>
+								<view v-show="queryParam.type == 0">昨天{{redPacketCount.yesterdayAmount || '0.00'}}
+								</view>
 							</view>
 						</view>
 						<view class="collapse-content x-ac" style="padding: 8px 0;">
 							<view class="collapse-content-item">
 								<view class="collapse-content-title">新会员奖励数</view>
 								<view class="collapse-content-num"><text>{{redPacketCount.newCount || 0}}</text>个</view>
-								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(redPacketCount.newCount,redPacketCount.yesterdayNewCount)}}</view>
+								<view style="color: #1677ff;" v-show="queryParam.type == 0">
+									较昨天持平{{compare(redPacketCount.newCount,redPacketCount.yesterdayNewCount)}}</view>
 								<view v-show="queryParam.type == 0">昨天{{redPacketCount.yesterdayNewCount || 0}}</view>
 							</view>
 							<view class="collapse-content-item">
 								<view class="collapse-content-title">新会员奖励金额</view>
-								<view class="collapse-content-num"><text>{{redPacketCount.totalAmount || '0.00'}}</text>元</view>
-								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(redPacketCount.totalAmount,redPacketCount.yesterdayTotalAmount,1)}}</view>
-								<view v-show="queryParam.type == 0">昨天{{redPacketCount.yesterdayTotalAmount || '0.00'}}</view>
+								<view class="collapse-content-num">
+									<text>{{redPacketCount.totalAmount || '0.00'}}</text>元
+								</view>
+								<view style="color: #1677ff;" v-show="queryParam.type == 0">
+									较昨天持平{{compare(redPacketCount.totalAmount,redPacketCount.yesterdayTotalAmount,1)}}
+								</view>
+								<view v-show="queryParam.type == 0">昨天{{redPacketCount.yesterdayTotalAmount || '0.00'}}
+								</view>
 							</view>
 						</view>
 					</view>
 				</u-collapse-item>
+				<u-collapse-item name="funnel" v-if="user.userType==0">
+					<text slot="title" class="statistics-title">转化漏斗图</text>
+					<text slot="value"
+						class="statistics-slot-title">{{collapseValue.includes('funnel')?'收回':'展开'}}</text>
+					<view slot="right-icon">
+						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+					</view>
+					<view >
+						<funnelChart :getratelist="getrateimg" />
+					</view>
+				</u-collapse-item>
+				<u-collapse-item name="rank"  v-if="user.userType==0">
+					<text slot="title" class="statistics-title">排行榜</text>
+					<text slot="value"
+						class="statistics-slot-title">{{collapseValue.includes('rank')?'收回':'展开'}}</text>
+					<view slot="right-icon">
+						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+					</view>
+					<view class="flex-1">
+						<view class="mt40">
+							<view class="justify-between align-center">
+								<view class="base-color-3 bold fs32">群管排行榜</view>
+								<view class="justify-start" @click="ordergroup(1)" 
+								v-if="orderGroup=='asc'">
+									<u-icon name="list-dot" size="20"></u-icon>
+									<view>按正序(前20名)</view>
+								</view>
+								<view class="justify-start" @click="ordergroup(0)" v-else>
+									<u-icon name="list-dot" size="20"></u-icon>
+									<view>按倒序(后20名)</view>
+								</view>
+							</view>
+							<view class="centerV">
+								<u-tabs :list="list1" @click="clickTab" lineColor='#1773ff' lineWidth='40'
+									activeStyle="font-weight:bold"></u-tabs>
+							</view>
+							<view class="justify-start align-center mtb28" 
+							v-for="(item,index) in rankListA"
+							:key="index">
+								<u-avatar :src="item.avatar" size="40"></u-avatar>
+								<view class="flex-1 ml20">
+									<view class="justify-between mb16">
+										<view class="base-color-3">{{item.name}}</view>
+										<view>{{item.rate}}%</view>
+									</view>
+									<u-line-progress :percentage="30" activeColor='#1773ff'/>
+								</view>
+							</view>
+							<view v-if="rankListA.length==0" class="center mtb32">暂无数据</view>
+						</view>
+						<view class="mt60 column flex-1">
+							<view class="justify-between align-center">
+								<view class="base-color-3 bold fs32">课程排行榜</view>
+								<view class="justify-start" @click="ordergroupB(1)" 
+								v-if="orderGroupB=='asc'">
+									<u-icon name="list-dot" size="20"></u-icon>
+									<view>按正序(前20名)</view>
+								</view>
+								<view class="justify-start" @click="ordergroupB(0)" v-else>
+									<u-icon name="list-dot" size="20"></u-icon>
+									<view>按倒序(后20名)</view>
+								</view>
+							</view>
+							<view class="centerV">
+								<u-tabs :list="list1" @click="clickTabB" lineColor='#1773ff' lineWidth='40'
+									activeStyle="font-weight:bold"></u-tabs>
+							</view>
+							<view class="justify-start align-center"
+							v-for="(item,index) in rankListB"
+							:key="index">
+								<view class="flex-1 ml20">
+									<view class="justify-between mb16">
+										<view class="base-color-3">{{item.name}}</view>
+										<view>{{item.rate}}%</view>
+									</view>
+									<u-line-progress :percentage="30" activeColor='#ed0922'/>
+								</view>
+							</view>
+							<view v-if="rankListB.length==0" class="center mtb32">暂无数据</view>
+						</view>
+					</view>
+				</u-collapse-item>
 			</u-collapse>
 		</view>
 	</view>
 </template>
 
 <script>
-	import {getCompanyUserAndUserCount,getCourseCount,getQuesCount,getRecPacketCount} from "@/api/courseManage.js"
+	import code from "../../../uni_modules/uview-ui/libs/config/props/code"
+	import funnelChart from "../components/chart.vue"
+	import {
+		getCompanyUserAndUserCount,
+		getCourseCount,
+		getQuesCount,
+		getRecPacketCount,
+		getlableNum,
+		getcourseRate,
+		getGroupRanklist,
+		getCourseRanklist
+	} from "@/api/courseManage.js"
 	export default {
+		components: {
+			funnelChart,
+		},
 		data() {
 			return {
+				list1: [{
+					name: '按完播率',
+				}, {
+					name: '按正确率'
+				}],
+				orderGroup:'asc',
+				orderGroupB:'asc',
 				showCalendar: false,
 				mode: 'range',
 				typeOption: [{
@@ -149,7 +302,7 @@
 					type: 0
 				},
 				contentH: 0,
-				collapseValue:['course','questions','redenvelope'],
+				collapseValue: ['course', 'questions', 'redenvelope', 'funnel','rank'],
 				user: {},
 				todayday: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
 				startTime: '',
@@ -158,12 +311,19 @@
 				companyUserUserCount: 0,
 				courseCount: {},
 				quesCount: {},
-				redPacketCount: {}
+				redPacketCount: {},
+				collapseLable: false,
+				lablelistnum: [],
+				getrateimg: {},
+				rankListA:[],
+				rankListB:[],
+				activeA:0,
+				activeB:0
 			}
 		},
 		computed: {
 			compare() {
-				return (today,yesterday,type)=>{
+				return (today, yesterday, type) => {
 					const num = Number(yesterday || 0) - Number(today || 0)
 					return type == 1 ? num.toFixed(2) : 0
 				}
@@ -174,59 +334,190 @@
 			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
 			// this.getUserCount()
 			// this.resetDate()
+			this.getlableData()
+			this.getrateList()
+			this.getrankCourse()
+			this.getrankgroup()
 		},
 		methods: {
+			clickTab(item) {
+				this.activeA=item.index+1
+				this.getrankgroup()
+				console.log(this.activeA)
+			},
+			clickTabB(item) {
+				this.activeB=item.index+1
+				this.getrankCourse()
+				console.log(this.activeA)
+			},
+			ordergroup(item){
+				if(item==0){
+					this.orderGroup='asc'
+					this.getrankgroup()
+				}else{
+					this.orderGroup='desc'
+					this.getrankgroup()
+				}
+			},
+			ordergroupB(item){
+				if(item==0){
+					this.orderGroupB='asc'
+					this.getrankCourse()
+				}else{
+					this.orderGroupB='desc'
+					this.getrankCourse()
+				}
+			},
+			getrankCourse(){
+				//获取课程排行
+				this.startTime = this.todayday
+				this.endTime = this.todayday
+				this.resetDate()
+				const params = {
+					companyId: this.user.companyId,
+					endTime: this.endTime+' 23:59:59',
+					startTime: this.startTime+' 00:00:00',
+					order:this.orderGroupB,
+					type: this.activeB, // 0:经销商/1:群管
+				}
+				getCourseRanklist(params).then(res=>{
+					if (res.code == 200) {
+						this.rankListB = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getrankgroup(){
+				//获取群管排行
+				this.startTime = this.todayday
+				this.endTime = this.todayday
+				this.resetDate()
+				const params = {
+					companyId: this.user.companyId,
+					endTime: this.endTime+' 23:59:59',
+					startTime: this.startTime+' 00:00:00',
+					order:this.orderGroup,
+					type: this.activeA, // 0:经销商/1:群管
+				}
+				getGroupRanklist(params).then(res=>{
+					if (res.code == 200) {
+						console.log(res)
+						this.rankListA = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getrateList() {
+				//获取漏斗图
+				this.startTime = this.todayday
+				this.endTime = this.todayday
+				this.resetDate()
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					endTime: this.endTime+' 23:59:59',
+					startTime: this.startTime+' 00:00:00',
+					courseId: this.courseid,
+					videoId: this.courseids,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getcourseRate(params).then(res => {
+					if (res.code == 200) {
+						this.getrateimg = res
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getlableData() {
+				const params = {
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getlableNum(params).then(res => {
+					if (res.code == 200) {
+						this.lablelistnum = res.data
+						this.$nextTick(() => {
+							this.$refs.puyaodameinvRef.init()
+						})
+						console.log(res)
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			changelable(e) {
+				if (e[0].status = 'open') {
+					this.collapseLable = true
+				}
+			},
 			getHeight() {
 				const windowHeight = uni.getSystemInfoSync().windowHeight
 				const query = uni.createSelectorQuery().in(this);
 				query
-				  .select(".topdata")
-				  .boundingClientRect((data) => {
-					this.contentH = `calc(${windowHeight}px - ${data.height}px - 56px)`
-				  })
-				  .exec();
+					.select(".topdata")
+					.boundingClientRect((data) => {
+						this.contentH = `calc(${windowHeight}px - ${data.height}px - 56px)`
+					})
+					.exec();
 			},
 			resetDate() {
-				if(this.queryParam.type == 0) {
+				if (this.queryParam.type == 0) {
 					this.startTime = this.todayday
 					this.endTime = this.todayday
-				} else if(this.queryParam.type == 1) {
+				} else if (this.queryParam.type == 1) {
 					let yesterday = new Date();
 					yesterday.setDate(yesterday.getDate() - 1);
-					
+
 					this.startTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd')
 					this.endTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd')
-				} else if(this.queryParam.type == 2) {
+				} else if (this.queryParam.type == 2) {
 					let today = new Date();
 					let lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
-				
+
 					this.startTime = uni.$u.timeFormat(this.todayday, 'yyyy-mm') + '-01'
 					this.endTime = uni.$u.timeFormat(lastDayOfMonth, 'yyyy-mm-dd')
-				} else if(this.queryParam.type == 3) {
+				} else if (this.queryParam.type == 3) {
 					this.startTime = this.todayday
 					this.endTime = this.todayday
 				}
 				this.getCount()
 			},
 			handleType(type) {
-				if(this.queryParam.type == type) {
+				if (this.queryParam.type == type) {
 					return
 				}
 				this.queryParam.type = type
-				if(this.queryParam.type == 3) {
+				if (this.queryParam.type == 3) {
 					this.getHeight()
 				}
 				this.resetDate()
+				this.getrateList()
+				this.getrankCourse()
+				this.getrankgroup()
 			},
 			changeCollapse(e) {
-				this.collapseValue = e.filter(item=>item.status == 'open').map(it=>it.name)
+				this.collapseValue = e.filter(item => item.status == 'open').map(it => it.name)
 			},
 			closeCalendar() {
 				this.showCalendar = false
 			},
 			confirmCalendar(e) {
 				this.startTime = e[0]
-				this.endTime =  e[e.length-1]
+				this.endTime = e[e.length - 1]
 				this.showCalendar = false
 				this.getCount()
 			},
@@ -238,15 +529,15 @@
 					courseId: '',
 					endTime: day + ' 23:59:59',
 					startTime: day + ' 00:00:00',
-					type: this.user.userType=='00' ? 0 : 1, // 0:经销商/1:群管
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
 				}
-				getCompanyUserAndUserCount(param).then(res=>{
-					if(res.code==200){
+				getCompanyUserAndUserCount(param).then(res => {
+					if (res.code == 200) {
 						this.companyUserCount = res.data.companyUserCount || 0
 						this.companyUserUserCount = res.data.companyUserUserCount || 0
-					}else{
+					} else {
 						uni.showToast({
-							icon:'none',
+							icon: 'none',
 							title: res.msg,
 						});
 					}
@@ -259,43 +550,43 @@
 					courseId: '',
 					endTime: this.endTime + ' 23:59:59',
 					startTime: this.startTime + ' 00:00:00',
-					type: this.user.userType=='00' ? 0 : 1, // 0:经销商/1:群管
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
 				}
 				this.getCourseCount(param)
 				this.getQuesCount(param)
 				this.getRecPacketCount(param)
 			},
-			getCourseCount(param){
-				getCourseCount(param).then(res=>{
-					if(res.code==200){
+			getCourseCount(param) {
+				getCourseCount(param).then(res => {
+					if (res.code == 200) {
 						this.courseCount = res.data
-					}else{
+					} else {
 						uni.showToast({
-							icon:'none',
+							icon: 'none',
 							title: res.msg,
 						});
 					}
 				})
 			},
-			getQuesCount(param){
-				getQuesCount(param).then(res=>{
-					if(res.code==200){
+			getQuesCount(param) {
+				getQuesCount(param).then(res => {
+					if (res.code == 200) {
 						this.quesCount = res.data
-					}else{
+					} else {
 						uni.showToast({
-							icon:'none',
+							icon: 'none',
 							title: res.msg,
 						});
 					}
 				})
 			},
-			getRecPacketCount(param){
-				getRecPacketCount(param).then(res=>{
-					if(res.code==200){
+			getRecPacketCount(param) {
+				getRecPacketCount(param).then(res => {
+					if (res.code == 200) {
 						this.redPacketCount = res.data
-					}else{
+					} else {
 						uni.showToast({
-							icon:'none',
+							icon: 'none',
 							title: res.msg,
 						});
 					}
@@ -306,12 +597,24 @@
 </script>
 
 <style scoped lang="scss">
+	.boxlable {
+		width: 47%;
+	}
+
+	.morelable {
+		width: 100%;
+		overflow-x: auto;
+		/* 添加横向滚动条 */
+		white-space: nowrap;
+	}
+
 	.container {
 		font-family: PingFang SC, PingFang SC;
 		font-weight: 400;
 		font-size: 14px;
 		color: #222222;
 	}
+
 	.calendar-day {
 		font-family: PingFang SC, PingFang SC;
 		font-weight: 400;
@@ -322,15 +625,16 @@
 		background-color: #f5f5f5;
 		border-radius: 4px;
 	}
+
 	.topdata {
 		padding-top: 25px;
-		background-color: rgb(216, 232, 255);
+		background: linear-gradient(to right, rgba(225, 238, 255, 1), rgba(223, 224, 254, 1));
 
 		&-box {
 			padding: 12px;
 			margin: 0 12px 0 12px;
 			background-color: rgba(255, 255, 255, 0.5);
-			border-radius: 8px 8px 0 0;
+			border-radius: 8px 8px;
 			font-family: PingFang SC, PingFang SC;
 			font-weight: 400;
 			font-size: 14px;
@@ -345,6 +649,7 @@
 
 			&-num {
 				color: #1677ff;
+
 				text {
 					font-family: DIN, DIN;
 					font-weight: bold;
@@ -377,22 +682,27 @@
 			}
 		}
 	}
+
 	.statistics {
 		background-color: #fff;
 		overflow-y: auto;
 		box-sizing: border-box;
+
 		&-title {
 			font-family: PingFang SC, PingFang SC;
 			font-weight: bold;
 			font-size: 16px;
 			color: #222222;
 		}
+
 		&-slot-title {
 			font-size: 12px;
 			color: #1677ff;
 		}
+
 		.collapse-content {
 			margin: 0 -8px -8rpx 0;
+
 			&-item {
 				flex: 1;
 				padding: 12px;
@@ -405,13 +715,16 @@
 				color: #222222;
 				margin: 0 8px 8rpx 0;
 			}
+
 			&-title {
 				font-size: 14px;
 				margin-bottom: 10px;
 			}
+
 			&-num {
 				color: #1677ff;
 				font-size: 10px;
+
 				text {
 					font-family: DIN, DIN;
 					font-weight: bold;

+ 59 - 17
pages/courseManage/index.vue

@@ -1,27 +1,50 @@
 <template>
 	<view>
-		<view class="app-wrapper" :style="{height: bodyHeight}">
+		<view class="app-wrapper" :style="{height: '100vh'}">
 			<dataIndex v-if="templateView == 'dataIndex'"></dataIndex>
-			<!-- <liveIndex ref="mescrollItem1" v-show="templateView == 'liveIndex'"></liveIndex>
-			<courseIndex ref="mescrollItem2" v-show="templateView == 'courseIndex'"></courseIndex> -->
+			<liveIndex ref="mescrollItem1" v-show="templateView == 'liveIndex'"></liveIndex>
+			<operationIndex ref="mescrollItem1" :key="templateView" v-show="templateView == 'operationIndex'">
+			</operationIndex>
+			<courseIndex ref="mescrollItem2" v-show="templateView == 'courseIndex'"></courseIndex>
 			<vipIndex ref="mescrollItem3" v-show="templateView == 'vipIndex'"></vipIndex>
+			<manageIndex ref="mescrollItem3" v-show="templateView == 'manageIndex'"></manageIndex>
 		</view>
 		<view class="myTabBar x-f x-bc">
 			<view class="myTabBar-item x-f y-f" @click="handleTab('dataIndex')">
-				<image src="../../static/logo.png" mode="aspectFill"></image>
-				<text>数据</text>
+				<image src="@/static/manageTabIcon/data.png" mode="aspectFill" v-if="templateView!=='dataIndex'">
+				</image>
+				<image src="@/static/manageTabIcon/data_on.png" mode="aspectFill" v-else></image>
+				<text :class="templateView=='dataIndex'?'base-color':''">数据</text>
 			</view>
-			<view class="myTabBar-item x-f y-f" @click="handleTab('liveIndex')">
-				<image src="../../static/logo.png" mode="aspectFill"></image>
-				<text>直播课</text>
+			<view class="myTabBar-item x-f y-f" @click="handleTab('operationIndex')" v-if="user.userType==0">
+				<image src="@/static/manageTabIcon/liveclasses.png" mode="aspectFill"
+					v-if="templateView!=='operationIndex'"></image>
+				<image src="@/static/manageTabIcon/liveclasses_on.png" mode="aspectFill" v-else></image>
+				<text :class="templateView=='operationIndex'?'base-color':''">运营</text>
+			</view>
+			<view class="myTabBar-item x-f y-f" @click="handleTab('liveIndex')" v-if="user.userType==1">
+				<image src="@/static/manageTabIcon/liveclasses.png" mode="aspectFill" v-if="templateView!=='liveIndex'">
+				</image>
+				<image src="@/static/manageTabIcon/liveclasses_on.png" mode="aspectFill" v-else></image>
+				<text :class="templateView=='liveIndex'?'base-color':''">直播课</text>
 			</view>
 			<view class="myTabBar-item x-f y-f" @click="handleTab('courseIndex')">
-				<image src="../../static/logo.png" mode="aspectFill"></image>
-				<text>训练营</text>
+				<image src="@/static/manageTabIcon/training.png" mode="aspectFill" v-if="templateView!=='courseIndex'">
+				</image>
+				<image src="@/static/manageTabIcon/training_on.png" mode="aspectFill" v-else></image>
+				<text :class="templateView=='courseIndex'?'base-color':''" v-if="user.userType==1">训练营</text>
+				<text :class="templateView=='courseIndex'?'base-color':''" v-else>课程库</text>
 			</view>
 			<view class="myTabBar-item x-f y-f" @click="handleTab('vipIndex')">
-				<image src="../../static/logo.png" mode="aspectFill"></image>
-				<text>会员</text>
+				<image src="@/static/manageTabIcon/vip.png" mode="aspectFill" v-if="templateView!=='vipIndex'"></image>
+				<image src="@/static/manageTabIcon/vip_on.png" mode="aspectFill" v-else></image>
+				<text :class="templateView=='vipIndex'?'base-color':''">会员</text>
+			</view>
+			<view class="myTabBar-item x-f y-f" @click="handleTab('manageIndex')" v-if="user.userType==0">
+				<image src="@/static/manageTabIcon/manage.png" mode="aspectFill" v-if="templateView!=='manageIndex'">
+				</image>
+				<image src="@/static/manageTabIcon/manage_on.png" mode="aspectFill" v-else></image>
+				<text :class="templateView=='manageIndex'?'base-color':''">管理</text>
 			</view>
 		</view>
 	</view>
@@ -29,17 +52,22 @@
 
 <script>
 	import MescrollCompMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js";
+	import MescrollMoreMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more.js";
 	import dataIndex from "./dataIndex/index.vue"
 	import liveIndex from "./live/index.vue"
 	import courseIndex from "./course/index.vue"
 	import vipIndex from "./vip/index.vue"
+	import operationIndex from './operation/index.vue'
+	import manageIndex from './manage/index.vue'
 	export default {
-		mixins: [MescrollCompMixin],
+		mixins: [ MescrollCompMixin,MescrollMoreMixin], //mescroll-body写在子组件时
 		components: {
 			dataIndex,
 			liveIndex,
 			courseIndex,
-			vipIndex
+			vipIndex,
+			operationIndex,
+			manageIndex
 		},
 		data() {
 			return {
@@ -47,18 +75,29 @@
 				statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
 				windowHeight: uni.getSystemInfoSync().windowHeight,
 				bodyHeight: 0,
-				tabIndex: 0
+				tabIndex: 0,
+				user: []
 			}
 		},
 		onLoad() {
-			this.bodyHeight = `calc(${this.windowHeight}px - 56px)` 
+			console.log(this.windowHeight);
+			this.bodyHeight = `calc(${this.windowHeight}px - 56px)`
 			uni.setNavigationBarTitle({
 				title: '数据'
 			});
 		},
+		mounted() {
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			if (this.user.userType == 0) {
+				this.templateView = 'manageIndex'
+			} else {
+				this.templateView = 'vipIndex'
+			}
+		},
 		methods: {
 			handleTab(tem) {
 				this.templateView = tem
+				// console.log(tem)
 			}
 		}
 	}
@@ -68,8 +107,9 @@
 	.app-wrapper {
 		width: 100%;
 	}
+
 	.myTabBar {
-		height: 56px;
+		height: 64px;
 		border-top: 1px solid #f5f5f5;
 		box-sizing: border-box;
 		width: 100%;
@@ -78,11 +118,13 @@
 		bottom: var(--window-bottom);
 		left: 0;
 		font-size: 10px;
+
 		image {
 			width: 24px;
 			height: 24px;
 			margin-bottom: 3px;
 		}
+
 		&-item {
 			flex: 1;
 		}

+ 158 - 77
pages/courseManage/live/index.vue

@@ -1,7 +1,7 @@
 <template>
 	<view class="container">
 		<view class="headbox">
-			<view class="headnav x-bc">
+			<!-- <view class="headnav x-bc">
 				<view :class="activeTab == 0 ? 'headnav-item headnav-active':'headnav-item'" @click="handleNav(0)">
 					<view>直播计划</view>
 					<view class="headnav-num">3</view>
@@ -17,7 +17,7 @@
 					<view class="headnav-num">3</view>
 					<image src="@/static/images/finished.png" mode="aspectFill"></image>
 				</view>
-			</view>
+			</view> -->
 			<!-- <view class="x-bc">
 				<view :class="searchbarNav == index ? 'searchbar x-c searchbar-active':'searchbar x-c'" v-for="(item,index) in searchbar" :key="index" @click="clickSearchbar(index)">
 					<text>{{item.name}}</text><u-icon class="arrow-down" name="arrow-down" :color="searchbarNav == index ?'#1677ff':'#999'" size="12"></u-icon>
@@ -28,30 +28,40 @@
 					<view v-if="searchbarNav == 0">
 						<view class="boxnav x-bc">
 							<view class="boxnav-item" v-for="(item,index) in courseList" :key="index">
-								<view class="boxnav-item-info one-t" :class="courserIndex == item.courseId ? 'boxnav-active':''" @click="handleCourse(item)">{{item.courseName}}</view>
+								<view class="boxnav-item-info one-t"
+									:class="courserIndex == item.courseId ? 'boxnav-active':''"
+									@click="handleCourse(item)">{{item.courseName}}</view>
 							</view>
 						</view>
 					</view>
 				</dropdownPanel>
 			</view>
 		</view>
-		<view class="coursebox" :style="{height: contentH}">
-			<mescroll-body  bottom="0" ref="mescrollRef" @init="mescrollInit" :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
-				<view class="courselist">
-					<courseItem :activeTab="activeTab" v-for="(item,index) in dataList" :key="index" :info="item" />
+		<view class="container-right" >
+			<scroll-view style="height:100%" :scroll-y="true" :refresher-enabled="true" :refresher-triggered="triggered"
+				refresher-background="rgba(0,0,0,0)" @refresherrefresh="pullDownRefresh"
+				@refresherrestore="triggered = false" :upper-threshold="100" :lower-threshold="100"
+				@refresherabort="triggered = false" @scrolltolower="reachBottom">
+				<view class="list">
+					<courseItem :from="'course'" :activeTab="1" v-for="(item,index) in dataList" :key="index"
+						:info="item" />
+					<u-loadmore :status="loadStatus" />
 				</view>
-			</mescroll-body>
+			</scroll-view>
 		</view>
 	</view>
 </template>
 
 <script>
 	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
-	import { getFsCourseList,getCourseVdieoList } from "@/api/courseManage.js"
-	import dropdownPanel  from "../components/dropdownPanel.vue"
+	import {
+		getFsCourseList,
+		getCourseVdieoList
+	} from "@/api/courseManage.js"
+	import dropdownPanel from "../components/dropdownPanel.vue"
 	import courseItem from "../components/courseItem.vue"
 	export default {
-		mixins: [MescrollMixin], 
+		mixins: [MescrollMixin],
 		components: {
 			courseItem,
 			dropdownPanel
@@ -59,15 +69,16 @@
 		data() {
 			return {
 				user: {},
-				filterData:[{
-					name: '训练营-营期',
-					value: 0,
-				},
-				{
-					name: '课程状态',
-					value: 1,
-				}],
-				defaultIndex:[0,0],
+				filterData: [{
+						name: '训练营-营期',
+						value: 0,
+					},
+					// {
+					// 	name: '课程状态',
+					// 	value: 1,
+					// },
+				],
+				defaultIndex: [0, 0],
 				contentH: 0,
 				activeTab: 1,
 				courseList: [],
@@ -75,18 +86,19 @@
 				searchbarNav: 0,
 				courserIndex: 0,
 				searchbar: [{
-					name: '训练营-营期'
-				},
-				{
-					name: '课程状态'
-				}],
-				mescroll:null,
+						name: '训练营-营期'
+					},
+					{
+						name: '课程状态'
+					}
+				],
+				mescroll: null,
 				downOption: {
-					use:true,
+					use: true,
 					auto: false
 				},
 				upOption: {
-					onScroll:false,
+					onScroll: false,
 					use: true, // 是否启用上拉加载; 默认true
 					auto: true,
 					page: {
@@ -94,13 +106,19 @@
 						size: 10 // 每页数据的数量,默认10
 					},
 					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
-					textNoMore:"已经到底了",
+					textNoMore: "已经到底了",
 					empty: {
-						icon:'https://cos.his.cdwjyyh.com/fs/20240423/cf4a86b913a04341bb44e34bb4d37aa2.png',
+						icon: 'https://cos.his.cdwjyyh.com/fs/20240423/cf4a86b913a04341bb44e34bb4d37aa2.png',
 						tip: '暂无数据'
 					}
 				},
 				dataList: [],
+				params: {
+					pageNum: 1,
+					pageSize: 10
+				},
+				triggered: false,
+				loadStatus: 'loadmore',
 			}
 		},
 		mounted() {
@@ -113,33 +131,38 @@
 			onChange(index) {
 				this.searchbarNav = index
 			},
-			confirm() {
-				this.courseId = this.courserIndex
-				this.mescroll.resetUpScroll()
-			},
 			reset() {
 				this.courserIndex = ''
 			},
 			handleCourse(item) {
 				this.courserIndex = item.courseId
 			},
+			confirm() {
+				this.courseId = this.courserIndex
+				console.log(this.courseId)
+				this.getFsCourseList()
+			},
 			// 训练营
 			getFsCourseList() {
 				const day = uni.$u.timeFormat(new Date(), 'yyyy-mm-dd')
 				const param = {
 					companyId: this.user.companyId,
 					companyUserId: this.user.userId,
-					type: this.user.userType=='00' ? 0 : 1, // 0:经销商/1:群管
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
 				}
-				getFsCourseList(param).then(res=>{
-					if(res.code==200){
+				getFsCourseList(param).then(res => {
+					if (res.code == 200) {
 						this.courseList = res.data || []
-						// this.courseId = this.courseList && this.courseList.length > 0 ? this.courseList[0].courseId : ''
-						// this.searchbar[0].name = this.courseList && this.courseList.length > 0 ? this.courseList[0].courseName : '训练营-营期'
+						// this.courseId = this.courseList && this.courseList.length > 0 ? this.courseList[0]
+						// 	.courseId : ''
+						// this.searchbar[0].name = this.courseList && this.courseList.length > 0 ? this.courseList[0]
+						// 	.courseName : '训练营-营期'
+							console.log(this.courseId)
 						// this.mescroll.resetUpScroll()
-					}else{
+						this.getListInit()
+					} else {
 						uni.showToast({
-							icon:'none',
+							icon: 'none',
 							title: res.msg,
 						});
 					}
@@ -154,45 +177,71 @@
 			mescrollInit(mescroll) {
 				this.mescroll = mescroll;
 			},
-			/*下拉刷新的回调 */
-			downCallback(mescroll) {
-				mescroll.resetUpScroll()
+			getListInit() {
+				this.params.pageNum = 1
+				this.getListData('refresh')
 			},
-			upCallback(page) {
-				//联网加载数据
-				var that = this;
-				var data={
-					courseId:this.courseId,
-					status: '',
-					pageNum: page.num,
-					pageSize: page.size
-				};
+			async getListData(type = 'refresh') {
 				uni.showLoading({
-					title:"加载中..."
+					title: "加载中..."
 				})
-				getCourseVdieoList(data).then(res => {
-					uni.hideLoading()
-					if(res.code==200){
-						//设置列表数据
-						if (page.num == 1) {
-							that.dataList = res.data.list; 
-							
-						} else {
-							that.dataList = that.dataList.concat(res.data.list);
-							 
-						}
-						that.mescroll.endBySize(res.data.list.length, res.data.total);
-						
-					}else{
-						uni.showToast({
-							icon:'none',
-							title: "请求失败",
-						});
-						that.dataList = null;
-						that.mescroll.endErr();
+				this.loadStatus = 'loading'
+				const result = await getCourseVdieoList({
+					courseId: this.courseId,
+					status: '',
+					...this.params
+				})
+				if (result) {
+					const {
+						isLastPage,
+						total,
+						list,
+					} = result.data
+					if (type == 'refresh') {
+						this.dataList = list
+					} else {
+						this.dataList = [...this.dataList, ...list]
+					}
+					if (isLastPage) {
+						this.loadStatus = 'nomore';
+					} else {
+						this.loadStatus = 'loadmore';
 					}
-				});
+					uni.hideLoading()
+				} else {
+					uni.showToast({
+						icon: 'none',
+						title: "请求失败",
+					});
+					this.dataList = []
+				}
+			},
+			/**
+			 * 触底添加下一页
+			 */
+			reachBottom(options) {
+				if (this.loadStatus === 'loadmore') {
+					this.loadStatus = 'loading'
+					uni.showNavigationBarLoading()
+					setTimeout(() => {
+						this.params.pageNum += 1;
+						this.getListData('more')
+						uni.hideNavigationBarLoading()
+					}, 500);
+				}
 			},
+			/**
+			 * 下拉列表页
+			 */
+			pullDownRefresh(options) {
+				this.triggered = true;
+				setTimeout(() => {
+					this.triggered = false;
+					uni.stopPullDownRefresh()
+					this.params.pageNum = 1;
+					this.getListData('refresh')
+				}, 500)
+			}
 		}
 	}
 </script>
@@ -203,14 +252,19 @@
 		font-weight: 400;
 		font-size: 14px;
 		color: #222;
+		display: flex;
+		flex-direction: column;
 	}
+
 	.boxnav {
 		flex-wrap: wrap;
 		padding: 0 0 0 10px;
+
 		&-item {
 			width: 50%;
 			overflow: hidden;
 		}
+
 		&-item-info {
 			border: 1px solid #f5f5f5;
 			text-align: center;
@@ -220,19 +274,23 @@
 			padding: 5px;
 			margin: 0 10px 10px 0;
 		}
+
 		&-active {
 			border: 1px solid #1677ff !important;
 			color: #1677ff !important;
 			background-color: #e7f1fe !important;
 		}
 	}
+
 	.headbox {
 		background-color: #fff;
 	}
+
 	.headnav {
 		padding: 15px 12px;
 		margin: 0 -10px -10px 0;
 		box-sizing: border-box;
+
 		image {
 			height: 60px;
 			width: 50px;
@@ -242,6 +300,7 @@
 			right: 0;
 			display: none;
 		}
+
 		&-item {
 			flex: 1;
 			font-size: 16px;
@@ -254,15 +313,19 @@
 			overflow: hidden;
 			color: #555;
 		}
+
 		&-active {
-			background-color:  rgb(231, 241, 255) !important;
+			background-color: rgb(231, 241, 255) !important;
+
 			.headnav-num {
 				color: #1677ff !important;
 			}
+
 			image {
 				display: block !important;
 			}
 		}
+
 		&-num {
 			font-family: DIN, DIN;
 			font-weight: bold;
@@ -270,27 +333,45 @@
 			margin: 5px 0;
 		}
 	}
+
 	.searchbar {
 		flex: 1;
 		padding-bottom: 10px;
+
 		.arrow-down {
 			margin-left: 5px;
 		}
+
 		&-active {
 			color: #1677ff !important;
+
 			.arrow-down {
 				transform: rotate(180deg);
 			}
 		}
 	}
+
 	.coursebox {
 		position: relative;
 		overflow-y: auto;
 		box-sizing: border-box;
 	}
+
 	.courselist {
 		padding: 12px;
 		box-sizing: border-box;
-		
+
+	}
+
+	.container-right {
+		flex: 1;
+		height: calc(100% - 80rpx);
+		overflow-y: scroll;
+		padding-bottom: 80rpx;
+		.list {
+			padding: 10px;
+			box-sizing: border-box;
+			width: 100%;
+		}
 	}
 </style>

+ 342 - 0
pages/courseManage/manage/changeVip.vue

@@ -0,0 +1,342 @@
+<template>
+	<view class="column flex-1 hb">
+		<view class="p20 justify-between" > 
+			<view class="column base-bg-f8 p20 mr20 top-item" v-for="(item,index) in list" :key="index"
+			style="width: 50%;" @click="change(index)" :class="active==index?'top-itemActive':''">
+				<text class="fs28 bold mb4">{{item.name}}</text>
+				<text class="fs24 base-color-6">{{item.text}}</text>
+			</view>
+		</view>
+		<view class="pl20 " v-if="active==0">
+			<view class="mb20 fs28">选择接收群管</view>
+			<view class="base-bg-f8 p10 justify-between align-center mr20 pl20">
+				<text class="mr20">接收群管</text>
+				<u-input placeholder="请选择接收群管" @focus="selsome" border="none"
+				v-model="username"></u-input>
+			</view>
+			<view class='fs24 mtb12 base-color base-bg-sure mr20 pl20 ptb4'>
+				如更换全部会员,则包含小黑屋会员</view>
+				
+		</view>
+		<view v-else class="pl20 ">
+			<view class="mb20 fs28">选择会员</view>
+			<view class="base-bg-f8 p10 justify-between align-center mr20 pl20">
+				<text class="mr20">标签</text>
+				<u-input placeholder="请选择标签" border="none" @focus='changelabel'
+				v-model="usertag"></u-input>
+			</view>
+			<view class="base-bg-f8 p10 justify-between align-center mr20 pl20 mt20">
+				<text class="mr20">选择会员</text>
+				<u-input placeholder="请选择会员" border="none" @focus='changelabelvip'
+				v-model="tagsuser"></u-input>
+			</view>
+			<view class="mb20 fs28 mt20">选择接收群管</view>
+			<view class="base-bg-f8 p10 justify-between align-center mr20 pl20">
+				<text class="mr20">接收群管</text>
+				<u-input placeholder="请选择接收群管" border="none" @focus='changeuser'
+				v-model="username"></u-input>
+			</view>
+		</view>
+		<view class="foot-box justify-around">
+			<!-- <view class="box" style="border: 2rpx solid #eee;">取消</view> -->
+			<view class="box base-bg-blue colorf" @click="sure">确定</view>
+		</view>
+		<u-picker :show="show" :columns="columns" @cancel='show=!show' 
+		@confirm='receiveA' keyName="nickName"
+		></u-picker>
+		<u-picker :show="showa" :columns="columnsa" @cancel='showa=!showa' 
+		@confirm='receiveB' keyName="tag"
+		 ></u-picker>
+		<u-picker :show="showb" :columns="columnsb" @cancel='showb=!showb' 
+		@confirm='receiveC' keyName="nickName"
+		></u-picker>
+		<u-popup :show="showvip" @close="close" @open="open" >
+			<view class="p20 h400">
+				<view class="justify-between mb20" style="flex-direction: row-reverse">
+					<u-icon name="close-circle" color="#ccc" size="28" 
+					@click="showvip=!showvip"></u-icon>
+				</view>
+				<view class="justify-start wrap">
+					<view class="p16 mlr12" v-for="(item,index) in tagsuserlist" :key="index">
+						<u-tag :text="item.nickName" :plain="!item.checked" type="primary" :name="index"
+								@click="checkboxClick">
+						</u-tag>
+					</view>
+				</view>
+				<view class="centerV">
+					<view @click="getuserid" class="surebtn">确定</view>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	import {
+		getgroupList,
+		changevipUser,
+		getcompanyTaglist,
+		changevipUserAll,
+		gettagsUser,
+	} from "@/api/courseManage.js"
+	export default {
+		data() {
+			return {
+				list:[{
+					name:'更换全部会员归属',
+					text:'一键更换全部会员归属'
+				},{
+					name:'更换部分会员归属',
+					text:'通过筛选,定位会员更换归属'
+				}],
+				active:0,
+				show:false,
+				columns:[],
+				columnsa:[],
+				columnsb:[],
+				border:false,
+				groupList:[],
+				showa:false,
+				showb:false,
+				userid:0,
+				username:'',
+				usertag:'',
+				tagsid:'',
+				showvip:false,
+				groupid:0,
+				tagsuser:'',
+				tagsuserlist:[],
+				user:[],
+				usertagsid:[]
+			}
+		},
+		onLoad(option) {
+			this.groupid=option.id
+			console.log(option)
+		},
+		mounted() {
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+		},
+		methods: {
+			checkboxClick(name){
+				console.log(name)
+				this.tagsuserlist[name].checked = !this.tagsuserlist[name].checked
+				console.log(this.tagsuser)
+			},
+			getuserid(){
+				this.tagsuser = this.tagsuserlist.filter(item => item.checked)
+				.map(v => v.nickName).join(',')
+				this.usertagsid = this.tagsuserlist.filter(item => item.checked)
+				.map(v => v.userId)
+				this.showvip=!this.showvip
+			},
+			close(){
+				this.showvip=!this.showvip
+			},
+			open(){
+				
+			},
+			changelabelvip(){
+				if(this.tagsid==''){
+					uni.showToast({
+						icon: 'none',
+						title: "请先选择标签"
+					})
+				}else{
+					this.showvip=!this.showvip
+					this.gettagUserlist()
+				}
+			},
+			gettagUserlist(){
+				const param={
+					companyId:this.user.companyId,
+					// companyUserId:this.groupid,
+					tagIds:this.tagsid,
+					companyUserId:this.userid
+				}
+				gettagsUser(param).then(res=>{
+					if(res.code==200){
+						this.tagsuserlist=res.data.map(item => {
+							return {
+								...item,
+								checked: false,
+							}
+						})
+						console.log(this.tagsuserlist)
+					}
+				})
+			},
+			changesomevip(){
+				//更换部分会员归属
+				const param={
+					companyId:this.user.companyId,
+					companyUserId:this.groupid,
+					tagIds:this.tagsid,
+					userId:this.usertagsid
+				}
+				changevipUser(param).then(res=>{
+					if(res.code==200){
+						uni.showToast({
+							icon: 'none',
+							title: '更换会员归属成功'
+						})
+					}else{
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			changeall(){
+				//更换全部会员
+				const param={
+					newCompanyUserId:this.userid,
+					oldCompanyUserId:this.groupid
+				}
+				changevipUserAll(param).then(res=>{
+					if(res.code==200){
+						uni.showToast({
+							icon: 'none',
+							title: '更换会员归属成功'
+						})
+					}else{
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			sure(){
+				if(this.active==0){
+					if(this.userid==''){
+						uni.showToast({
+							icon: 'none',
+							title: '请选择接收群管'
+						})
+					}else{
+						this.changeall()
+					}
+				}else{
+					if(this.tagsid==''){
+						uni.showToast({
+							icon: 'none',
+							title: '请选择标签'
+						})
+					}else if(this.usertagsid==''){
+						uni.showToast({
+							icon: 'none',
+							title: '请选择会员'
+						})
+					}else{
+						this.changesomevip()
+					}
+				}
+			},
+			receiveC(e){
+				this.username=e.value[0].nickName
+				this.userid=e.value[0].userId
+				this.showb=!this.showb
+			},
+			receiveB(e){
+				this.tagsid=e.value[0].tagId
+				this.usertag=e.value[0].tag
+				this.showa=!this.showa
+			},
+			receiveA(e){
+				this.username=e.value[0].nickName
+				this.userid=e.value[0].userId
+				this.show=!this.show
+			},
+			change(index){
+				this.active=index
+			},
+			selsome(){
+				console.log(12)
+				this.show=true
+				this.getgrouplist()
+			},
+			changelabel(){
+				this.tagsuser=''
+				this.showa=true
+				this.gettaglist()
+			},
+			changeuser(){
+				this.showb=true
+				this.getgrouplist()
+			},
+			gettaglist(){
+				getcompanyTaglist().then(res=>{
+					if(res.code==200){
+						this.columnsa=[res.data]
+					}else{
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getgrouplist(){
+				//获取群管列表
+				getgroupList().then(res=>{
+					if(res.code==200){
+						this.columns=[res.data]
+						this.columnsb=[res.data]
+						console.log(this.columns)
+					}else{
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+page{
+	background-color: #fff;
+}
+.top-itemActive{
+	color: #1773ff;
+	border-bottom: 4rpx solid #1773ff;
+	background-color: rgba(232,242,254,1);
+	text{
+		color: #1773ff !important;
+	}
+}
+.foot-box{
+	position: fixed;
+	bottom: 0;
+	width: 100%;
+	padding: 20rpx;
+}
+.box{
+	width:70%;
+	text-align: center;
+	padding: 16rpx 0;
+	border-radius: 50rpx;
+	font-size: 28rpx;
+}
+.box-list{
+	width: fit-content;
+	background-color: #eee;
+	border-radius: 10rpx;
+	font-size: 28rpx;
+}
+.surebtn{
+	position: absolute;
+	bottom: 0;
+	margin-bottom: 20rpx;
+	background-color: #1773ff;
+	color: #fff;
+	width: 80%;
+	text-align: center;
+	padding: 10rpx 0;
+	border-radius: 30rpx;
+}
+</style>

+ 47 - 0
pages/courseManage/manage/exprotList.vue

@@ -0,0 +1,47 @@
+<template>
+	<view class="column flex-1 hb">
+		<view class="p20">
+			<view class="fs28 base-color-6 justify-between">
+				<view>今日</view>
+				<view class="justify-start align-center base-color">
+					<image src="../../../static/manergevip/Refresh.png"
+					 class="w30 h30" ></image>
+					 <text class="ml8">刷新</text>
+				</view>
+			</view>
+			<view class="justify-start mtb20 pb20" style="border-bottom: #eee solid 2rpx;">
+				<image src="../../../static/images/send.png" class="w60 h60"></image>
+				<view class="ml20">
+					<view class="fs28">数据表格名称</view>
+					<view class="justify-start fs24 base-color-6">
+						<view>0MB</view>
+						<view class="plr12 mlr12" 
+						style="border-right: 2rpx solid #ddd;border-left: 2rpx solid #ddd">2024-01-26 16:37:30</view>
+						<view>已过期</view>
+					</view>
+				</view>
+			</view>
+			<view class="fs28 base-color-6">昨日</view>
+			<view class="fs28 base-color-6">以往</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+			}
+		},
+		methods: {
+			
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	page{
+		background-color: #fff;
+	}
+
+</style>

+ 449 - 0
pages/courseManage/manage/index.vue

@@ -0,0 +1,449 @@
+<template>
+	<view class="column flex-1 hb">
+		<view class="header-box ptb32 pl20">
+			<view class="justify-between align-center">
+				<view class="justify-start">
+					<u-avatar :src="user.avatar" size="50"></u-avatar>
+					<view class="column colorf ml16">
+						<text>{{user.userName}}</text>
+						<text class="mt20 fs24">ID:{{user.companyId}}</text>
+					</view>
+				</view>
+				<view class="column justify-between mr4 ">
+					<!-- <view class="box-btn base-color" @click="exportlist">导出列表</view> -->
+					<view class="box-btn base-color" @click="lableSetup">标签设置</view>
+				</view>
+			</view>
+			<view class="base-bg-f8 mr4 radius20 p32 mt32 justify-start pb32">
+				<view class="flex-1 pb20" @click="userdataDetail">
+					<view class="bold">账户可用余额</view>
+					<view class="bold fs48 base-color">{{balanceuserDay.balance}}</view>
+				</view>
+				<view class="flex-1 pb20 pl40" style="border-left:4rpx solid #0cb2ff;" @click="showday=!showday">
+					<view class="bold"><text class="base-color">
+							{{PickerName}}</text>已发金额</view>
+					<view class="bold fs48 base-color">{{balanceuserDay.todayMoney}}</view>
+				</view>
+			</view>
+		</view>
+		<view class="bgf centerV" style="border-radius: 10rpx 10rpx 0 0; margin-top: -8rpx;">
+			<u-tabs :list="list1" @click="clickTab" lineColor='#1773ff' lineWidth='40'
+				activeStyle="font-weight:bold"></u-tabs>
+		</view>
+		<view class="column flex-1 scrolly">
+			<scroll-view scroll-y="true" class="hb" :refresher-enabled="isEnabled" :refresher-triggered="triggered"
+				refresher-background="rgba(0,0,0,0)" @refresherrefresh="pullDownRefresh"
+				@refresherrestore="triggered = false" :upper-threshold="100" :lower-threshold="100"
+				@refresherabort="triggered = false" @scrolltolower="reachBottom">
+				<view class="mtb20 ml20 mr8 p20 bgf radius20" v-for="(item,index) in redpacklist" :key="index"
+					@click="managedetail(item.userId,item)">
+					<view class="justify-start align-center">
+						<u-avatar :src="item.avatar" size="50"></u-avatar>
+						<view class="ml20" @click.passive.stop>
+							<view class="justify-start align-center">
+								<text class="fs28 bold">{{item.nickName}}</text>
+								<image class="w40 h40" src="@/static/images/copy_icon.png" 
+								@click="copyId(item.userId)" mode="aspectFill">
+							</view>
+							<view class="fs24 base-color-6">id:{{item.userId}}</view>
+						</view>
+					</view>
+					<view class="justify-around mt32">
+						<view class="column align-center">
+							<view class="fs24 base-color-3">今日观看人数</view>
+							<view class="base-color fs28">
+								<text class="bold fs40 base-color">{{item.count}}</text>人
+							</view>
+						</view>
+						<view class="column align-center">
+							<view class="fs24 base-color-3">今日完播人数</view>
+							<view class="base-color fs28">
+								<text class="bold fs40 base-color">{{item.completeCount}}</text>人
+							</view>
+						</view>
+						<view class="column align-center">
+							<view class="fs24 base-color-3">今日完播率</view>
+							<view class="base-color fs28">
+								<text class="bold fs40 base-color">{{item.rate.toFixed(2)}}</text>%
+							</view>
+						</view>
+						<view class="column align-center">
+							<view class="fs24 base-color-3">今日红包金额</view>
+							<view class="base-color fs28">
+								<text class="bold fs40 base-color">{{item.amount}}</text>元
+							</view>
+						</view>
+					</view>
+					<view class="base-bg-f8 pl20">-</view>
+					<view class="justify-between fs24 mt20 align-center"  @click.passive.stop>
+						<view >
+							<text @click="showmoreA(item)">更多</text></view>
+						<view @click.passive.stop>
+							<view class="ptb16 plr32 base-bg-false radius50 base-color-red fs28 bold"
+								@click="changevipDetail(item.userId)">更换会员归属</view>
+						</view>
+						
+					</view>
+	
+				</view>
+				<u-loadmore :status="status" />
+				<view class="ptb80"></view>
+			</scroll-view>
+		</view>
+		<!-- <image class="invite-member" src="@/static/images/invite-member-icon.png" mode="aspectFill" @click="vipInvite">
+		</image> -->
+		<!-- 不要写在v-for里面 点击存到data里面-->
+		<view class="">
+			<u-popup :show="showmore" @close="closemore" @open="openmore" >
+				<view class="column align-center">
+					<view class="m20" v-for="(items,index) in morelist"
+					 :key="items.value" @click="nameMore(items)">
+						{{items.name}}</view>
+					<u-modal :show="showlist" :title="titlelist" @confirm="confirmchange" >
+						<view class="slot-content">
+							<u-input :placeholder="contpl" v-model="changelist" v-if="selnum==1"></u-input>
+							<u-input :placeholder="contpl" v-model="changephone" v-if="selnum==2"></u-input>
+							<u-input :placeholder="contpl" v-model="changeremark" v-if="selnum==3"></u-input>
+						</view>
+					</u-modal>
+				</view>
+			</u-popup>
+		</view>
+		<u-picker :show="showday" :columns="columns" keyName="label" style="flex:0;" @cancel="cancel"
+			@confirm='confirm'></u-picker>
+	</view>
+</template>
+
+<script>
+	import {
+		getusergroup,
+		getuserbalance,
+		updategroupinfo
+	} from "@/api/courseManage.js"
+	export default {
+		data() {
+			return {
+				list1: [{
+						name: '群管理员(0)',
+					},
+					// {
+					// 	name: '待审群管(0)'
+					// }, {
+					// 	name: '已拒绝(0)'
+					// }
+				],
+				user: [],
+				showmore:false,
+				showlist:false,
+				contpl:'',
+				changelist:'',
+				changephone:'',
+				changeremark:'',
+				titlelist:'',
+				selnum:0,
+				showday: false,
+				columns: [
+					[{
+							label: '今日',
+							id: 1
+						},
+						{
+							label: '昨日',
+							id: 2
+						},
+						{
+							label: '近七日',
+							id: 3
+						},
+						{
+							label: '本月',
+							id: 4
+						},
+					],
+				],
+				redpacklist: [],
+				triggered: false,
+				status: 'loadmore',
+				isEnabled: true,
+				pageNum: 1,
+				pageSize: 5,
+				startTime: '',
+				endTime: '',
+				balanceuserDay: [],
+				pickered: '',
+				todayday: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
+				PickerName: "今日",
+				morelist:[
+					{
+						name:'复制ID',
+						value:0,
+						text:''
+					},
+					{
+						name:'改姓名',
+						value:1,
+						text:'请输入姓名'
+					},
+					{
+						name:'改手机号码',
+						value:2,
+						text:'请输入手机号码'
+					},
+					{
+						name:'改备注',
+						value:3,
+						text:'请输入备注'
+					}
+				],
+				openData:{}
+			}
+		},
+		mounted() {
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			this.getredlist()
+			this.getuserbalancelist()
+		},
+		methods: {
+			showmoreA(data){
+				this.openData=data
+				this.groupid=data.userId,
+				this.showmore=!this.showmore
+			},
+			closemore(){
+				this.showmore=false
+			},
+			openmore(){
+				
+			},
+			updatagroup(){
+				console.log( Number(this.groupid))
+				const param={
+					nickName:this.changelist,
+					phonenumber:this.changephone,
+					remark:this.changeremark,
+					userId: this.groupid
+				}
+				updategroupinfo(param).then(res=>{
+					if(res.code==200){
+						uni.showToast({
+							icon: 'none',
+							title: "修改成功"
+						})
+					}else{
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			nameMore(item){
+				console.log(item)
+				this.titlelist=item.name
+				this.contpl=item.text
+				this.selnum=item.value
+				if(item.value==0){
+					uni.setClipboardData({
+						data: String(this.openData.userId),
+						success: () => {
+						  uni.showToast({
+							title: '复制成功',
+							icon: 'none',
+							duration: 2000
+						  });
+						  },
+					})
+				}else if(item.value==1){
+					this.changelist=this.openData.nickName
+					this.showlist=true
+				}else if(item.value==2){
+					console.log(this.openData.phonenumber)
+					this.changephone=this.openData.phonenumber
+					this.showlist=true
+				}else{
+					this.changeremark=this.openData.remark
+					this.showlist=true
+				}
+			},
+			confirmchange(){
+				this.updatagroup()
+				this.showlist=false
+				setTimeout(()=>{
+					this.showmore=false
+					this.getredlist()
+					console.log(this.getredlist())
+				},200)
+			},
+			copyId(id) {
+				uni.setClipboardData({
+					data: String(id),
+					success: () => {
+					  uni.showToast({
+						title: '复制成功',
+						icon: 'none',
+						duration: 2000
+					  });
+					  },
+				})
+			},
+			getuserbalancelist() {
+				const params = {
+					companyId: this.user.companyId,
+					endTime: this.endTime,
+					startTime: this.startTime,
+				}
+				getuserbalance(params).then(res => {
+					if (res.code == 200) {
+						this.balanceuserDay = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			pullDownRefresh() {
+				// 下拉刷新
+				this.triggered = true; //下拉了状态为true
+				setTimeout(() => {
+					this.triggered = false;
+					uni.stopPullDownRefresh()
+					this.pageNum = 1;
+					this.getredlist('refresh') //触底  不穿执行else
+					// 请求接口里面需要判断是不是最后一页   是最后一页 status赋值为‘loadmore’没有更多了
+					// 请求接口
+				}, 1000)
+			},
+			reachBottom() {
+				// status这个是加载状态
+				console.log(111);
+				if (this.status === 'loadmore') {
+					this.status = 'loading'
+					uni.showNavigationBarLoading()
+					setTimeout(() => {
+						this.pageNum++
+						this.getredlist() //触底  不穿执行else
+						uni.hideNavigationBarLoading()
+					}, 1000);
+				}
+			},
+			 getredlist(type) {
+				const params = {
+					pageNum: this.pageNum,
+					pageSize: this.pageSize,
+				}
+				 getusergroup(params).then(res => {
+					if (res.code == 200) {
+						console.log(res)
+						this.list1[0].name = '群管理员(' + res.data.total + ')'
+						// refresh 下拉
+						if (type == 'refresh') {
+							this.redpacklist = res.data.list
+						} else {
+							// 加载更多 当前页和下一页合并
+							this.redpacklist = [...this.redpacklist, ...res.data.list]
+						}
+						if (this.pageNum >= res.data.pages) {
+							this.status = 'nomore'
+						} else {
+							this.status = 'loadmore'
+						}
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			cancel() {
+				this.showday = !this.showday
+			},
+			close() {
+				this.showday = !this.showday
+			},
+			confirm(e) {
+				this.pickered = e.indexs[0]
+				this.PickerName = e.value[0].label
+				if (this.pickered == 0) {
+					this.startTime = this.todayday
+					this.endTime = this.todayday
+				} else if (this.pickered == 1) {
+					let yesterday = new Date();
+					yesterday.setDate(yesterday.getDate() - 1);
+
+					this.startTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd')
+					this.endTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd')
+				} else if (this.pickered == 2) {
+					let yesterday = new Date();
+					yesterday.setDate(yesterday.getDate() - 6);
+
+					this.startTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd')
+					this.endTime = uni.$u.timeFormat(this.todayday, 'yyyy-mm-dd')
+				} else {
+					let today = new Date();
+					let lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
+
+					this.startTime = uni.$u.timeFormat(this.todayday, 'yyyy-mm') + '-01'
+					this.endTime = uni.$u.timeFormat(lastDayOfMonth, 'yyyy-mm-dd')
+				}
+				this.startTime = this.startTime + ' 00:00:00'
+				this.endTime = this.endTime + ' 23:59:59'
+				this.showday = !this.showday
+				this.getuserbalancelist()
+			},
+			clickTab(item) {
+				// console.log('item', item);  
+			},
+			exportlist() {
+				uni.navigateTo({
+					url: "/pages/courseManage/manage/exprotList"
+				})
+			},
+			lableSetup() {
+				uni.navigateTo({
+					url: "/pages/courseManage/manage/lableSetup"
+				})
+			},
+			userdataDetail() {
+				uni.navigateTo({
+					url: "/pages/courseManage/manage/userDataDetail"
+				})
+			},
+			managedetail(id,item) {
+				uni.setStorageSync('grouplist', item)
+				uni.navigateTo({
+					url: "/pages/courseManage/manage/manageDetail?id="+id
+				})
+			},
+			changevipDetail(id) {
+				uni.navigateTo({
+					url: '/pages/courseManage/manage/changeVip?id='+id
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.header-box {
+		background: linear-gradient(to right, rgba(66, 144, 240, 1.0), rgba(39, 107, 254, 1));
+	}
+
+	.box-btn {
+		font-size: 28rpx;
+		background-color: #f8f8f8;
+		border-radius: 8rpx;
+		padding: 4rpx 20rpx;
+	}
+
+	.invite-member {
+		height: 55px;
+		width: 50px;
+		position: fixed;
+		bottom: 80px;
+		right: 10px;
+		cursor: pointer;
+	}
+</style>

+ 188 - 0
pages/courseManage/manage/lableSetup.vue

@@ -0,0 +1,188 @@
+<template>
+	<view class="column flex-1 hb">
+		<view class="align-center justify-between p20">
+			<view class="justify-start fs28 align-center">
+				<u-icon name="bookmark-fill" color="#1677ff" size="28"></u-icon>
+				<view>自定义标签</view>
+			</view>
+			<view class="fs28" @click="isShowSelectAll=!isShowSelectAll">
+			{{isShowSelectAll?'取消多选':'多选'}}</view>
+		</view>
+		<view class="column flex-1 scrolly plr20" >
+			<view class="justify-start" v-for="(item,index) in companylabel"
+				:key="index">
+				<u-checkbox-group @change="selectlist(index)">
+					<u-checkbox :checked="item.checked" shape="circle" activeColor="#FF6C47" :name="true"
+						label="" labelColor="#333" v-if="isShowSelectAll"/>
+				</u-checkbox-group>
+				<view class="justify-start align-center ptb16 flex-1" 
+				style="border-bottom: 2rpx solid #e7e7e7;" >
+					<u-icon name="list" color="#666" size="24"></u-icon>
+					<view class="fs28 ml12">{{item.tag}}</view>
+				</view>
+			</view>
+		</view>
+		<view class="footbtn">
+			<view  v-if="isShowSelectAll" class="justify-between p20">
+				<view class="align-center justify-between" v-if="isShowSelectAll">
+					<u-checkbox-group @change="selectAll">
+						<u-checkbox :checked="isSelectAll" shape="circle" activeColor="#FF6C47" 
+						:name="true" label="全选" labelColor="#333" />
+						<text class="fs24 base-color-9 ml12">已选 {{selectedCount}}
+							个</text>
+					</u-checkbox-group>
+				</view>
+				<view class="justify-center ">
+					<button class="base-bg-red radius100 colorf h62 fs28 lh62 mlr10 plr60"
+						@click="changeProhibit">删除</button>
+				</view>
+			</view>
+			<view class='base-bg radius50 m20 p20 center colorf'
+			 @click="addLable" v-else>新增标签</view>
+		</view>
+		<u-modal :show="showlist" title="新增标签" @confirm="confirmchange"
+		 :showCancelButton="true" @cancel="cancel" style="flex: 0;">
+			<view class="slot-content">
+				<u-input placeholder="请输入新增标签" v-model="addlable"></u-input>
+			</view>
+		</u-modal>
+	</view>
+</template>
+
+<script>
+	import {
+		getcompanyTaglist,
+		addCompanyLabel,
+		deleteCompanyLabel
+	} from "@/api/courseManage.js"
+	export default {
+		data() {
+			return {
+				isShowSelectAll:false,
+				showlist:false,
+				addlable:'',
+				companylabel:[],
+				isSelectAll:false,
+				isSelected:false,
+				selectedCount:0,
+				selectidAll:[]
+			}
+		},
+		mounted() {
+			this.getcompanylabel()
+		},
+		methods: {
+			changeProhibit(){
+				//删除标签
+				const params={
+					tagIds:this.selectidAll
+				}
+				console.log(this.selectidAll)
+				deleteCompanyLabel(params).then(res=>{
+					if(res.code==200){
+						uni.showToast({
+							icon: 'none',
+							title: '标签删除成功'
+						})
+						this.getcompanylabel()
+					}else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getcompanylabel(){
+				getcompanyTaglist().then(res=>{
+					if(res.code==200){
+						this.companylabel = res.data.map(item => {
+							return {
+								...item,
+								checked: false,
+							}
+						})
+					}else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			addcompanySingle(){
+				const params={
+					tag:this.addlable
+				}
+				addCompanyLabel(params).then(res=>{
+					if(res.code==200){
+						uni.showToast({
+							icon: 'none',
+							title: '标签添加成功'
+						})
+						this.getcompanylabel()
+					}else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			addLable(){
+				this.showlist=!this.showlist
+			},
+			selectlist(i){
+				// 单选 /反选
+				console.log(i)
+				let arr = {
+					...this.companylabel[i],
+					checked: !this.companylabel[i].checked
+				}
+				this.$set(this.companylabel, i, arr)
+				this.selectidAll = this.companylabel.filter(item => item.checked).map(item => item.tagId)
+				console.log(this.selectidAll)
+				this.isSelectAll = this.companylabel.every(item => item.checked)
+				if (this.isSelectAll) {
+					this.companylabel = this.companylabel.map(item => {
+						return {
+							...item,
+							checked: this.isSelectAll
+						}
+					})
+				}
+			},
+			selectAll(){
+				// 全选
+				this.isSelectAll = !this.isSelectAll
+				console.log(this.isSelectAll)
+				this.companylabel = this.companylabel.map(item => {
+					// 每一项的 checked为 全选的状态
+					return {
+						...item,
+						checked: this.isSelectAll
+					}
+				})
+				console.log(this.companylabel)
+			},
+			confirmchange(){
+				if(this.addlable==''){
+					uni.showToast({
+						icon: 'none',
+						title: "请输入标签"
+					})
+				}else{
+					this.addcompanySingle()
+					this.showlist=!this.showlist
+				}
+			},
+			cancel(){
+				this.showlist=!this.showlist
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+
+</style>

+ 745 - 0
pages/courseManage/manage/manageDetail.vue

@@ -0,0 +1,745 @@
+<template>
+	<view class="column flex-1 hb">
+		<view class="header p20">
+			<view class="justify-start">
+				<u-avatar :src="grouplist.avatar" size="50"></u-avatar>
+				<view>
+					<view class="justify-start align-center ml20">
+						<text class="fs32 bold mr8">{{grouplist.nickName}}</text>
+						<image class="w40 h40" src="@/static/images/copy_icon.png" @click="copyId" mode="aspectFill">
+					</view>
+				</view>
+			</view>
+			<view class="ptb20">-</view>
+		</view>
+		<view class="bgf">
+			<view class="centerV">
+				<u-tabs :list="list1" @click="clickTab" lineColor='#1773ff' lineWidth='40'
+					activeStyle="font-weight:bold"></u-tabs>
+			</view>
+			<view class="justify-between box-blue" v-show="showCont==1">
+				<view class="base-bg-sure boxs">
+					<view class="bold">会员总数</view>
+					<view class="base-color fs28">
+						<text class="fs40 bold">{{companyUserUserCount}}</text>人
+					</view>
+				</view>
+				<view class="base-bg-sure boxs">
+					<view class="bold">今日新增会员</view>
+					<view class="base-color fs28">
+						<text class="fs40 bold">{{companyUserCount}}</text>人
+					</view>
+				</view>
+				<view class="base-bg-sure boxs">
+					<view class="bold">会员红包数</view>
+					<view class="base-color fs28"><text class="fs40 bold">{{redbaglist.count}}</text>个</view>
+				</view>
+				<view class="base-bg-sure boxs">
+					<view class="bold">新会员红包金额</view>
+					<view class="base-color fs28"><text class="fs40 bold">{{redbaglist.amount}}</text>元</view>
+				</view>
+			</view>
+			<dropdownPanel :filterData='filterData' @onChange="onChange" @confirm="confirm" @reset="reset">
+				<view v-if="searchbarNav == 0">
+					<view class="p20 fs28 column flex-1 scrolly">
+						<view v-for="(item,index) in courseOne" :key="item.index"
+							:class="courseid==item.courseId?'actNav':''" class="m10 p10 center"
+							style="border-bottom: 2rpx solid #eee;" @click="getCourseOne(item.courseId)">
+							{{item.courseName}}
+						</view>
+					</view>
+				</view>
+				<view class="p20 fs28 column flex-1 hidden h100" v-if="searchbarNav == 1">
+					<scroll-view scroll-y="true" class="hb" :refresher-enabled="isEnableds"
+						:refresher-triggered="triggereds" refresher-background="rgba(0,0,0,0)"
+						@refresherrefresh="pullDownRefreshs" @refresherrestore="triggereds = false"
+						:upper-threshold="100" :lower-threshold="100" @refresherabort="triggereds = false"
+						@scrolltolower="reachBottoms">
+						<view v-for="(item,index) in courseTwo" :key="item.index"
+							:class="courseids==item.videoId?'actNav':''" class="m10 p10 center"
+							style="border-bottom: 2rpx solid #eee;" @click="getCourseTwo(item.videoId)">
+							{{item.title}}
+						</view>
+						<u-loadmore :status="statusA" />
+						<view class="ptb40"></view>
+					</scroll-view>
+				</view>
+			</dropdownPanel>
+		</view>
+		<view class="column flex-1 scrolly" v-show="showCont==0">
+			<scroll-view scroll-y="true" class="hb" :refresher-enabled="isEnabled" :refresher-triggered="triggered"
+				refresher-background="rgba(0,0,0,0)" @refresherrefresh="pullDownRefresh"
+				@refresherrestore="triggered = false" :upper-threshold="100" :lower-threshold="100"
+				@refresherabort="triggered = false" @scrolltolower="reachBottom">
+				<view class="m20 radius16 bgf p20" style="border: #3e9f59 solid 2rpx;"
+					v-for="(item,index) in courselist" :key="index">
+					<view class="justify-start align-center pb20" style="border-bottom: #eee solid 2rpx;">
+						<view class="doingimg">
+							<image src="../../../static/manergevip/book.png" class='w150 h90 radius16'></image>
+							<view class="doing">进行中</view>
+						</view>
+						<view class="ml20">
+							<view class="justify-start align-center">
+								<view>{{item.courseName}}</view>
+								<view class="justify-start align-center base-color">ID
+									<image class="w40 h40" @click="copyCourseId(item.courseId)"
+										src="@/static/images/copy_icon.png" mode="aspectFill">
+								</view>
+							</view>
+							<view class="fs24 base-color-6 column">
+								<text>{{item.title}}</text>
+								<text></text>
+							</view>
+						</view>
+					</view>
+					<view class="justify-between fs28 mt20">
+						<view class="flex-1">
+							<view class="mb8 base-color-6">观看人数</view>
+							<view>{{item.count}}</view>
+						</view>
+						<view class="flex-1">
+							<view class="mb8 base-color-6">完播人数</view>
+							<view>{{item.completeCount}}
+								<text class="base-color-red ml16">完播率{{item.completeRate.toFixed(2)}}%</text>
+							</view>
+						</view>
+					</view>
+					<view class="justify-between fs28 mt20 pb20" style="border-bottom: #eee solid 2rpx;">
+						<view class="flex-1">
+							<view class="mb8 base-color-6">答题红包数</view>
+							<view>{{item.redPacketCount}}</view>
+						</view>
+						<view class="flex-1">
+							<view class="mb8 base-color-6">答题红包金额</view>
+							<view>{{item.amount}}元</view>
+						</view>
+					</view>
+					<view class="justify-between mt20">
+						<view class="flex-1">
+							<view class="base-color-6 mb8 ">答题人数</view>
+							<view>{{item.answerCount}}</view>
+						</view>
+						<view class="flex-1">
+							<view class="base-color-6 mb8 ">正确人数</view>
+							<view>{{item.answerRightCount}}</view>
+						</view>
+						<view class="flex-1">
+							<view class="base-color-6 mb8 ">正确率</view>
+							<view>{{item.answerRightRate.toFixed(2)}}%</view>
+						</view>
+					</view>
+					<view class="centerV mt20">
+						<view class="colorf base-bg ptb12 w400 radius80 center ">课程数据</view>
+					</view>
+				</view>
+				<u-loadmore :status="status" />
+				<view class="h90 "></view>
+			</scroll-view>
+		</view>
+		<view class="column flex-1 scrolly " v-show="showCont==1"
+			style="height: calc(100% - 100rpx);padding-bottom: 100rpx;">
+			<u-collapse ref="collapseRef" :border="false" :value="collapseValue" @change="changeCollapse">
+				<u-collapse-item name="course">
+					<text slot="title" class="bold fs32">课程统计</text>
+					<text slot="value" class="base-color fs24">{{collapseValue.includes('course')?'收回':'展开'}}</text>
+					<view slot="right-icon">
+						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+					</view>
+					<view class="justify-around ">
+						<view class="base-bg-f radius16 p30 flex-1">
+							<view class="mb12 bold">营期</view>
+							<view class="base-color">
+								<text class="bold fs40">{{courseCount.course}}</text>期
+							</view>
+						</view>
+						<view class="base-bg-f radius16 p20 flex-1 mlr20">
+							<view class="mb12 bold">课程数</view>
+							<view class="base-color"><text class="bold fs40">{{courseCount.count}}
+								</text>课</view>
+						</view>
+						<view class="base-bg-f radius16 p20 flex-1">
+							<view class="mb12 bold">参与会员</view>
+							<view class="base-color"><text class="bold fs40">
+									{{courseCount.watchCount}}</text>人</view>
+						</view>
+					</view>
+				</u-collapse-item>
+				<u-collapse-item name="redenvelope">
+					<text slot="title" class="bold fs32">答题统计</text>
+					<text slot="value"
+						class="base-color fs24">{{collapseValue.includes('redenvelope')?'收回':'展开'}}</text>
+					<view slot="right-icon">
+						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+					</view>
+					<view class="justify-around ">
+						<view class="base-bg-f radius16 p30 flex-1">
+							<view class="mb12 bold">答题人次</view>
+							<view class="base-color"><text class="bold fs40">
+									{{answerlist.count}}</text>人</view>
+						</view>
+						<view class="base-bg-f radius16 p20 flex-1 mlr20">
+							<view class="mb12 bold">正确人次</view>
+							<view class="base-color"><text class="bold fs40">
+									{{answerlist.completeCount}}</text>人</view>
+						</view>
+						<view class="base-bg-f radius16 p20 flex-1">
+							<view class="mb12 bold">正确率</view>
+							<view class="base-color"><text class="bold fs40">
+									{{answerlist.rate.toFixed(2)}}</text>%</view>
+						</view>
+					</view>
+				</u-collapse-item>
+				<u-collapse-item name="live">
+					<text slot="title" class="bold fs32">红包统计</text>
+					<text slot="value" class="base-color fs24">{{collapseValue.includes('live')?'收回':'展开'}}</text>
+					<view slot="right-icon">
+						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+					</view>
+					<view class="justify-around ">
+						<view class="base-bg-f radius16 p30 flex-1">
+							<view class="mb12 bold">红包个数</view>
+							<view class="base-color"><text class="bold fs40">
+									{{redbaglist.count}}</text>个</view>
+						</view>
+						<view class="base-bg-f radius16 p20  mlr20 justify-start align-center">
+							<image src="../../../static/images/redenvelope.png" class="w102 h102"></image>
+							<view class="ml20">
+								<view class="mb12 bold">答题红包金额</view>
+								<view class="base-color">
+									<text class="bold fs40">
+										{{redbaglist.amount}}
+									</text>元
+								</view>
+							</view>
+						</view>
+					</view>
+				</u-collapse-item>
+			</u-collapse>
+			<view class="h90 "></view>
+		</view>
+		<view class="foot-box justify-between bgf p20 align-center">
+			<view class="fs28 base-color-6" @click="showmore=!showmore">更多</view>
+			<view class="base-color-red base-bg-false plr20 fs28 ptb8 radius40 bold" @click="changevipDetail()">更换会员归属
+			</view>
+		</view>
+		<u-popup :show="showmore" @close="closemore" @open="openmore" style="flex: 0;">
+			<view class="column align-center">
+				<view class="m20" v-for="(item,index) in morelist" :key="item.value" @click="nameMore(item)">
+					{{item.name}}
+				</view>
+				<u-modal :show="showlist" :title="titlelist" @confirm="confirmchange">
+					<view class="slot-content">
+						<u-input :placeholder="contpl" v-model="changelist" v-if="selnum==1"></u-input>
+						<u-input :placeholder="contpl" v-model="changephone" v-if="selnum==2"></u-input>
+						<u-input :placeholder="contpl" v-model="changeremark" v-if="selnum==3"></u-input>
+					</view>
+				</u-modal>
+			</view>
+		</u-popup>
+
+	</view>
+</template>
+
+<script>
+	import dropdownPanel from "../components/dropdownPanel.vue"
+	import {
+		getFsCourseList,
+		getCourseVdieoList,
+		getCompanyUserAndUserCount,
+		getRecPacketCount,
+		getshopCoursenum,
+		getQuesCount,
+		getcourseList,
+		updategroupinfo,
+		getGroupDetail
+	} from "@/api/courseManage.js"
+	export default {
+		components: {
+			dropdownPanel
+		},
+		data() {
+			return {
+				changeremark: '',
+				changephone: '',
+				list1: [{
+					name: '课程分析'
+				}, {
+					name: '群管数据'
+				}],
+				filterData: [{
+						name: '训练营-营期',
+						value: 0,
+					},
+					{
+						name: '课程',
+						value: 1,
+					},
+				],
+				searchbarNav: 0,
+				showCont: 0,
+				collapseValue: ['course', 'questions', 'redenvelope', 'live', 'funnel'], //
+				showmore: false,
+				morelist: [{
+						name: '复制ID',
+						value: 0,
+						text: ''
+					},
+					{
+						name: '改姓名',
+						value: 1,
+						text: '请输入姓名'
+					},
+					{
+						name: '改手机号码',
+						value: 2,
+						text: '请输入手机号码'
+					},
+					{
+						name: '改备注',
+						value: 3,
+						text: '请输入备注'
+					}
+				],
+				showlist: false,
+				titlelist: '',
+				contpl: '',
+				changelist: '',
+				groupid: 0,
+				courseid: '',
+				courseids: '',
+				courseOne: [],
+				courseTwo: [],
+				triggereds: false,
+				statusA: 'loadmore',
+				isEnableds: true,
+				pageNums: 1,
+				pageSizes: 5,
+				redprice: '',
+				companyUserCount: 0,
+				companyUserUserCount: 0,
+				redbaglist: [],
+				courseCount: [],
+				answerlist: [],
+				courselist: [],
+				triggered: false,
+				status: 'loadmore',
+				isEnabled: true,
+				pageNum: 1,
+				pageSize: 3,
+				grouplist: [],
+				selnum: 0
+			}
+		},
+		onLoad(option) {
+			this.groupid = option.id
+		},
+		mounted() {
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			this.grouplist = uni.getStorageSync("grouplist")
+			this.getvipcount()
+			this.getredbaglist()
+			this.getcout()
+			this.getcoursegroup()
+			this.getgroupdetail()
+		},
+		methods: {
+			copyId() {
+				uni.setClipboardData({
+					data: String(this.groupid),
+					success: () => {
+						uni.showToast({
+							title: '用户id复制成功',
+							icon: 'none',
+							duration: 2000
+						});
+					},
+				})
+			},
+			copyCourseId(id) {
+				uni.setClipboardData({
+					data: String(id),
+					success: () => {
+						uni.showToast({
+							title: '课程id复制成功',
+							icon: 'none',
+							duration: 2000
+						});
+					},
+				})
+			},
+			getgroupdetail() {
+				const param = {
+					userId: this.groupid
+				}
+				getGroupDetail(param).then(res => {
+					this.grouplist = res.data
+				})
+			},
+			updatagroup() {
+				console.log(Number(this.groupid))
+				const param = {
+					nickName: this.changelist,
+					phonenumber: this.changephone,
+					remark: this.changeremark,
+					userId: this.groupid
+				}
+				updategroupinfo(param).then(res => {
+					if (res.code == 200) {
+						uni.showToast({
+							icon: 'none',
+							title: "修改成功"
+						})
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			pullDownRefresh() {
+				// 下拉
+				this.triggered = true; //下拉了状态为true
+				setTimeout(() => {
+					this.triggered = false;
+					uni.stopPullDownRefresh()
+					this.pageNum = 1;
+					this.getcoursegroup('refresh') //触底  不穿执行else
+					// 请求接口里面需要判断是不是最后一页   是最后一页 status赋值为‘loadmore’没有更多了
+					// 请求接口
+				}, 1000)
+			},
+			reachBottom() {
+				// status这个是加载状态
+				if (this.status === 'loadmore') {
+					this.status = 'loading'
+					uni.showNavigationBarLoading()
+					setTimeout(() => {
+						this.pageNum++
+						this.getcoursegroup() //触底  不穿执行else
+						uni.hideNavigationBarLoading()
+					}, 1000);
+				}
+			},
+			getcoursegroup(type) {
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.groupid,
+					courseId: this.courseid,
+					videoId: this.courseids,
+					endTime: '',
+					startTime: '',
+					pageNum: this.pageNum,
+					pageSize: this.pageSize,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getcourseList(param).then(res => {
+					console.log(res)
+					if (res.code == 200) {
+						// refresh 下拉
+						if (type == 'refresh') {
+							this.courselist = res.data.list
+						} else {
+							// 加载更多 当前页和下一页合并
+							this.courselist = [...this.courselist, ...res.data.list]
+						}
+						if (this.pageNum >= res.data.pages) {
+							this.status = 'nomore'
+						} else {
+							this.status = 'loadmore'
+						}
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getcout() {
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.groupid,
+					courseId: this.courseid,
+					videoId: this.courseids,
+					endTime: '',
+					startTime: '',
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				this.getCourselist(param)
+				this.getanswer(param)
+			},
+			getanswer(param) {
+				getQuesCount(param).then(res => {
+					if (res.code == 200) {
+						// console.log(res)
+						this.answerlist = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getCourselist(param) {
+				getshopCoursenum(param).then(res => {
+					if (res.code == 200) {
+						this.courseCount = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			getredbaglist() {
+				//红包统计
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.groupid,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+					courseId: this.courseid,
+					videoId: this.courseids,
+					endTime: '',
+					startTime: '',
+				}
+				getRecPacketCount(params).then(res => {
+					if (res.code == 200) {
+						// console.log(res)
+						this.redbaglist = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getvipcount() {
+				//获取会员总数
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.groupid,
+					courseId: '',
+					endTime: '',
+					startTime: '',
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getCompanyUserAndUserCount(param).then(res => {
+					if (res.code == 200) {
+						this.companyUserCount = res.data.companyUserCount || 0
+						this.companyUserUserCount = res.data.companyUserUserCount || 0
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			pullDownRefreshs() {
+				// 下拉
+				this.triggereds = true; //下拉了状态为true
+				setTimeout(() => {
+					this.triggereds = false;
+					uni.stopPullDownRefresh()
+					this.pageNums = 1;
+					this.getCourseListsmall('refresh') //触底  不穿执行else
+					// 请求接口里面需要判断是不是最后一页   是最后一页 status赋值为‘loadmore’没有更多了
+					// 请求接口
+				}, 1000)
+			},
+			reachBottoms() {
+				// status这个是加载状态
+				if (this.statusA === 'loadmore') {
+					this.statusA = 'loading'
+					uni.showNavigationBarLoading()
+					setTimeout(() => {
+						this.pageNums++
+						this.getCourseListsmall() //触底  不穿执行else
+						uni.hideNavigationBarLoading()
+					}, 1000);
+				}
+			},
+			getCourseOne(id) {
+				this.courseid = id
+			},
+			getCourseTwo(id) {
+				this.courseids = id
+			},
+			getCourseList() {
+				//获取课程列表
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getFsCourseList(param).then(res => {
+					if (res.code == 200) {
+						this.courseOne = res.data
+						// console.log(res)
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getCourseListsmall(type) {
+				//获取小节目录
+				const param = {
+					courseId: this.courseid,
+					pageNum: this.pageNums,
+					pageSize: this.pageSizes, //
+					status: ''
+				}
+				getCourseVdieoList(param).then(res => {
+					if (res.code == 200) {
+						// refresh 下拉
+						if (type == 'refresh') {
+							this.courseTwo = res.data.list
+						} else {
+							// 加载更多 当前页和下一页合并
+							this.courseTwo = [...this.courseTwo, ...res.data.list]
+						}
+						if (this.pageNums >= res.data.pages) {
+							this.statusA = 'nomore'
+						} else {
+							this.statusA = 'loadmore'
+						}
+					} else {
+
+					}
+				})
+			},
+			clickTab(e) {
+				this.showCont = e.index
+				this.$nextTick(() => {
+					this.$refs?.collapseRef?.init()
+				})
+				console.log(e.index)
+			},
+			onChange(index) {
+				this.searchbarNav = index
+
+				if (index == 0) {
+					this.getCourseList()
+				} else {
+					this.getCourseListsmall()
+				}
+			},
+			confirm() {
+				this.getcout()
+				this.getredbaglist()
+				this.courselist = []
+				this.getcoursegroup()
+			},
+			reset() {
+				if (this.searchbarNav == 0) {
+					this.courseid = ''
+					this.getcout()
+					this.getredbaglist()
+					this.courselist = []
+					this.getcoursegroup()
+				} else {
+					this.courseids = ''
+					this.getcout()
+					this.getredbaglist()
+					this.courselist = []
+					this.getcoursegroup()
+				}
+			},
+			changeCollapse(e) {
+				this.collapseValue = e.filter(item => item.status == 'open').map(it => it.name)
+				console.log(e.filter(item => item.status == 'open').map(it => it.name))
+			},
+			closemore() {
+				this.showmore = false
+			},
+			openmore() {
+
+			},
+			nameMore(item) {
+				this.titlelist = item.name
+				this.contpl = item.text
+				this.selnum = item.value
+				if (item.value == 0) {
+
+				} else if (item.value == 1) {
+					this.changelist = this.grouplist.nickName
+					this.showlist = true
+				} else if (item.value == 2) {
+					console.log(this.grouplist.phonenumber)
+					this.changephone = this.grouplist.phonenumber
+					this.showlist = true
+				} else {
+					this.changeremark = this.grouplist.remark
+					this.showlist = true
+				}
+			},
+			confirmchange() {
+				this.updatagroup()
+				this.showlist = false
+				setTimeout(() => {
+					this.showmore = false
+					this.getgroupdetail()
+				}, 200)
+			},
+			changevipDetail() {
+				uni.navigateTo({
+					url: '/pages/courseManage/manage/changeVip?id=' + this.groupid
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.header {
+		background: linear-gradient(to right, rgba(218, 233, 255, 1), rgba(225, 225, 253, 1));
+	}
+
+	.doingimg {
+		position: relative;
+
+	}
+
+	.doing {
+		border-radius: 12rpx 0 12rpx 0;
+		background-color: #3e9f59;
+		color: #fff;
+		width: fit-content;
+		padding: 0 20rpx;
+		font-size: 24rpx;
+		position: absolute;
+		top: 3rpx;
+	}
+
+	.box-blue {
+		flex-wrap: wrap;
+		padding: 20rpx;
+
+		.boxs {
+			width: 48%;
+			border-radius: 20rpx;
+			margin-top: 20rpx;
+			padding: 20rpx 0;
+			padding-left: 32rpx;
+			font-size: 28rpx;
+		}
+	}
+
+	.foot-box {
+		position: fixed;
+		bottom: 0;
+		width: 100%;
+	}
+
+	.actNav {
+		color: #1677ff !important;
+		background-color: #e7f2fe;
+	}
+</style>

+ 54 - 0
pages/courseManage/manage/setup.vue

@@ -0,0 +1,54 @@
+<template>
+	<view class="setup">
+		<view class="base-bg-f p20 radius20 mb60">
+			<view class="justify-between align-center">
+				<view>是否允许新会员观看</view>
+				<switch :checked="value" @change="switch1Change" />
+			</view>
+		</view>
+		<view class="surebtn ">保存</view>
+	</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				value: false
+			}
+		},
+		methods: {
+			switch1Change(e) {
+				this.value = e.detail.value
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	/* 去除switch点击时的闪烁效果 */
+	::v-deep {
+		.u-switch-input {
+			-webkit-tap-highlight-color: transparent;
+		}
+	}
+
+	.setup {
+		height: calc(100% - 40rpx);
+		padding: 20rpx;
+		margin: 0 !important;
+	}
+
+	.surebtn {
+		width: calc(100% - 100rpx);
+		padding: 0 50rpx;
+		background-color: #1777ff;
+		height: 72rpx;
+		border-radius: 8rpx;
+		color: #fff;
+		text-align: center;
+		line-height: 72rpx;
+		margin: 0 auto;
+	}
+</style>

+ 515 - 0
pages/courseManage/manage/userDataDetail.vue

@@ -0,0 +1,515 @@
+<template>
+	<view class="column flex-1 hb">
+		<view class="justify-between align-center plr32 m20 base-bg 
+		colorf ptb48 radius20">
+			<view>账户可用余额</view>
+			<view class="bold fs40">{{balanceuserDay.balance}}</view>
+		</view>
+		<view class="bgf">
+			<view class="centerV">
+				<u-tabs :list="list1" @click="clickTab" lineColor='#1773ff' lineWidth='40'
+					activeStyle="font-weight:bold"></u-tabs>
+			</view>
+			<view class="justify-around ptb20">
+				<view :class="queryParam.type == index ? 'searchbox-item active':'searchbox-item'"
+					v-for="(item,index) in typeOption" :key="index" @click="handleType(index)">{{item.label}}</view>
+			</view>
+		</view>
+		<view style="z-index: 999;">
+			<dropdownPanel :filterData='filterData' @onChange="onChange" @confirm="confirm" @reset="reset">
+				<view v-if="searchbarNav == 0" class="justify-start wrap p20 ">
+					<view class="warpbox ptb8 plr16 m10 radius12 base-bg-f8 base-color-6"
+						:class="redid==item.id?'activebag':''" @click="selredbag(item)"
+						v-for="(item,index) in redlist" :key="item.id">{{item.redPacketMoney}}</view>
+				</view>
+				<view v-if="searchbarNav == 1" class="justify-start wrap p20 ">
+					<view class="warpbox ptb8 plr16 m10 radius12 base-bg-f8 base-color-6"
+						:class="groupid==item.userId?'activebag':''" @click="selredgroup(item.userId)"
+						v-for="(item,index) in grouplist" :key="item.userId">
+						{{item.nickName}}
+					</view>
+				</view>
+				<view v-if="searchbarNav == 2">
+					<view class="p20 fs28 column flex-1 scrolly">
+						<view v-for="(item,index) in courseOne" :key="item.index"
+							:class="courseid==item.courseId?'actNav':''" class="m10 p10 center"
+							style="border-bottom: 2rpx solid #eee;" @click="getCourseOne(item.courseId)">
+							{{item.courseName}}
+						</view>
+					</view>
+				</view>
+				<view class="p20 fs28 column flex-1 hidden h100" v-if="searchbarNav == 3">
+					<scroll-view scroll-y="true" class="hb" :refresher-enabled="isEnableds"
+						:refresher-triggered="triggereds" refresher-background="rgba(0,0,0,0)"
+						@refresherrefresh="pullDownRefreshs" @refresherrestore="triggereds = false" :upper-threshold="100"
+						:lower-threshold="100" @refresherabort="triggereds = false" @scrolltolower="reachBottoms">
+						<view v-for="(item,index) in courseTwo" :key="item.index"
+							:class="courseids==item.videoId?'actNav':''" class="m10 p10 center"
+							style="border-bottom: 2rpx solid #eee;" @click="getCourseTwo(item.videoId)">
+							{{item.title}}
+						</view>
+						<u-loadmore :status="statusA" />
+						<view class="ptb40"></view>
+					</scroll-view>
+				</view>
+			</dropdownPanel>
+		</view>
+		<view class="m20 plr32 ptb40 justify-between align-center base-bg-sure radius20"
+			style="border: 2rpx solid #1773ff;">
+			<view class="bold">发放金额</view>
+			<view class="bold fs40">{{balanceuserDay.todayMoney}}</view>
+		</view>
+		<view class="column flex-1 scrolly">
+			<scroll-view scroll-y="true" class="hb" :refresher-enabled="isEnabled" :refresher-triggered="triggered"
+				refresher-background="rgba(0,0,0,0)" @refresherrefresh="pullDownRefresh"
+				@refresherrestore="triggered = false" :upper-threshold="100" :lower-threshold="100"
+				@refresherabort="triggered = false" @scrolltolower="reachBottom">
+				<view class="bgf m20 p20 radius16" v-for="(item,index) in userlist" :key="index">
+					<view class="justify-between align-center">
+						<view class="justify-start align-center">
+							<image src="../../../static/manergevip/book.png" class="w80 h80"></image>
+							<view class="fs36 bold ml20">课程答题红包</view>
+							<view class="fs28 ml8 base-color-6">归属</view>
+						</view>
+						<view class="base-color fs40 bold">{{item.amount}}</view>
+					</view>
+					<view class="justify-start mtb20">
+						<u-avatar :src="item.avatar" size="50"></u-avatar>
+						<view class="ml20">
+							<view class="justify-start align-center">
+								<text class="fs28 bold">{{item.nickName}}</text>
+								<image class="w40 h40" src="@/static/images/copy_icon.png" mode="aspectFill">
+							</view>
+							<view class="fs24 base-color-6">
+								<text>--</text>
+								<text class="ml20">{{item.fsUserCreatTime}} 注册</text>
+							</view>
+						</view>
+					</view>
+					<view class="base-bg-f8 radius16 p20">
+						<view class="justify-start fs28 mb8">
+							<view class="base-color-6 mr20">观看课程</view>
+							<view>{{item.courseName}}</view>
+						</view>
+						<view class="justify-start fs28">
+							<view class="base-color-6 mr20">领取时间</view>
+							<view>{{item.createTime}}</view>
+						</view>
+					</view>
+				</view>
+				<u-loadmore :status="status" />
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		getCourseRedPacklist,
+		getuserbalance,
+		getredPrice,
+		getgroupList,
+		getFsCourseList,
+		getCourseVdieoList
+	} from "@/api/courseManage.js"
+	import dropdownPanel from "../components/dropdownPanel.vue"
+	export default {
+		components: {
+			dropdownPanel
+		},
+		data() {
+			return {
+				list1: [{
+					name: '发放记录'
+				}],
+				typeOption: [{
+					label: '全部',
+					value: 0
+				}, {
+					label: '今日',
+					value: 1
+				}, {
+					label: '昨日',
+					value: 2
+				}, {
+					label: '近七天',
+					value: 3
+				}, {
+					label: '本月',
+					value: 4
+				}],
+				queryParam: {
+					type: 0
+				},
+				filterData: [{
+						name: '答题红包',
+						value: 0,
+					},
+					{
+						name: '群管',
+						value: 1,
+					}, {
+						name: '训练营-营期',
+						value: 2,
+					}, {
+						name: '课程',
+						value: 3,
+					}
+				],
+				searchbarNav: 0,
+				userlist: [],
+				triggered: false,
+				status: 'loadmore',
+				isEnabled: true,
+				pageNum: 1,
+				pageSize: 5,
+				startTime: '',
+				endTime: '',
+				todayday: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
+				balanceuserDay: [],
+				user: [],
+				redlist: [],
+				redid: '',
+				groupid: '',
+				grouplist: [],
+				courseid: '',
+				courseids: '',
+				courseOne: [],
+				courseTwo: [],
+				triggereds: false,
+				statusA: 'loadmore',
+				isEnableds: true,
+				pageNums: 1,
+				pageSizes: 5,
+				redprice:''
+			}
+		},
+		mounted() {
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			this.getuserList()
+			this.getuserbalancelist()
+		},
+		methods: {
+			pullDownRefreshs() {
+				// 下拉
+				this.triggereds = true; //下拉了状态为true
+				setTimeout(() => {
+					this.triggereds = false;
+					uni.stopPullDownRefresh()
+					this.pageNums = 1;
+					this.getCourseListsmall('refresh') //触底  不穿执行else
+					// 请求接口里面需要判断是不是最后一页   是最后一页 status赋值为‘loadmore’没有更多了
+					// 请求接口
+				}, 1000)
+			},
+			reachBottoms() {
+				// status这个是加载状态
+				console.log(111);
+				if (this.statusA === 'loadmore') {
+					this.statusA = 'loading'
+					uni.showNavigationBarLoading()
+					setTimeout(() => {
+						this.pageNums++
+						this.getCourseListsmall() //触底  不穿执行else
+						uni.hideNavigationBarLoading()
+					}, 1000);
+				}
+			},
+			getCourseOne(id) {
+				this.courseid = id
+			},
+			getCourseTwo(id) {
+				this.courseids = id
+			},
+			getCourseList() {
+				//获取课程列表
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getFsCourseList(param).then(res => {
+					if (res.code == 200) {
+						this.courseOne = res.data
+						// console.log(res)
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getCourseListsmall(type) {
+				//获取小节目录
+				const param = {
+					courseId: this.courseid,
+					pageNum: this.pageNums,
+					pageSize: this.pageSizes, //
+					status: ''
+				}
+				getCourseVdieoList(param).then(res => {
+					if (res.code == 200) {
+						// refresh 下拉
+						if (type == 'refresh') {
+							this.courseTwo = res.data.list
+						} else {
+							// 加载更多 当前页和下一页合并
+							this.courseTwo = [...this.courseTwo, ...res.data.list]
+						}
+						if (this.pageNums >= res.data.pages) {
+							this.statusA = 'nomore'
+						} else {
+							this.statusA = 'loadmore'
+						}
+					} else {
+
+					}
+				})
+			},
+			selredbag(item) {
+				this.redid =item.id
+				this.redprice=item.redPacketMoney
+			},
+			selredgroup(id) {
+				this.groupid = id
+			},
+			getredlist() {
+				//获取红包价格表
+				const params = {
+					companyId: this.user.companyId
+				}
+				getredPrice(params).then(res => {
+					if (res.code == 200) {
+						this.redlist = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getgrouplist() {
+				//获取群管列表
+				const params = {
+					companyId: this.user.companyId
+				}
+				getgroupList(params).then(res => {
+					if (res.code == 200) {
+						this.grouplist = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getuserbalancelist() {
+				const params = {
+					companyId: this.user.companyId,
+					endTime: this.endTime,
+					startTime: this.startTime,
+				}
+				getuserbalance(params).then(res => {
+					if (res.code == 200) {
+						this.balanceuserDay = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			pullDownRefresh() {
+				// 下拉
+				this.triggered = true; //下拉了状态为true
+				setTimeout(() => {
+					this.triggered = false;
+					uni.stopPullDownRefresh()
+					this.pageNum = 1;
+					this.getuserList('refresh') //触底  不穿执行else
+					// 请求接口里面需要判断是不是最后一页   是最后一页 status赋值为‘loadmore’没有更多了
+					// 请求接口
+				}, 1000)
+			},
+			reachBottom() {
+				// status这个是加载状态
+				console.log(111);
+				if (this.status === 'loadmore') {
+					this.status = 'loading'
+					uni.showNavigationBarLoading()
+					setTimeout(() => {
+						this.pageNum++
+						this.getuserList() //触底  不穿执行else
+						uni.hideNavigationBarLoading()
+					}, 1000);
+				}
+			},
+			getuserList(type) {
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId:this.groupid,
+					redPacketId:this.redprice,
+					courseId:this.courseid,
+					videoId:this.courseids,
+					endTime: this.endTime,
+					startTime: this.startTime,
+				}
+				getCourseRedPacklist(params).then(res => {
+					if (res.code == 200) {
+						console.log(res)
+						// refresh 下拉
+						if (type == 'refresh') {
+							this.userlist = res.data.list
+						} else {
+							// 加载更多 当前页和下一页合并
+							this.userlist = [...this.userlist, ...res.data.list]
+						}
+						if (this.pageNum >= res.data.pages) {
+							this.status = 'nomore'
+						} else {
+							this.status = 'loadmore'
+						}
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			clickTab(item) {
+				// console.log('item', item);  
+			},
+			handleType(type) {
+				console.log(type);
+				this.queryParam.type = type
+				this.userlist = []
+				if (this.queryParam.type == 0) {
+					this.endTime = ''
+					this.startTime = ''
+					this.getuserbalancelist()
+					this.getuserList()
+				} else if (this.queryParam.type == 1) {
+					this.startTime = this.todayday + ' 00:00:00'
+					this.endTime = this.todayday + ' 23:59:59'
+					this.getuserbalancelist()
+					this.getuserList()
+				} else if (this.queryParam.type == 2) {
+					let yesterday = new Date();
+					yesterday.setDate(yesterday.getDate() - 1);
+					this.startTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd') + ' 00:00:00'
+					this.endTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd') + ' 23:59:59'
+					this.getuserbalancelist()
+					this.getuserList()
+					console.log(this.userlist)
+				} else if (this.queryParam.type == 3) {
+					let yesterday = new Date();
+					yesterday.setDate(yesterday.getDate() - 6);
+					this.startTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd') + ' 00:00:00'
+					this.endTime = this.todayday + ' 23:59:59'
+					this.getuserbalancelist()
+					this.getuserList()
+				} else {
+					let today = new Date();
+					let lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
+
+					this.startTime = uni.$u.timeFormat(this.todayday, 'yyyy-mm') + '-01' + " 00:00:00"
+					this.endTime = uni.$u.timeFormat(lastDayOfMonth, 'yyyy-mm-dd') + " 23:59:59"
+					this.getuserbalancelist()
+					this.getuserList()
+				}
+			},
+			onChange(index) {
+				this.searchbarNav = index
+				this.courseTwo=[]
+				if (index == 0) {
+					console.log(11)
+					this.getredlist()
+				} else if (index == 1) {
+					this.getgrouplist()
+				} else if (index == 2) {
+					this.getCourseList()
+				} else {
+					if(this.courseid==''){
+						uni.showToast({
+							icon: 'none',
+							title: '请先选择训练营-营期'
+						})
+					}else{
+						this.getCourseListsmall()
+					}
+				}
+			},
+			confirm() {
+				this.userlist=[]
+				this.getuserList()
+			},
+			reset() {
+				if(this.searchbarNav==0){
+					this.redprice=''
+					this.getuserList()
+				}else if(this.searchbarNav==1){
+					this.groupid=''
+					this.getuserList()
+				}else if(this.searchbarNav==1){
+					this.courseid=''
+					this.getuserList()
+				}else{
+					this.courseids=''
+					this.getuserList()
+				}
+				console.log(this.searchbarNav)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.warpbox {
+		width: fit-content;
+	}
+
+	.activebag {
+		border: 2rpx solid #1773ff;
+		background-color: #e7f2fe;
+		color: #1677ff;
+	}
+
+	.searchbox {
+		background-color: #fff;
+		border-radius: 8rpx 8rpx 0 0;
+		padding: 15px;
+
+		&-item {
+			height: 23px;
+			line-height: 23px;
+			width: fit-content;
+			padding: 0 20rpx;
+			background: #f5f5f5;
+			text-align: center;
+			border-radius: 5px;
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 400;
+			font-size: 12px;
+			color: #888;
+		}
+	}
+
+	.active {
+		color: #fff !important;
+		background-color: #1677ff !important;
+	}
+
+	:deep .navs .c-flex-center text {
+		width: max-content;
+	}
+
+	.actNav {
+		color: #1677ff !important;
+		background-color: #e7f2fe;
+	}
+</style>

+ 643 - 0
pages/courseManage/operation/index.vue

@@ -0,0 +1,643 @@
+<template>
+	<view class="column flex-1 hb">
+		<!-- <view class="headnav x-bc">
+			<view :class="activeTab == 0 ? 'headnav-item headnav-active':'headnav-item'" @click="handleNav(0)">
+				<view>直播计划</view>
+				<view class="headnav-num">3</view>
+				<image src="@/static/images/idle.png" mode="aspectFill"></image>
+			</view>
+			<view :class="activeTab == 1? 'headnav-item headnav-active':'headnav-item'" @click="handleNav(1)">
+				<view>今日直播</view>
+				<view class="headnav-num">3</view>
+				<image src="@/static/images/streaming.png" mode="aspectFill"></image>
+			</view>
+			<view :class="activeTab == 2 ? 'headnav-item headnav-active':'headnav-item'" @click="handleNav(2)">
+				<view>往日直播</view>
+				<view class="headnav-num">3</view>
+				<image src="@/static/images/finished.png" mode="aspectFill"></image>
+			</view>
+		</view> -->
+		<view>
+			<dropdownPanel :filterData='filterData' @onChange="onChange" @confirm="confirm" @reset="reset">
+				<view class="column flex-1 hb hidden">
+					<view v-if="searchbarNav == 0">
+						<view class="p20 fs28 column flex-1 scrolly">
+							<view v-for="(item,index) in courseOne" :key="item.index"
+								:class="courseid==item.courseId?'actNav':''" class="m10 p10 center"
+								style="border-bottom: 2rpx solid #eee;" @click="getCourseOne(item.courseId)">
+								{{item.courseName}}
+							</view>
+						</view>
+					</view>
+					<view v-else class="p20 fs28 column flex-1 hidden">
+						<scroll-view scroll-y="true" class="hb" :refresher-enabled="isEnabled"
+							:refresher-triggered="triggered" refresher-background="rgba(0,0,0,0)"
+							@refresherrefresh="pullDownRefresh" @refresherrestore="triggered = false"
+							:upper-threshold="100" :lower-threshold="100" @refresherabort="triggered = false"
+							@scrolltolower="reachBottom">
+							<view v-for="(item,index) in courseTwo" :key="item.index"
+								:class="courseids==item.videoId?'actNav':''" class="m10 p10 center"
+								style="border-bottom: 2rpx solid #eee;" @click="getCourseTwo(item.videoId)">
+								{{item.title}}
+							</view>
+							<u-loadmore :status="status" />
+							<view class="ptb40"></view>
+						</scroll-view>
+					</view>
+				</view>
+			</dropdownPanel>
+		</view>
+		<view class="bgf m20 radius8 column flex-1 scrolly" style="max-height: 100%;">
+			<view class="column flex-1 scrolly">
+				<u-collapse ref="collapseRef" :border="false" :value="collapseValue" @change="changeCollapse">
+					<u-collapse-item name="course">
+						<text slot="title" class="bold fs32">课程统计</text>
+						<text slot="value" class="base-color fs24">{{collapseValue.includes('course')?'收回':'展开'}}</text>
+						<view slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</view>
+						<view class="justify-around ">
+							<view class="base-bg-f8 radius16 p30 flex-1">
+								<view class="mb12 bold">营期</view>
+								<view class="base-color"><text class="bold fs40">
+										{{courselist.course}}</text>期</view>
+							</view>
+							<view class="base-bg-f8 radius16 p20 flex-1 mlr20">
+								<view class="mb12 bold">课程数</view>
+								<view class="base-color"><text class="bold fs40">
+										{{courselist.count}}</text>课</view>
+							</view>
+							<view class="base-bg-f8 radius16 p20 flex-1">
+								<view class="mb12 bold">参与会员</view>
+								<view class="base-color"><text class="bold fs40">
+										{{courselist.watchCount}}</text>人</view>
+							</view>
+						</view>
+					</u-collapse-item>
+					<!-- <u-collapse-item name="questions">
+						<text slot="title" class="bold fs32">直播统计</text>
+						<text slot="value"
+							class="base-color fs24">{{collapseValue.includes('questions')?'收回':'展开'}}</text>
+						<view slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</view>
+						<view class="justify-around ">
+							<view class="base-bg-f8 radius16 p30 flex-1">
+								<view class="mb12 bold">观看人次</view>
+								<view class="base-color"><text class="bold fs40">3</text>人</view>
+							</view>
+							<view class="base-bg-f8 radius16 p20 flex-1 mlr20">
+								<view class="mb12 bold">完播人次</view>
+								<view class="base-color"><text class="bold fs40">3</text>人</view>
+							</view>
+							<view class="base-bg-f8 radius16 p20 flex-1">
+								<view class="mb12 bold">完播率</view>
+								<view class="base-color"><text class="bold fs40">3</text>%</view>
+							</view>
+						</view>
+					</u-collapse-item> -->
+					<u-collapse-item name="redenvelope">
+						<text slot="title" class="bold fs32">答题统计</text>
+						<text slot="value"
+							class="base-color fs24">{{collapseValue.includes('redenvelope')?'收回':'展开'}}</text>
+						<view slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</view>
+						<view class="justify-around ">
+							<view class="base-bg-f8 radius16 p30 flex-1">
+								<view class="mb12 bold">答题人次</view>
+								<view class="base-color"><text class="bold fs40">
+										{{answerlist.count}}</text>人</view>
+							</view>
+							<view class="base-bg-f8 radius16 p20 flex-1 mlr20">
+								<view class="mb12 bold">正确人次</view>
+								<view class="base-color"><text class="bold fs40">
+										{{answerlist.completeCount}}</text>人</view>
+							</view>
+							<view class="base-bg-f8 radius16 p20 flex-1">
+								<view class="mb12 bold">正确率</view>
+								<view class="base-color"><text class="bold fs40">
+										{{answerlist.rate}}</text>%</view>
+							</view>
+						</view>
+					</u-collapse-item>
+					<u-collapse-item name="live">
+						<text slot="title" class="bold fs32">红包统计</text>
+						<text slot="value" class="base-color fs24">{{collapseValue.includes('live')?'收回':'展开'}}</text>
+						<view slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</view>
+						<view class="justify-around ">
+							<view class="base-bg-f8 radius16 p30 flex-1">
+								<view class="mb12 bold">红包个数</view>
+								<view class="base-color"><text class="bold fs40">
+										{{redbaglist.count}}</text>个</view>
+							</view>
+							<view class="base-bg-f8 radius16 p20  mlr20 justify-start align-center">
+								<image src="../../../static/images/redenvelope.png" class="w102 h102"></image>
+								<view class="ml20">
+									<view class="mb12 bold">答题红包金额</view>
+									<view class="base-color">
+										<text class="bold fs40">{{redbaglist.amount}}</text>元
+									</view>
+								</view>
+							</view>
+						</view>
+					</u-collapse-item>
+					<u-collapse-item name="funnel">
+						<text slot="title" class="bold fs32">转化漏斗图</text>
+						<text slot="value" class="base-color fs24">{{collapseValue.includes('funnel')?'收回':'展开'}}</text>
+						<view slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</view>
+						<view>
+							<funnelChart :getratelist="getrateimg" />
+						</view>
+					</u-collapse-item>
+					<u-collapse-item name="rank">
+						<text slot="title" class="bold fs32">排行榜</text>
+						<text slot="value" class="base-color fs24">{{collapseValue.includes('rank')?'收回':'展开'}}</text>
+						<view slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</view>
+						<view class="flex-1">
+							<view class="mt40">
+								<view class="justify-between align-center">
+									<view class="base-color-3 bold fs32">群管排行榜</view>
+									<view class="justify-start" @click="ordergroup(1)" v-if="orderGroup=='asc'">
+										<u-icon name="list-dot" size="20"></u-icon>
+										<view>按正序(前20名)</view>
+									</view>
+									<view class="justify-start" @click="ordergroup(0)" v-else>
+										<u-icon name="list-dot" size="20"></u-icon>
+										<view>按倒序(后20名)</view>
+									</view>
+								</view>
+								<view class="centerV">
+									<u-tabs :list="list1" @click="clickTab" lineColor='#1773ff' lineWidth='40'
+										activeStyle="font-weight:bold"></u-tabs>
+								</view>
+								<view class="justify-start align-center mtb28" v-for="(item,index) in rankListA"
+									:key="index">
+									<u-avatar :src="item.avatar" size="40"></u-avatar>
+									<view class="flex-1 ml20">
+										<view class="justify-between mb16">
+											<view class="base-color-3">{{item.name}}</view>
+											<view>{{item.rate}}%</view>
+										</view>
+										<u-line-progress :percentage="30" activeColor='#1773ff' />
+									</view>
+								</view>
+								<view v-if="rankListA.length==0" class="center mtb32">暂无数据</view>
+							</view>
+							<view class="mt60 column flex-1">
+								<view class="justify-between align-center">
+									<view class="base-color-3 bold fs32">课程排行榜</view>
+									<view class="justify-start" @click="ordergroupB(1)" v-if="orderGroupB=='asc'">
+										<u-icon name="list-dot" size="20"></u-icon>
+										<view>按正序(前20名)</view>
+									</view>
+									<view class="justify-start" @click="ordergroupB(0)" v-else>
+										<u-icon name="list-dot" size="20"></u-icon>
+										<view>按倒序(后20名)</view>
+									</view>
+								</view>
+								<view class="centerV">
+									<u-tabs :list="list1" @click="clickTabB" lineColor='#1773ff' lineWidth='40'
+										activeStyle="font-weight:bold"></u-tabs>
+								</view>
+								<view class="justify-start align-center" v-for="(item,index) in rankListB" :key="index">
+									<view class="flex-1 ml20">
+										<view class="justify-between mb16">
+											<view class="base-color-3">{{item.name}}</view>
+											<view>{{item.rate}}%</view>
+										</view>
+										<u-line-progress :percentage="30" activeColor='#ed0922' />
+									</view>
+								</view>
+								<view v-if="rankListB.length==0" class="center mtb32">暂无数据</view>
+							</view>
+						</view>
+					</u-collapse-item>
+				</u-collapse>
+			</view>
+			<view class="h120"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		getFsCourseList,
+		getCourseVdieoList,
+		getshopCoursenum,
+		getQuesCount,
+		getRecPacketCount,
+		getcourseRate,
+		getGroupRanklist,
+		getCourseRanklist
+	} from "@/api/courseManage.js"
+	import dropdownPanel from "../components/dropdownPanel.vue"
+	import parse from "../../../uni_modules/uview-ui/libs/config/props/parse"
+	import funnelChart from "../components/chart.vue"
+	export default {
+		components: {
+			dropdownPanel,
+			funnelChart,
+		},
+		data() {
+			return {
+				activeTab: 0,
+				showCalendar: false,
+				user: {},
+				filterData: [{
+						name: '训练营-营期',
+						value: 1,
+					},
+					{
+						name: '课程',
+						value: 2,
+					}
+				],
+				searchbarNav: 0,
+				collapseValue: ['course', 'questions', 'redenvelope', 'live', 'funnel', 'rank'],
+				courseOne: [],
+				courseTwo: [],
+				courseid: '',
+				courseids: '',
+				triggered: false,
+				status: 'loadmore',
+				isEnabled: true,
+				pageNum: 1,
+				pageSize: 10,
+				courselist: [],
+				answerlist: [],
+				redbaglist: [],
+				getrateimg: {},
+				list1: [{
+					name: '按完播率',
+				}, {
+					name: '按正确率'
+				}],
+				orderGroup: 'asc',
+				orderGroupB: 'asc',
+				rankListA: {},
+				rankListB: {},
+				activeA: 1,
+				activeB: 1,
+			}
+		},
+		async mounted() {
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			this.getCoursestatistics()
+			this.getquslist()
+			this.getredbaglist()
+			this.getrateList()
+			this.getrankgroup()
+			await this.getrankCourse()
+			this.$nextTick(() => {
+				setTimeout(()=>{
+					this.$refs?.collapseRef?.init()
+				},1000)
+			})
+		},
+		methods: {
+			ordergroup(item) {
+				if (item == 0) {
+					this.orderGroup = 'asc'
+					this.getrankgroup()
+				} else {
+					this.orderGroup = 'desc'
+					this.getrankgroup()
+				}
+			},
+			ordergroupB(item) {
+				if (item == 0) {
+					this.orderGroupB = 'asc'
+					this.getrankCourse()
+				} else {
+					this.orderGroupB = 'desc'
+					this.getrankCourse()
+				}
+			},
+			getrankCourse() {
+				//获取课程排行
+				// console.log(this.orderGroupB)
+				const params = {
+					companyId: this.user.companyId,
+					endTime: '',
+					startTime: '',
+					courseId: this.courseid,
+					videoId: this.courseids,
+					order: this.orderGroupB,
+					type: this.activeB, // 0:经销商/1:群管
+				}
+				getCourseRanklist(params).then(res => {
+					if (res.code == 200) {
+						// console.log(res)
+						this.rankListB = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getrankgroup() {
+				//获取群管排行
+				const params = {
+					companyId: this.user.companyId,
+					endTime: '',
+					startTime: '',
+					courseId: this.courseid,
+					videoId: this.courseids,
+					order: this.orderGroup,
+					type: this.activeA, // 0:经销商/1:群管
+				}
+				getGroupRanklist(params).then(res => {
+					if (res.code == 200) {
+						// console.log(res)
+						this.rankListA = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getrateList() {
+				//获取漏斗图
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					endTime: '',
+					startTime: '',
+					courseId: this.courseid,
+					videoId: this.courseids,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getcourseRate(params).then(res => {
+					if (res.code == 200) {
+						this.getrateimg = res
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getredbaglist() {
+				//红包统计
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+					courseId: this.courseid,
+					videoId: this.courseids,
+					endTime: '',
+					startTime: '',
+				}
+				getRecPacketCount(params).then(res => {
+					if (res.code == 200) {
+						// console.log(res)
+						this.redbaglist = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getquslist() {
+				//答题统计
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+					courseId: this.courseid,
+					videoId: this.courseids,
+					endTime: '',
+					startTime: '',
+				}
+				getQuesCount(params).then(res => {
+					if (res.code == 200) {
+						// console.log(res)
+						this.answerlist = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getCoursestatistics() {
+				//课程统计
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+					courseId: this.courseid,
+					// videoId:this.courseids,
+					endTime: '',
+					startTime: '',
+				}
+				getshopCoursenum(params).then(res => {
+					if (res.code == 200) {
+						// console.log(res)
+						this.courselist = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+
+			},
+			pullDownRefresh() {
+				// 下拉
+				this.triggered = true; //下拉了状态为true
+				setTimeout(() => {
+					this.triggered = false;
+					uni.stopPullDownRefresh()
+					this.pageNum = 1;
+					this.getCourseListsmall('refresh') //触底  不穿执行else
+					// 请求接口里面需要判断是不是最后一页   是最后一页 status赋值为‘loadmore’没有更多了
+					// 请求接口
+				}, 1000)
+			},
+			reachBottom() {
+				// status这个是加载状态
+				console.log(111);
+				if (this.status === 'loadmore') {
+					this.status = 'loading'
+					uni.showNavigationBarLoading()
+					setTimeout(() => {
+						this.pageNum++
+						this.getCourseListsmall() //触底  不穿执行else
+						uni.hideNavigationBarLoading()
+					}, 1000);
+				}
+			},
+			getCourseOne(id) {
+				this.courseid = id
+			},
+			getCourseTwo(id) {
+				this.courseids = id
+			},
+			getCourseList() {
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getFsCourseList(param).then(res => {
+					if (res.code == 200) {
+						this.courseOne = res.data
+						// console.log(res)
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			getCourseListsmall(type) {
+				const param = {
+					courseId: this.courseid,
+					pageNum: this.pageNum,
+					pageSize: this.pageSize, //
+					status: ''
+				}
+				getCourseVdieoList(param).then(res => {
+					if (res.code == 200) {
+						// refresh 下拉
+						if (type == 'refresh') {
+							this.courseTwo = res.data.list
+						} else {
+							// 加载更多 当前页和下一页合并
+							this.courseTwo = [...this.courseTwo, ...res.data.list]
+						}
+						if (this.pageNum >= res.data.pages) {
+							this.status = 'nomore'
+						} else {
+							this.status = 'loadmore'
+						}
+					} else {
+
+					}
+				})
+			},
+			changeCollapse(e) {
+				this.collapseValue = e.filter(item => item.status == 'open').map(it => it.name)
+			},
+			handleNav(type) {
+				this.activeTab = type
+			},
+			onChange(index) {
+				this.searchbarNav = index
+				if (index == 0) {
+					this.getCourseList()
+				} else {
+					this.pageNum = 1
+					if (this.courseid == 0) {
+						uni.showToast({
+							icon: 'none',
+							title: "请先选择训练营-营期"
+						})
+					} else {
+						this.getCourseListsmall('refresh')
+					}
+				}
+			},
+			confirm() {
+				this.getCoursestatistics()
+				this.getquslist()
+				this.getredbaglist()
+				this.getrateList()
+				this.getrankgroup()
+				this.getrankCourse()
+			},
+			reset() {
+				console.log(this.searchbarNav)
+				if (this.searchbarNav == 0) {
+					this.courseid = ''
+				} else {
+					this.courseids = ''
+				}
+			},
+			clickTab(item) {
+				this.activeA = item.index + 1
+				this.getrankgroup()
+				console.log(this.activeA)
+			},
+			clickTabB(item) {
+				this.activeB = item.index + 1
+				this.getrankCourse()
+				console.log(this.activeA)
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.headnav {
+		padding: 15px 12px;
+		margin: 0 -10px -10px 0;
+		box-sizing: border-box;
+		background-color: #fff;
+
+		image {
+			height: 60px;
+			width: 50px;
+			position: absolute;
+			z-index: 0;
+			bottom: 0;
+			right: 0;
+			display: none;
+		}
+
+		&-item {
+			flex: 1;
+			font-size: 16px;
+			padding: 10px;
+			border-radius: 10px;
+			background: #f8f8f8;
+			margin: 0 10px 10px 0;
+			position: relative;
+			z-index: 1;
+			overflow: hidden;
+			color: #555;
+		}
+
+		&-active {
+			background-color: rgb(231, 241, 255) !important;
+
+			.headnav-num {
+				color: #1677ff !important;
+			}
+
+			image {
+				display: block !important;
+			}
+		}
+
+		&-num {
+			font-family: DIN, DIN;
+			font-weight: bold;
+			font-size: 25px;
+			margin: 5px 0;
+		}
+	}
+
+	.actNav {
+		color: #1677ff !important;
+		background-color: #e7f2fe;
+	}
+</style>

+ 881 - 669
pages/courseManage/statistics.vue

@@ -1,676 +1,888 @@
-<template>
-	<view class="container">
-		<image class="topbg" :src="info.thumbnail" mode="scaleToFill"></image>
-		<view class="container-body">
-			<view class="coursebox">
-				<view class="coursebox-info">
-					<!-- <view class="status">进行中</view> -->
-					<!-- <view class="status" style="background-color: #ff4746;">已结束</view> -->
-					<view class="coursebox-name">
-						<text class="more-t">{{info.title}}</text>
-						<view class="btn_icon" @click="copyId">ID
-							<image src="@/static/images/copy_icon.png" mode="aspectFill"></image>
-						</view>
-					</view>
-					<!-- <view class="coursebox-name">7.明医有话说-杜老师给您讲讲抑郁杜老师给您讲讲抑郁</view> -->
-					<view style="margin-top: 5px;">{{info.courseName}}</view>
-					<view class="coursebox-time x-start">
-						<view class="coursebox-time-item" style="margin-right: 14px;">
-							<view>直播时间</view>
-							<view class="coursebox-time-t">{{info.createTime}}</view>
-						</view>
-						<view class="coursebox-time-item duration">
-							<view>课程时长</view>
-							<view class="coursebox-time-t">{{$formatSeconds(info.duration || 0,1)}}</view>
-						</view>
-					</view>
-				</view>
-			</view>
-			<view class="statistics">
-				<u-tabs :list="list1" :current='current' :scrollable="false" :lineWidth="40" lineColor="#1677ff" :activeStyle="activeStyle" :inactiveStyle="inactiveStyle" @click="clickTab"></u-tabs>
-				<u-collapse :border='false' :value='collapseValue' @change="changeCollapse" v-if="current == 0">
-					<u-collapse-item name="live">
-						<view slot="title" class="statistics-title">直播数据<text class="statistics-title-tip">2分钟自动更新</text></view>
-						<text slot="value" class="statistics-slot-title">{{collapseValue.includes('live')?'收回':'展开'}}</text>
-						<text slot="right-icon">
-							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
-						</text>
-						<view class="collapse-content x-ac">
-							<view class="collapse-content-item">
-								<view class="collapse-content-title">观看人数</view>
-								<view class="collapse-content-num"><text>{{courseCount.count || 0}}</text>人</view>
-							</view>
-							<view class="collapse-content-item">
-								<view class="collapse-content-title">完播人数</view>
-								<view class="collapse-content-num"><text>{{courseCount.completeCount || 0}}</text>人</view>
-							</view>
-							<view class="collapse-content-item">
-								<view class="collapse-content-title">完播率</view>
-								<view class="collapse-content-num"><text>{{courseCount.rate || 0}}</text>%</view>
-							</view>
-						</view>
-					</u-collapse-item>
-					<u-collapse-item name="questions">
-						<text slot="title" class="statistics-title">答题数据<text class="statistics-title-tip">2分钟自动更新</text></text>
-						<text slot="value" class="statistics-slot-title">{{collapseValue.includes('questions')?'收回':'展开'}}</text>
-						<text slot="right-icon">
-							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
-						</text>
-						<view class="collapse-content">
-							<view class="x-ac">
-								<view class="collapse-content-item">
-									<view class="collapse-content-title">答题人数</view>
-									<view class="collapse-content-num"><text>{{quesCount.count || 0}}</text>人</view>
-								</view>
-								<view class="collapse-content-item">
-									<view class="collapse-content-title">正确人数</view>
-									<view class="collapse-content-num"><text>{{quesCount.completeCount || 0}}</text>人</view>
-								</view>
-								<view class="collapse-content-item">
-									<view class="collapse-content-title">答题红包数</view>
-									<view class="collapse-content-num"><text>{{redPacketCount.count || 0}}</text>个</view>
-								</view>
-							</view>
-							<view class="redenvelope x-bc">
-								<view class="x-f">
-									<image src="@/static/images/redenvelope.png" mode="aspectFill"></image>
-									<text>答题红包金额</text>
-								</view>
-								<view class="collapse-content-num"><text>{{redPacketCount.amount || '0.00'}}</text>元</view>
-							</view>
-						</view>
-					</u-collapse-item>
-					<u-collapse-item name="funnelplot">
-						<text slot="title" class="statistics-title">转化漏斗图<text class="statistics-title-tip">2分钟自动更新</text></text>
-						<text slot="value" class="statistics-slot-title">{{collapseValue.includes('funnelplot')?'收回':'展开'}}</text>
-						<text slot="right-icon">
-							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
-						</text>
-						<view>
-							漏斗
-						</view>
-					</u-collapse-item>
-				</u-collapse>
-			</view>
-			<!-- 参与记录 -->
-			<view v-if="current == 1">
-				<view class="participate-search">
-					<view class="x-bc">
-						<u-search placeholder="会员ID、昵称、姓名、手机" v-model="keyword" :showAction="false" height="30px"></u-search>
-						<u-button type="primary" class="refresh" color='#1677ff' size="small" :disabled="disabled" text="刷新" @click="refresh"></u-button>
-					</view>
-					<view class="x-bc">
-						<u-tabs :list="list2" :current='currentType' :lineWidth="40" lineColor="#1677ff" :activeStyle="activeStyle" :inactiveStyle="inactiveStyle" @click="clickTypeTab"></u-tabs>
-						<view class="participate-order x-f">
-							<image src="@/static/images/order_icon2.png" mode="aspectFill" v-if="searchTypeIndex == 3"></image>
-							<image src="@/static/images/order_icon.png" mode="aspectFill" v-else></image>
-							<picker @change="bindPickerChange" :value="searchTypeIndex" :range="typeArray">
-								{{typeoption[searchTypeIndex]}}
-							</picker>
-						</view>
-					</view>
-				</view>
-				<mescroll-body top="0" bottom="0" ref="mescrollRef" @init="mescrollInit" :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
-					<view class="list">
-						<view class="list-item" v-for="(item,index) in dataList" :key="index">
-							<view class="list-item-head x-bc">
-								<view class="x-f" style="flex: 1;overflow: hidden;">
-									<u-avatar :src='item.avatar'></u-avatar>
-									<view class="list-item-head-l">
-										<view style="flex: 1;overflow: hidden;display: flex;">
-											<text class="list-item-name one-t">{{item.nickName}}</text>
-											<image class="list-item-copy" src="@/static/images/copy_icon.png" mode="aspectFill"></image>
-										</view>
-										<view class="list-item-re">注册时间:{{item.createTime?item.createTime.substring(0,10):'--'}}</view>
-									</view>
-								</view>
-								<image class="phone" src="@/static/logo.png" mode="aspectFill"></image>
-							</view>
-							<view class="list-item-desc">
-								<view class="taglist">
-									<view v-for="(tag,i) in item.tags ? item.tags.split(',') : []">
-										<u-tag :text="tag" size="mini" color="#999" bgColor="#f5f5f5" borderColor="#f5f5f5"></u-tag>
-									</view>
-								</view>
-								<view style="margin-top: 5px;">
-									<text class="label">观看次数</text><text class="value-num">{{item.watchCount || 0}}</text>
-									<text class="label">完播次数</text><text class="value-num">{{item.watchComlpleteCount || 0}}</text>
-									<text class="label">累计时长</text><text class="value-num">{{item.watchTime || '--'}}</text>
-								</view>
-								<view style="margin-top: 5px;">
-									<text class="label">红包领取状态</text>
-									<!-- 发送中  1  已发送 -->
-									<text class="value-num">{{item.redStatus == 1 ? '已发送': item.redStatus == 0 ? '发送中': item.redStatus}}</text>
-								</view>
-								<view style="margin-top: 5px;"><text class="label">观看时间</text><text class="value-num">{{item.watchDate}}</text></view>
-							</view>
-							<view class="list-item-footer x-f">
-								<view class="list-item-footer-btn x-f" @click="openModel('name',item)">改姓名</view>
-								<view class="list-item-footer-btn x-f" @click="openModel('remark',item)">改备注</view>
-								<view class="list-item-footer-btn footer-tagbtn x-f" @click="openModel('tag',item)">改标签</view>
-							</view>
-						</view>
-					</view>
-				</mescroll-body>
-			</view>
-			<!-- 修改名字/备注-->
-			<u-modal :show="modelShow" :title="modelTitle" content='content' class="model" :showCancelButton='true' @cancel="closeModel" @confirm="confirmModel">
-				<view class="setTimebox">
-					<u-input
-						fontSize="14px"
-						:placeholder="modelTitle == '修改姓名' ? '请输入姓名':''"
-						v-model="inputVal"
-						maxlength="5"
-						style="width: 520rpx;"
-					  ></u-input>
-				</view>
-			</u-modal>
-		</view>
+<template>
+	<view class="container">
+		<image class="topbg" :src="info.thumbnail" mode="aspectFill"></image>
+		<view class="container-body">
+			<view class="coursebox">
+				<view class="coursebox-info">
+					<!-- <view class="status">进行中</view> -->
+					<!-- <view class="status" style="background-color: #ff4746;">已结束</view> -->
+					<view class="coursebox-name">
+						<text class="more-t">{{info.title}}</text>
+						<view class="btn_icon" @click="copyId">ID
+							<image src="@/static/images/copy_icon.png" mode="aspectFill"></image>
+						</view>
+					</view>
+					<!-- <view class="coursebox-name">7.明医有话说-杜老师给您讲讲抑郁杜老师给您讲讲抑郁</view> -->
+					<view style="margin-top: 5px;">{{info.courseName}}</view>
+					<view class="coursebox-time x-start">
+						<view class="coursebox-time-item" style="margin-right: 14px;">
+							<view>直播时间</view>
+							<view class="coursebox-time-t">{{info.createTime}}</view>
+						</view>
+						<view class="coursebox-time-item duration">
+							<view>课程时长</view>
+							<view class="coursebox-time-t">{{$formatSeconds(info.duration || 0,1)}}</view>
+						</view>
+					</view>
+				</view>
+			</view>
+			<view class="statistics">
+				<u-tabs :list="list1" :current='current' :scrollable="false" :lineWidth="40" lineColor="#1677ff"
+					:activeStyle="activeStyle" :inactiveStyle="inactiveStyle" @click="clickTab"></u-tabs>
+				<u-collapse :border='false' :value='collapseValue' @change="changeCollapse" v-if="current == 0">
+					<u-collapse-item name="live">
+						<view slot="title" class="statistics-title">直播数据<text
+								class="statistics-title-tip">2分钟自动更新</text></view>
+						<view slot="value"
+							class="statistics-slot-title">{{collapseValue.includes('live')?'收回':'展开'}}</view>
+						<view slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</view>
+						<view class="collapse-content x-ac">
+							<view class="collapse-content-item">
+								<view class="collapse-content-title">观看人数</view>
+								<view class="collapse-content-num"><text>{{courseCount.count || 0}}</text>人</view>
+							</view>
+							<view class="collapse-content-item">
+								<view class="collapse-content-title">完播人数</view>
+								<view class="collapse-content-num"><text>{{courseCount.completeCount || 0}}</text>人
+								</view>
+							</view>
+							<view class="collapse-content-item">
+								<view class="collapse-content-title">完播率</view>
+								<view class="collapse-content-num"><text>{{courseCount.rate || 0}}</text>%</view>
+							</view>
+						</view>
+					</u-collapse-item>
+					<u-collapse-item name="questions">
+						<view slot="title" class="statistics-title">答题数据<view
+								class="statistics-title-tip">2分钟自动更新</view></view>
+						<view slot="value"
+							class="statistics-slot-title">{{collapseValue.includes('questions')?'收回':'展开'}}</view>
+						<view slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</view>
+						<view class="collapse-content">
+							<view class="x-ac">
+								<view class="collapse-content-item">
+									<view class="collapse-content-title">答题人数</view>
+									<view class="collapse-content-num"><text>{{quesCount.count || 0}}</text>人</view>
+								</view>
+								<view class="collapse-content-item">
+									<view class="collapse-content-title">正确人数</view>
+									<view class="collapse-content-num"><text>{{quesCount.completeCount || 0}}</text>人
+									</view>
+								</view>
+								<view class="collapse-content-item">
+									<view class="collapse-content-title">答题红包数</view>
+									<view class="collapse-content-num"><text>{{redPacketCount.count || 0}}</text>个
+									</view>
+								</view>
+							</view>
+							<view class="redenvelope x-bc">
+								<view class="x-f">
+									<image src="@/static/images/redenvelope.png" mode="aspectFill"></image>
+									<text>答题红包金额</text>
+								</view>
+								<view class="collapse-content-num"><text>{{redPacketCount.amount || '0.00'}}</text>元
+								</view>
+							</view>
+						</view>
+					</u-collapse-item>
+					<u-collapse-item name="funnelplot">
+						<view slot="title" class="statistics-title">转化漏斗图<view
+								class="statistics-title-tip">2分钟自动更新</view></view>
+						<view slot="value"
+							class="statistics-slot-title">{{collapseValue.includes('funnelplot')?'收回':'展开'}}</view>
+						<view slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</view>
+						<view style="height: 600rpx;">
+							<funnelChart :getratelist="getrateimg"/>
+						</view>
+					</u-collapse-item>
+				</u-collapse>
+			</view>
+			<!-- 参与记录 -->
+			<view v-if="current == 1">
+				<view class="participate-search">
+					<view class="x-bc">
+						<u-search placeholder="会员ID、昵称、姓名、手机" v-model="keyword" :showAction="false"
+							height="30px"></u-search>
+						<u-button type="primary" class="refresh" color='#1677ff' size="small" :disabled="disabled"
+							text="刷新" @click="refresh"></u-button>
+					</view>
+					<view class="x-bc">
+						<u-tabs :list="list2" :current='currentType' :lineWidth="40" lineColor="#1677ff"
+							:activeStyle="activeStyle" :inactiveStyle="inactiveStyle" @click="clickTypeTab"></u-tabs>
+						<!-- <view class="participate-order x-f">
+							<image src="@/static/images/order_icon2.png" mode="aspectFill" v-if="searchTypeIndex == 3">
+							</image>
+							<image src="@/static/images/order_icon.png" mode="aspectFill" v-else></image>
+							<picker @change="bindPickerChange" :value="searchTypeIndex" :range="typeArray">
+								{{typeoption[searchTypeIndex]}}
+							</picker>
+						</view> -->
+					</view>
+				</view>
+				<mescroll-body top="0" bottom="0" ref="mescrollRef" @init="mescrollInit" :down="downOption"
+					:up="upOption" @down="downCallback" @up="upCallback">
+					<view class="list">
+						<view class="list-item" v-for="(item,index) in dataList" :key="index">
+							<view class="list-item-head x-bc">
+								<view class="x-f" style="flex: 1;overflow: hidden;">
+									<u-avatar :src='item.avatar'></u-avatar>
+									<view class="list-item-head-l">
+										<view style="flex: 1;overflow: hidden;display: flex;">
+											<text class="list-item-name one-t">{{item.nickName}}</text>
+											<image class="list-item-copy" src="@/static/images/copy_icon.png"
+												mode="aspectFill"></image>
+										</view>
+										<view class="list-item-re">
+											注册时间:{{item.createTime?item.createTime.substring(0,10):'--'}}</view>
+									</view>
+								</view>
+								<image class="phone" src="@/static/manergevip/phone.png" mode="aspectFill"></image>
+							</view>
+							<view class="list-item-desc">
+								<view class="taglist">
+									<view v-for="(tag,i) in item.tags ? item.tags.split(',') : []">
+										<u-tag :text="tag" size="mini" color="#999" bgColor="#f5f5f5"
+											borderColor="#f5f5f5"></u-tag>
+									</view>
+								</view>
+								<view style="margin-top: 5px;">
+									<text class="label">观看次数</text><text
+										class="value-num">{{item.watchCount || 0}}</text>
+									<text class="label">完播次数</text><text
+										class="value-num">{{item.watchComlpleteCount || 0}}</text>
+									<text class="label">累计时长</text><text
+										class="value-num">{{item.watchTime || '--'}}</text>
+								</view>
+								<view style="margin-top: 5px;">
+									<text class="label">红包领取状态</text>
+									<!-- 发送中  1  已发送 -->
+									<text
+										class="value-num">{{item.redStatus == 1 ? '已发送': item.redStatus == 0 ? '发送中': item.redStatus}}</text>
+								</view>
+								<view style="margin-top: 5px;"><text class="label">观看时间</text><text
+										class="value-num">{{item.watchDate}}</text></view>
+							</view>
+							<view class="list-item-footer x-f">
+								<view class="list-item-footer-btn x-f" @click="openModel('name',item)">改姓名</view>
+								<view class="list-item-footer-btn x-f" @click="openModel('remark',item)">改备注</view>
+								<view class="list-item-footer-btn footer-tagbtn x-f" @click="openModel('tag',item)">改标签
+								</view>
+							</view>
+						</view>
+					</view>
+				</mescroll-body>
+			</view>
+			<!-- 修改名字/备注-->
+			<u-modal :show="modelShow" :title="modelTitle" content='content' class="model" :showCancelButton='true'
+				@cancel="closeModel" @confirm="confirmModel">
+				<view class="setTimebox">
+					<u-input fontSize="14px" :placeholder="modelTitle == '修改姓名' ? '请输入姓名':''" v-model="inputVal"
+						maxlength="5" style="width: 520rpx;"></u-input>
+				</view>
+			</u-modal>
+			<!-- 修改标签 -->
+			<u-popup :show="showTagSelect"  @close='closetagselect' :closeOnClickOverlay='true' mode="bottom">
+				<view class=" w100 bgf">
+					<view class="plr28 ptb16 justify-between" style="flex-direction: row-reverse">
+						<u-icon class=""  name="close-circle" color="#ccc" size="24" @click="closetagselect"></u-icon>
+					</view>
+					<view class="justify-start p32">
+						<view class="mlr10 " v-for="(item,index) in changetag" :key='item.tagId'>
+							<u-tag :text="item.tag" :plain="!item.checked" :name="index"
+								@click="choosechangeTag"></u-tag>
+						</view>
+					</view>
+					<view class="justify-between p32">
+						<view class="changetagbtn base-bg-f8" @click="showTagSelect=!showTagSelect">取消</view>
+						<view class="changetagbtn colorf base-bg-blue" @click="suretagchangeAll">确定</view>
+					</view>
+				</view>
+			</u-popup>
+		</view>
 	</view>
 </template>
 
-<script>
-	import {getCompanyUserAndUserCount,getCourseCount,getQuesCount,getRecPacketCount,getUserLogListByCourseId,updateFsUser} from "@/api/courseManage.js"
-	export default{
-		data() {
-			return {
-				modelShow: false,
-				modelTitle: '',
-				inputVal: '',
-				info: {},
-				list1: [{
-					name: '课程数据',
-				}, {
-					name: '参与记录',
-				}, {
-					name: '活动节目'
-				}],
-				current: 0,
-				list2: [{
-					name: '答题正确',
-					label: '答题正确',
-					value: 0
-				}, {
-					name: '仅播完',
-					label: '仅播完',
-					value: 2
-				}, {
-					name: '未完播',
-					label: '未完播',
-					value: 1
-				}, {
-					name: '未播放',
-					label: '未播放',
-					value: 3
-				}],
-				currentType: 0,
-				activeStyle:{
-					color: '#1677ff',
-					fontSize: '14px',
-					fontWeight: 'bold'
-				},
-				inactiveStyle:{
-					fontSize: '14px'
-				},
-				collapseValue:['live','questions','funnelplot'],
-				keyword: '',
-				disabled: false,
-				searchTypeIndex: 0,
-				typeArray: ['按看课时间从晚到早', '按完播时间从晚到早', '按注册时间从晚到早','按会员姓名0-9-A-Z'],
-				typeoption: ['看课时间', '完播时间', '注册时间','会员姓名'],
-				typeoptionOrder: ['desc', 'watchDesc', 'registerDesc','nameDesc'],
-				mescroll:null,
-				downOption: {
-					auto:false//不要自动加载
-				},
-				upOption: {
-					onScroll:false,
-					use: true, // 是否启用上拉加载; 默认true
-					page: {
-						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
-						size: 10 // 每页数据的数量,默认10
-					},
-					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
-					textNoMore:"已经到底了",
-					empty: {
-						icon:'/static/images/empty.png',
-						tip: '暂无数据'
-					}
-				},
-				dataList: [],
-				user: {},
-				todayday: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
-				courseCount: {},
-				quesCount: {},
-				redPacketCount: {},
-				courseId: '',
-				chooseUserId: ''
-			}
-		},
-		onLoad(option) {
-			this.info = option.info ? JSON.parse(option.info) : {}
-			uni.setNavigationBarTitle({
-				title: '数据统计'
-			});
-			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
-			this.courseId = this.info.courseId || ''
-			if(this.courseId)
-				this.getCount()
-		},
-		methods: {
-			copyId() {
-				uni.setClipboardData({
-					data: this.info.fileId,
-					success: ()=> {
-						uni.showToast({
-							title: '复制课程ID成功',
-							icon: 'success'
-						})
-					}
-				});
-			},
-			clickTab(e) {
-				this.current = e.index
-			},
-			clickTypeTab(e) {
-				this.currentType = e.index
-				this.mescroll.resetUpScroll()
-			},
-			bindPickerChange(e) {
-				console.log('picker发送选择改变,携带值为', e.detail.value)
-				this.searchTypeIndex = e.detail.value
-			},
-			changeCollapse(e) {
-				this.collapseValue = e.filter(item=>item.status == 'open').map(it=>it.name)
-			},
-			refresh() {
-				this.mescroll.resetUpScroll()
-			},
-			mescrollInit(mescroll) {
-				this.mescroll = mescroll;
-			},
-			/*下拉刷新的回调 */
-			downCallback(mescroll) {
-				mescroll.resetUpScroll()
-			},
-			upCallback(page) {
-				//联网加载数据
-				var that = this;
-				var data={
-					courseId:this.courseId,
-					order: this.typeoptionOrder[this.searchTypeIndex],
-					tagIds: [],
-					type: this.list2[this.currentType].value, // 0:答题正确 1:未完播 2:已完播 3:未播放
-					pageNum: page.num,
-					pageSize: page.size
-				};
-				uni.showLoading({
-					title:"加载中..."
-				})
-				this.list2[this.currentType].name = this.list2[this.currentType].label
-				getUserLogListByCourseId(data).then(res => {
-					uni.hideLoading()
-					if(res.code==200){
-						//设置列表数据
-						if (page.num == 1) {
-							that.dataList = res.data.list; 
-							
-						} else {
-							that.dataList = that.dataList.concat(res.data.list);
-							 
-						}
-						this.list2[this.currentType].name = this.list2[this.currentType].label + (res.data.total)
-						that.mescroll.endBySize(res.data.list.length, res.data.total);
-						
-					}else{
-						uni.showToast({
-							icon:'none',
-							title: "请求失败",
-						});
-						that.dataList = null;
-						that.mescroll.endErr();
-					}
-				});
-			},
-			getCount() {
-				const param = {
-					companyId: this.user.companyId,
-					companyUserId: this.user.userId,
-					courseId: this.courseId,
-					endTime: this.todayday + ' 23:59:59',
-					startTime: this.todayday + ' 00:00:00',
-					type: this.user.userType=='00' ? 0 : 1, // 0:经销商/1:群管
-				}
-				this.getCourseCount(param)
-				this.getQuesCount(param)
-				this.getRecPacketCount(param)
-			},
-			getCourseCount(param){
-				getCourseCount(param).then(res=>{
-					if(res.code==200){
-						this.courseCount = res.data
-					}else{
-						uni.showToast({
-							icon:'none',
-							title: res.msg,
-						});
-					}
-				})
-			},
-			getQuesCount(param){
-				getQuesCount(param).then(res=>{
-					if(res.code==200){
-						this.quesCount = res.data
-					}else{
-						uni.showToast({
-							icon:'none',
-							title: res.msg,
-						});
-					}
-				})
-			},
-			getRecPacketCount(param){
-				getRecPacketCount(param).then(res=>{
-					if(res.code==200){
-						this.redPacketCount = res.data
-					}else{
-						uni.showToast({
-							icon:'none',
-							title: res.msg,
-						});
-					}
-				})
-			},
-			openModel(type,item) {
-				this.chooseUserId = item.userId
				if(type == 'name') {
					this.modelTitle = '修改姓名'
-					this.inputVal = item.nickName || ''
				} else if(type == 'remark') {
					this.modelTitle = '修改备注'
-					this.inputVal = item.remark || ''
				}
				this.modelShow = true
			},
-			closeModel() {
-				this.modelShow = false
-				this.inputVal = ''
-			},
-			confirmModel() {
-				this.modelShow = false
-				if(this.modelTitle == '修改姓名') {
-					const param = {
-						userId: this.chooseUserId,
-						nickName: this.inputVal,
-					}
-					this.updateFsUser(param)
-				} else if(this.modelTitle == '修改备注') {
-					const param = {
-						userId: this.chooseUserId,
-						remark: this.inputVal,
-					}
-					this.updateFsUser(param)
-				}
-			},
-			updateFsUser(param) {
-				console.log(param)
-				updateFsUser(param).then(res=>{
-					if(res.code==200){
-						this.redPacketCount = res.data
-					}else{
-						uni.showToast({
-							icon:'none',
-							title: res.msg,
-						});
-					}
-				})
-			}
-		}
+<script>
+	import {
+		getCompanyUserAndUserCount,
+		getCourseCount,
+		getQuesCount,
+		getRecPacketCount,
+		getUserLogListByCourseId,
+		updateFsUser,
+		getcompanyTaglist,
+		changeLable,
+		changeUserName,
+		getuserdetail,
+		getcourseRate
+	} from "@/api/courseManage.js"
+	import funnelChart from './components/chart.vue'
+	export default {
+		components: {
+			funnelChart,
+		},
+		data() {
+			return {
+				modelShow: false,
+				showTagSelect:false,
+				changetag:'',
+				modelTitle: '',
+				inputVal: '',
+				info: {},
+				list1: [{
+					name: '课程数据',
+				}, {
+					name: '参与记录',
+				}],
+				current: 0,
+				list2: [{
+					name: '答题正确',
+					label: '答题正确',
+					value: 0
+				}, {
+					name: '仅播完',
+					label: '仅播完',
+					value: 2
+				}, {
+					name: '未完播',
+					label: '未完播',
+					value: 1
+				}, {
+					name: '未播放',
+					label: '未播放',
+					value: 3
+				}],
+				currentType: 0,
+				activeStyle: {
+					color: '#1677ff',
+					fontSize: '14px',
+					fontWeight: 'bold'
+				},
+				inactiveStyle: {
+					fontSize: '14px'
+				},
+				collapseValue: ['live', 'questions', 'funnelplot'],
+				keyword: '',
+				disabled: false,
+				searchTypeIndex: 0,
+				typeArray: ['按看课时间从晚到早', '按完播时间从晚到早', '按注册时间从晚到早', '按会员姓名0-9-A-Z'],
+				typeoption: ['看课时间', '完播时间', '注册时间', '会员姓名'],
+				typeoptionOrder: ['desc', 'watchDesc', 'registerDesc', 'nameDesc'],
+				mescroll: null,
+				downOption: {
+					auto: false //不要自动加载
+				},
+				upOption: {
+					onScroll: false,
+					use: true, // 是否启用上拉加载; 默认true
+					page: {
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					textNoMore: "已经到底了",
+					empty: {
+						icon: '/static/images/empty.png',
+						tip: '暂无数据'
+					}
+				},
+				dataList: [],
+				user: {},
+				todayday: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
+				courseCount: {},
+				quesCount: {},
+				redPacketCount: {},
+				courseId: '',
+				chooseUserId: '',
+				companytag:[],
+				selectidtag:[],
+				getrateimg:[]
+			}
+		},
+		onLoad(option) {
+			this.info = option.info ? JSON.parse(option.info) : {}
+			uni.setNavigationBarTitle({
+				title: '数据统计'
+			});
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			this.courseId = this.info.courseId || ''
+			this.getrateList()
+			if (this.courseId)
+				this.getCount()
+		},
+		methods: {
+			getrateList(){
+				//获取漏斗图
+				const params={
+					companyId: this.user.companyId,
+					companyUserId: '',
+					courseId:this.info.courseId,
+					endTime:'',
+					startTime:'',
+					type:this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+					userId:'',
+				}
+				getcourseRate(params).then(res=>{
+					if(res.code==200){
+						this.getrateimg=res
+						console.log(res)
+					}else{
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			suretagchangeAll(){
+				this.selectidtag = this.changetag.filter(item => item.checked).map(v=>v.tagId).join(',')
+				this.changeLabelmore()
+			},
+			changeLabelmore() {
+				//改标签选择按钮
+				const selid=[]
+				selid[0]=this.chooseUserId
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					creatTime: '',
+					createBy: '',
+					createTime: '',
+					id: '',
+					params: {},
+					remark: '',
+					searchValue: '',
+					updateBy: '',
+					tagIds:this.selectidtag,
+					updateTime: '',
+					userId: selid
+				}
+				changeLable(params).then(res => {
+					if (res.code == 200) {
+						const timer = setTimeout(function() {
+							uni.showToast({
+								icon: 'none',
+								title: '标签更改成功'
+							})
+						}, 500);
+						this.showTagSelect = !this.showTagSelect
+						this.mescroll.resetUpScroll()
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			closetagselect() {
+				this.showTagSelect = !this.showTagSelect
+			},
+			choosechangeTag(i) {
+				this.changetag[i].checked = !this.changetag[i].checked
+			},
+			getcompanyTag() {
+				getcompanyTaglist().then(res => {
+					this.showCompanytag = res.data
+					this.companytag = res.data.map(item => {
+						return {
+							...item,
+							checked: false,
+						}
+					})
+					this.changetag=this.companytag
+				})
+			},
+			copyId() {
+				uni.setClipboardData({
+					data: this.info.fileId,
+					success: () => {
+						uni.showToast({
+							title: '复制课程ID成功',
+							icon: 'success'
+						})
+					}
+				});
+			},
+			clickTab(e) {
+				this.current = e.index
+			},
+			clickTypeTab(e) {
+				this.currentType = e.index
+				this.mescroll.resetUpScroll()
+			},
+			bindPickerChange(e) {
+				console.log('picker发送选择改变,携带值为', e.detail.value)
+				this.searchTypeIndex = e.detail.value
+			},
+			changeCollapse(e) {
+				this.collapseValue = e.filter(item => item.status == 'open').map(it => it.name)
+			},
+			refresh() {
+				this.mescroll.resetUpScroll()
+			},
+			mescrollInit(mescroll) {
+				this.mescroll = mescroll;
+			},
+			/*下拉刷新的回调 */
+			downCallback(mescroll) {
+				mescroll.resetUpScroll()
+			},
+			upCallback(page) {
+				//联网加载数据
+				var that = this;
+				var data = {
+					courseId: this.courseId,
+					order: this.typeoptionOrder[this.searchTypeIndex],
+					tagIds: [],
+					type: this.list2[this.currentType].value, // 0:答题正确 1:未完播 2:已完播 3:未播放
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title: "加载中..."
+				})
+				this.list2[this.currentType].name = this.list2[this.currentType].label
+				getUserLogListByCourseId(data).then(res => {
+					uni.hideLoading()
+					if (res.code == 200) {
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list;
+
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
+
+						}
+						this.list2[this.currentType].name = this.list2[this.currentType].label + (res.data.total)
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
+
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
+			},
+			getCount() {
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					courseId: this.courseId,
+					endTime: this.todayday + ' 23:59:59',
+					startTime: this.todayday + ' 00:00:00',
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				this.getCourseCount(param)
+				this.getQuesCount(param)
+				this.getRecPacketCount(param)
+			},
+			getCourseCount(param) {
+				getCourseCount(param).then(res => {
+					if (res.code == 200) {
+						this.courseCount = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			getQuesCount(param) {
+				getQuesCount(param).then(res => {
+					if (res.code == 200) {
+						this.quesCount = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			getRecPacketCount(param) {
+				getRecPacketCount(param).then(res => {
+					if (res.code == 200) {
+						this.redPacketCount = res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			openModel(type, item) {
+				this.chooseUserId = item.userId
+				if (type == 'name') {
+					this.modelTitle = '修改姓名'
+					this.inputVal = item.nickName || ''
+					this.modelShow = true
+				} else if (type == 'remark') {
+					this.modelTitle = '修改备注'
+					this.inputVal = item.remark || ''
+					this.modelShow = true
+				}else{
+					this.getcompanyTag()
+					this.showTagSelect=!this.showTagSelect
+				}
+			},
+			closeModel() {
+				this.modelShow = false
+				this.inputVal = ''
+			},
+			confirmModel() {
+				this.modelShow = false
+				if (this.modelTitle == '修改姓名') {
+					const param = {
+						userId: this.chooseUserId,
+						nickName: this.inputVal,
+					}
+					this.updateFsUser(param)
+					// this.updataName()
+					
+				} else if (this.modelTitle == '修改备注') {
+					const param = {
+						userId: this.chooseUserId,
+						remark: this.inputVal,
+					}
+					this.updateFsUser(param)
+					// this.updataName()
+				}
+			},
+			updateFsUser(param) {
+				console.log(param)
+				updateFsUser(param).then(res => {
+					if (res.code == 200) {
+						uni.showToast({
+							icon: 'none',
+							title: '标签更改成功'
+						})
+						this.redPacketCount = res.data
+						this.mescroll.resetUpScroll()
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg,
+						});
+					}
+				})
+			}
+		}
 	}
 </script>
 
-<style lang="scss" scoped>
-.container {
-	position: relative;
-	z-index: 2;
-	font-family: PingFang SC, PingFang SC;
-	font-weight: 400;
-	font-size: 14px;
-	color: #222;
-	.topbg {
-		position: absolute;
-		z-index: -1;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 210px;
-	}
-	&-body {
-		
-	}
-	.coursebox {
-		padding: 120px 50rpx 20px 50rpx;
-		font-family: PingFang SC, PingFang SC;
-		font-weight: 400;
-		font-size: 14px;
-		color: #999;
-		.status {
-			position: absolute;
-			top: 0;
-			right: 0;
-			z-index: 2;
-			height: 30px;
-			padding: 0 8px;
-			box-sizing: border-box;
-			line-height: 30px;
-			border-radius: 0 12px 0 12px;
-			text-align: center;
-			color: #fff;
-			background-color: #08ce36;
-		}
-		&-info {
-			padding: 12px;
-			background-color: rgba(255,255,255,0.88);
-			border-radius: 12px;
-			overflow: hidden;
-			position: relative;
-		}
-		&-name {
-			color: #222;
-			font-weight: bold;
-			font-size: 16px;
-			padding-right: 50px;
-			.more-t {
-				flex: 1;
-				font-size: 14px;
-				color: #222;
-				display: inline;
-			}
-		}
-		&-time-item {
-			flex: 1;
-			margin-top: 5px;
-		}
-		&-time-t {
-			color: #222;
-			font-weight: bold;
-			margin-top: 5px;
-			font-size: 12px;
-		}
-		.duration {
-			position: relative;
-			padding-left: 14px;
-			&::after {
-				content: "";
-				height: 30px;
-				width: 1px;
-				background-color: #f5f5f5;
-				position: absolute;
-				left: 0;
-				top: 5px;
-			}
-		}
-	}
-}
-.statistics {
-	background-color: #fff;
-	&-title {
-		font-family: PingFang SC, PingFang SC;
-		font-weight: bold;
-		font-size: 16px;
-		color: #222222;
-	}
-	&-title-tip {
-		font-weight: 400;
-		font-size: 11px;
-		color: #999;
-		padding-left: 10px;
-	}
-	&-slot-title {
-		font-size: 12px;
-		color: #1677ff;
-	}
-	.collapse-content {
-		margin: 0 -8px -8rpx 0;
-		&-item {
-			flex: 1;
-			padding: 12px;
-			box-sizing: border-box;
-			border-radius: 10px;
-			background: #f5f5f5;
-			font-family: PingFang SC, PingFang SC;
-			font-weight: 400;
-			font-size: 10px;
-			color: #222222;
-			margin: 0 8px 8rpx 0;
-		}
-		&-title {
-			font-size: 14px;
-			margin-bottom: 10px;
-		}
-		&-num {
-			color: #1677ff;
-			font-size: 10px;
-			text {
-				font-family: DIN, DIN;
-				font-weight: bold;
-				font-size: 25px;
-			}
-		}
-	}
-	.redenvelope {
-		background-color: #f5f5f5;
-		margin-right: 8px;
-		margin-top: 4px;
-		padding: 15px;
-		box-sizing: border-box;
-		border-radius: 10px;
-		color: #222222;
-		image {
-			width: 50px;
-			height: 50px;
-			margin-right: 10px;
-		}
-	}
-}
-.participate-search {
-	background-color: #fff;
-	padding: 10px 15px;
-	.refresh {
-		height: 26px;
-		min-width: 40px;
-		width: 40px;
-		padding: 0;
-		display: inline-flex;
-		margin-left: 15px;
-		border-radius: 5px;
-		overflow: hidden;
-	}
-}
-.participate-order {
-	font-size: 12px;
-	image {
-		width: 15px;
-		height: 15px;
-	}
-}
-.btn_icon {
-	font-weight: 400;
-	font-size: 14px;
-	color: #1677ff;
-	display: inline-flex;
-	align-items: center;
-	margin-left: 5px;
-	image {
-		width: 20px;
-		height: 20px;
-	}
-}
-.list {
-	padding: 20px 10px;
-}
-.list-item{
-	padding: 10px;
-	margin-bottom: 10px;
-	background-color: #fff;
-	border-radius: 12px;
-	&-head {
-		.phone {
-			flex-shrink: 0;
-			width: 30px;
-			height: 30px;
-		}
-		&-l {
-			flex: 1;
-			overflow: hidden;
-			margin-left: 10px;
-			margin-right: 10px;
-		}
-	}
-	&-copy {
-		width: 20px;
-		height: 20px;
-	}
-	&-re {
-		font-size: 10px;
-		margin-top: 5px;
-	}
-	&-desc {
-		padding: 5px;
-		color: #999;
-		font-size: 12px;
-	}
-	.label {
-		margin-right: 4px;
-	}
-	.value-num {
-		margin-right: 18px;
-		color: #222;
-		font-weight: bold;
-	}
-	.taglist {
-		display: flex;
-		align-items: center;
-		flex-wrap: wrap;
-		color: #555;
-		padding-top: 5px;
-		view {
-			margin: 0 5px 5px 0;
-		}
-	}
-	&-footer {
-		justify-content: flex-end;
-		padding: 6px 0;
-		.footer-tagbtn {
-			color: #1677ff;
-			background-color: #e7f2fe;
-			border: 1px solid #c9e1fb;
-		}
-		&-btn {
-			height: 26px;
-			padding: 0 10px;
-			margin-left: 10px;
-			background-color: #f5f5f5;
-			border-radius: 25px;
-			font-weight: 400;
-			font-size: 10px;
-			line-height: 26px;
-			border: 1px solid #f5f5f5;
-			box-sizing: border-box;
-		}
-	}
-}
+<style lang="scss" scoped>
+	.checked-bg {
+		border: 1px solid #1677ff !important;
+		color: #1677ff !important;
+		background-color: #e7f1fe !important;
+	}
+	.changetagbtn{
+		width: 45%;
+		height: 80rpx;
+		border-radius: 50rpx;
+		text-align: center;
+		line-height: 80rpx;
+	}
+	.container {
+		position: relative;
+		z-index: 2;
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 400;
+		font-size: 14px;
+		color: #222;
+
+		.topbg {
+			position: absolute;
+			z-index: -1;
+			top: 0;
+			left: 0;
+			width: 100%;
+			height: 210px;
+		}
+
+		&-body {}
+
+		.coursebox {
+			padding: 120px 50rpx 20px 50rpx;
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 400;
+			font-size: 14px;
+			color: #999;
+
+			.status {
+				position: absolute;
+				top: 0;
+				right: 0;
+				z-index: 2;
+				height: 30px;
+				padding: 0 8px;
+				box-sizing: border-box;
+				line-height: 30px;
+				border-radius: 0 12px 0 12px;
+				text-align: center;
+				color: #fff;
+				background-color: #08ce36;
+			}
+
+			&-info {
+				padding: 12px;
+				background-color: rgba(255, 255, 255, 0.88);
+				border-radius: 12px;
+				overflow: hidden;
+				position: relative;
+			}
+
+			&-name {
+				color: #222;
+				font-weight: bold;
+				font-size: 16px;
+				padding-right: 50px;
+
+				.more-t {
+					flex: 1;
+					font-size: 14px;
+					color: #222;
+					display: inline;
+				}
+			}
+
+			&-time-item {
+				flex: 1;
+				margin-top: 5px;
+			}
+
+			&-time-t {
+				color: #222;
+				font-weight: bold;
+				margin-top: 5px;
+				font-size: 12px;
+			}
+
+			.duration {
+				position: relative;
+				padding-left: 14px;
+
+				&::after {
+					content: "";
+					height: 30px;
+					width: 1px;
+					background-color: #f5f5f5;
+					position: absolute;
+					left: 0;
+					top: 5px;
+				}
+			}
+		}
+	}
+
+	.statistics {
+		background-color: #fff;
+
+		&-title {
+			font-family: PingFang SC, PingFang SC;
+			font-weight: bold;
+			font-size: 16px;
+			color: #222222;
+		}
+
+		&-title-tip {
+			font-weight: 400;
+			font-size: 11px;
+			color: #999;
+			padding-left: 10px;
+		}
+
+		&-slot-title {
+			font-size: 12px;
+			color: #1677ff;
+		}
+
+		.collapse-content {
+			margin: 0 -8px -8rpx 0;
+
+			&-item {
+				flex: 1;
+				padding: 12px;
+				box-sizing: border-box;
+				border-radius: 10px;
+				background: #f5f5f5;
+				font-family: PingFang SC, PingFang SC;
+				font-weight: 400;
+				font-size: 10px;
+				color: #222222;
+				margin: 0 8px 8rpx 0;
+			}
+
+			&-title {
+				font-size: 14px;
+				margin-bottom: 10px;
+			}
+
+			&-num {
+				color: #1677ff;
+				font-size: 10px;
+
+				text {
+					font-family: DIN, DIN;
+					font-weight: bold;
+					font-size: 25px;
+				}
+			}
+		}
+
+		.redenvelope {
+			background-color: #f5f5f5;
+			margin-right: 8px;
+			margin-top: 4px;
+			padding: 15px;
+			box-sizing: border-box;
+			border-radius: 10px;
+			color: #222222;
+
+			image {
+				width: 50px;
+				height: 50px;
+				margin-right: 10px;
+			}
+		}
+	}
+
+	.participate-search {
+		background-color: #fff;
+		padding: 10px 15px;
+
+		.refresh {
+			height: 26px;
+			min-width: 40px;
+			width: 40px;
+			padding: 0;
+			display: inline-flex;
+			margin-left: 15px;
+			border-radius: 5px;
+			overflow: hidden;
+		}
+	}
+
+	.participate-order {
+		font-size: 12px;
+
+		image {
+			width: 15px;
+			height: 15px;
+		}
+	}
+
+	.btn_icon {
+		font-weight: 400;
+		font-size: 14px;
+		color: #1677ff;
+		display: inline-flex;
+		align-items: center;
+		margin-left: 5px;
+
+		image {
+			width: 20px;
+			height: 20px;
+		}
+	}
+
+	.list {
+		padding: 20px 10px;
+	}
+
+	.list-item {
+		padding: 10px;
+		margin-bottom: 10px;
+		background-color: #fff;
+		border-radius: 12px;
+
+		&-head {
+			.phone {
+				flex-shrink: 0;
+				width: 30px;
+				height: 30px;
+			}
+
+			&-l {
+				flex: 1;
+				overflow: hidden;
+				margin-left: 10px;
+				margin-right: 10px;
+			}
+		}
+
+		&-copy {
+			width: 20px;
+			height: 20px;
+		}
+
+		&-re {
+			font-size: 10px;
+			margin-top: 5px;
+		}
+
+		&-desc {
+			padding: 5px;
+			color: #999;
+			font-size: 12px;
+		}
+
+		.label {
+			margin-right: 4px;
+		}
+
+		.value-num {
+			margin-right: 18px;
+			color: #222;
+			font-weight: bold;
+		}
+
+		.taglist {
+			display: flex;
+			align-items: center;
+			flex-wrap: wrap;
+			color: #555;
+			padding-top: 5px;
+
+			view {
+				margin: 0 5px 5px 0;
+			}
+		}
+
+		&-footer {
+			justify-content: flex-end;
+			padding: 6px 0;
+
+			.footer-tagbtn {
+				color: #1677ff;
+				background-color: #e7f2fe;
+				border: 1px solid #c9e1fb;
+			}
+
+			&-btn {
+				height: 26px;
+				padding: 0 10px;
+				margin-left: 10px;
+				background-color: #f5f5f5;
+				border-radius: 25px;
+				font-weight: 400;
+				font-size: 10px;
+				line-height: 26px;
+				border: 1px solid #f5f5f5;
+				box-sizing: border-box;
+			}
+		}
+	}
 </style>

+ 306 - 0
pages/courseManage/vip/ManageDetail.vue

@@ -0,0 +1,306 @@
+<template>
+	<view>
+		<view class="topBgline plr20">
+			<view class="justify-start p30 bgcolf radius12">
+				<u-avatar :src='detailUser.avatar'></u-avatar>
+				<view class="ml16">
+					<view class="bold fs28">{{detailUser.nickName?detailUser.nickName:detailUser.phone}}</view>
+					<view class="fs24 base-color-3">注册时间:{{detailUser.createTime}}</view>
+				</view>
+			</view>
+		</view>
+		<view class="plr20 ptb32 justify-around">
+			<view v-for="(item,index) in typeOption" :key="item.value" class='fs24 base-color-3 ptb4 plr20 radius4'
+				@click="tabActive(index)" :class="tabindex==index?'actsel':'notact'">{{item.label}}</view>
+		</view>
+		<view>
+			<u-collapse :border='false' :value='collapseValue' @change="changeCollapse">
+				<!-- <u-collapse-item name="course">
+					<text slot="title" >直播统计</text>
+					<text slot="value" class="base-color fs24">{{collapseValue.includes('course')?'收回':'展开'}}</text>
+					<view slot="right-icon">
+						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+					</view>
+					<view class="justify-between">
+						<view class="justify-between livebox">
+							<view class="base-color-3">观看次数</view>
+							<view class="base-color fs24"><text class="fs32 bold">0</text>次</view>
+						</view>
+						<view class="livebox justify-between">
+							<view class="base-color-3">完播次数</view>
+							<view class="base-color fs24"><text class="fs32 bold">0</text>次</view>
+						</view>
+					</view>
+				</u-collapse-item> -->
+				<u-collapse-item name="course">
+					<text slot="title">答题统计</text>
+					<text slot="value" class="base-color fs24">{{collapseValue.includes('course')?'收回':'展开'}}</text>
+					<view slot="right-icon">
+						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+					</view>
+					<view class="justify-between">
+						<view class=" liveboxs">
+							<view class="base-color-3">答题次数</view>
+							<view class="base-color fs24">
+								<text class="fs32 bold">{{answerlist.answerCount}}</text>次
+							</view>
+						</view>
+						<view class="liveboxs ">
+							<view class="base-color-3">正确次数</view>
+							<view class="base-color fs24">
+								<text class="fs32 bold">{{answerlist.rightCount}}</text>次
+							</view>
+						</view>
+						<view class="liveboxs ">
+							<view class="base-color-3">答题红包数</view>
+							<view class="base-color fs24">
+								<text class="fs32 bold">{{answerlist.redSize}}</text>次
+							</view>
+						</view>
+					</view>
+				</u-collapse-item>
+			</u-collapse>
+			<view class="justify-start align-center base-bg-f8 mlr30 radius12 plr20 ptb20">
+				<u-image src="@/static/manergevip/book.png" width="30" height="30"></u-image>
+				<view class="ml20">
+					<view class="base-color-3">答题红包金额</view>
+					<view class="base-color fs24"><text class="fs32 bold mr4">
+					{{answerlist.totalAmount==0?'00.00':answerlist.totalAmount}}</text>元</view>
+				</view>
+			</view>
+		</view>
+		<view class="justify-center botfun">
+			<view class="justify-start align-center bottom-btns" style="border: 2rpx solid #ee0a25;"
+			@click="disableUser" :class="this.detailUser.isBlack==0?'':'base-bg-red'">
+				<u-icon name="close-circle" :color="this.detailUser.isBlack==0?'#ee0a25':'#fff'" size="22" ></u-icon>
+				<view class=" ml12" :class="this.detailUser.isBlack==0?'base-color-red':'colorf'">{{answerText}}</view>
+			</view>
+			<!-- <view class="justify-start align-center bottom-btns" style="border: 2rpx solid #1677ff;">
+				<u-icon name="pushpin" color="#1677ff" size="22"></u-icon>
+				<view class="base-color">会员分组</view>
+			</view>
+			<view class="justify-start align-center base-bg bottom-btns">
+				<u-icon name="rmb-circle" color="#fff" size="22"></u-icon>
+				<view class="colorf">新会员奖励</view>
+			</view> -->
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		getanswerlist,
+		Addblacklist,
+		removebalcklist
+	} from "@/api/courseManage.js";
+	export default {
+		data() {
+			return {
+				typeOption: [{
+					label: '全部',
+					value: 0
+				}, {
+					label: '今天',
+					value: 1
+				}, {
+					label: '昨天',
+					value: 2
+				}, {
+					label: '前天',
+					value: 3
+				}, {
+					label: '近七天',
+					value: 4
+				}],
+				tabindex: 0,
+				collapseValue: ['course', 'questions', 'redenvelope'],
+				detailUser: [],
+				user: [],
+				todayday: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
+				startTime: '',
+				endTime: '',
+				answerlist:[],
+				answerText:'禁用',
+				userId:[]
+			}
+		},
+		
+		mounted() {
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			this.detailUser = uni.getStorageSync('detailUser')
+			console.log(this.detailUser)
+			this.getAnswerlists()
+		},
+		methods: {
+			tabActive(index) {
+				console.log(index)
+				this.tabindex = index
+				if(index==0){
+					this.startTime=''
+					 this.endTime=''
+					 this.getAnswerlists()
+				}else if(index==1){
+					this.startTime = this.todayday + ' 00:00:00'
+					this.endTime = this.todayday + ' 23:59:59'
+					this.getAnswerlists()
+				}else if(index==2){
+					let yesterday = new Date();
+					yesterday.setDate(yesterday.getDate() - 1);
+					this.startTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd') + ' 00:00:00'
+					this.endTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd') + ' 23:59:59'
+					this.getAnswerlists()
+				}else if(index==3){
+					let yesterday = new Date();
+					yesterday.setDate(yesterday.getDate() - 2);
+					this.startTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd') + ' 00:00:00'
+					this.endTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd') + ' 23:59:59'
+					this.getAnswerlists()
+				}else{
+					let yesterday = new Date();
+					yesterday.setDate(yesterday.getDate() - 6);
+					this.startTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd') + ' 00:00:00'
+					this.endTime = this.todayday + ' 23:59:59'
+					this.getAnswerlists()
+				}
+			},
+			changeCollapse(e) {
+				this.collapseValue = e.filter(item => item.status == 'open').map(it => it.name)
+			},
+			getAnswerlists() {
+				const params = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					type: this.user.userType == '00' ? 0 : 1, // 0:经销商/1:群管
+					courseId: this.tabindex,
+					startTime: this.startTime,
+					endTime: this.endTime,
+					userId: this.detailUser.userId
+				}
+				getanswerlist(params).then(res => {
+					if (res.code == 200) {
+						this.answerlist=res.data
+					} else {
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			disableUser(){
+				if(this.detailUser.isBlack==0){
+					this.addblack()
+					this.detailUser.isBlack=1
+					this.answerText='已禁用'
+				}else{
+					this.deleteblack()
+					this.detailUser.isBlack=0
+					this.answerText='禁用'
+				}
+			},
+			addblack(){
+				//添加到黑名单
+				this.userId[0]=this.detailUser.userId
+				const params={
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					id:'',
+					userId: this.userId
+				}
+				Addblacklist(params).then(res=>{
+					if(res.code==200){
+						console.log(res)
+						uni.showToast({
+							icon: 'none',
+							title: '用户成功被禁用'
+						})
+						setTimeout(()=>{
+							uni.navigateTo({
+								url:'/pages/courseManage/index'
+							})
+						},500)
+					}else{
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			},
+			deleteblack(){
+				//移除黑名单
+				const params={
+					userId:this.detailUser.userId
+				}
+				removebalcklist(params).then(res=>{
+					if(res.code){
+						uni.showToast({
+							icon: 'none',
+							title: '用户成功取消禁用'
+						})
+						setTimeout(()=>{
+							uni.navigateTo({
+								url:'/pages/courseManage/index'
+							})
+						},500)
+					}else{
+						uni.showToast({
+							icon: 'none',
+							title: res.msg
+						})
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	page {
+		background-color: #fff;
+	}
+
+	.topBgline {
+		background: linear-gradient(to right, rgba(225, 238, 255, 1), rgba(223, 224, 254, 1));
+		padding-top: 40rpx;
+	}
+
+	.bgcolf {
+		background: rgba(255, 255, 255, 0.4);
+	}
+
+	.actsel {
+		background-color: #1773ff;
+		color: #fff;
+		transition: background-color 0.4s ease-in-out;
+	}
+
+	.notact {
+		background-color: #f8f8f8;
+	}
+
+	.livebox {
+		width: calc(50% - 10rpx);
+		background-color: #f8f8f8;
+		padding: 20rpx 10rpx;
+		border-radius: 12rpx;
+	}
+
+	.liveboxs {
+		width: calc(33% - 10rpx);
+		background-color: #f8f8f8;
+		padding: 20rpx 10rpx;
+		border-radius: 12rpx;
+	}
+
+	.bottom-btns {
+		padding: 10rpx 40rpx;
+		border-radius: 50rpx;
+	}
+
+	.botfun {
+		position: fixed;
+		bottom: 32rpx;
+		right: 32rpx;
+	}
+</style>

+ 22 - 0
pages/courseManage/vip/dropDownList.vue

@@ -0,0 +1,22 @@
+<template>
+	<!-- 必须保证父组件高度是确认的 -->
+	<view class="dropd_own_list column">
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.dropd_own_list {
+		height: 100%;
+	}
+</style>

Разлика између датотеке није приказан због своје велике величине
+ 781 - 137
pages/courseManage/vip/index.vue


+ 28 - 0
project.config.json

@@ -0,0 +1,28 @@
+{
+  "appid": "wx93ce67750e3cfba3",
+  "compileType": "miniprogram",
+  "libVersion": "3.7.3",
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "setting": {
+    "coverView": true,
+    "es6": true,
+    "postcss": true,
+    "minified": true,
+    "enhance": true,
+    "showShadowRootInWxmlPanel": true,
+    "packNpmRelationList": [],
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    }
+  },
+  "condition": {},
+  "editorSetting": {
+    "tabIndent": "insertSpaces",
+    "tabSize": 2
+  }
+}

+ 7 - 0
project.private.config.json

@@ -0,0 +1,7 @@
+{
+  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+  "projectname": "company_course_app",
+  "setting": {
+    "compileHotReLoad": true
+  }
+}

BIN
static/image/becomevip.png


BIN
static/image/changePlayer-icon.png


BIN
static/image/course_answer_img.png


BIN
static/image/course_answer_incorrectly_img.png


BIN
static/image/course_arrow_down_icon.png


BIN
static/image/course_arrow_up_icon.png


BIN
static/image/course_close_white_icon.png


BIN
static/image/downicon.png


BIN
static/image/point.png


BIN
static/image/red_envelope_btnimg.png


BIN
static/image/red_envelope_img.png


BIN
static/image/safe.png


BIN
static/image/tc_close_icon.png


BIN
static/image/tips_title_img.png


BIN
static/image/video_icon.png


BIN
static/image/wechat.png


BIN
static/image/wxmore.png



BIN
static/manageTabIcon/data.png


BIN
static/manageTabIcon/data_on.png


BIN
static/manageTabIcon/liveclasses.png


BIN
static/manageTabIcon/liveclasses_on.png


BIN
static/manageTabIcon/manage.png


BIN
static/manageTabIcon/manage_on.png


BIN
static/manageTabIcon/training.png


BIN
static/manageTabIcon/training_on.png


BIN
static/manageTabIcon/vip.png


BIN
static/manageTabIcon/vip_on.png


BIN
static/manergevip/Refresh.png


BIN
static/manergevip/becomeTrue.png


BIN
static/manergevip/book.png


BIN
static/manergevip/no-vip.png


BIN
static/manergevip/phone.png


BIN
static/manergevip/vip.png


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
uni_modules/lime-painter/components/l-painter/painter.js


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

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

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
uni_modules/lime-painter/components/l-painter/single.js


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

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

Разлика између датотеке није приказан због своје велике величине
+ 166 - 0
uni_modules/lime-painter/components/lime-painter/lime-painter.vue


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

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

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
uni_modules/lime-painter/hybrid/html/painter.js


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
uni_modules/lime-painter/hybrid/html/uni.webview.1.5.3.js


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

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

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

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

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

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

+ 320 - 0
uni_modules/qiun-data-charts/changelog.md

@@ -0,0 +1,320 @@
+## 2.5.0-20230101(2023-01-01)
+- 秋云图表组件 修改条件编译顺序,确保uniapp的cli方式的项目依赖不完整时可以正常显示
+- 秋云图表组件 恢复props属性directory的使用,以修复vue3项目中,开启echarts后,echarts目录识别错误的bug
+- uCharts.js 修复区域图、混合图只有一个数据时图表显示不正确的bug
+- uCharts.js 修复折线图、区域图中时间轴类别图表tooltip指示点显示不正确的bug
+- uCharts.js 修复x轴使用labelCount时,并且boundaryGap = 'justify' 并且关闭Y轴显示的时候,最后一个坐标值不显示的bug
+- uCharts.js 修复折线图只有一组数据时 ios16 渲染颜色不正确的bug
+- uCharts.js 修复玫瑰图半径显示不正确的bug
+- uCharts.js 柱状图、山峰图增加正负图功能,y轴网格如果需要显示0轴则由 min max 及 splitNumber 确定,后续版本优化自动显示0轴
+- uCharts.js 柱状图column增加 opts.extra.column.labelPosition,数据标签位置,有效值为 outside外部, insideTop内顶部, center内中间, bottom内底部
+- uCharts.js 雷达图radar增加 opts.extra.radar.labelShow,否显示各项标识文案是,默认true
+- uCharts.js 提示窗tooltip增加 opts.extra.tooltip.boxPadding,提示窗边框填充距离,默认3px
+- uCharts.js 提示窗tooltip增加 opts.extra.tooltip.fontSize,提示窗字体大小配置,默认13px
+- uCharts.js 提示窗tooltip增加 opts.extra.tooltip.lineHeight,提示窗文字行高,默认20px
+- uCharts.js 提示窗tooltip增加 opts.extra.tooltip.legendShow,是否显示左侧图例,默认true
+- uCharts.js 提示窗tooltip增加 opts.extra.tooltip.legendShape,图例形状,图例标识样式,有效值为 auto自动跟随图例, diamond◆, circle●, triangle▲, square■, rect▬, line-
+- uCharts.js 标记线markLine增加 opts.extra.markLine.labelFontSize,字体大小配置,默认13px
+- uCharts.js 标记线markLine增加 opts.extra.markLine.labelPadding,标签边框内填充距离,默认6px
+- uCharts.js 折线图line增加 opts.extra.line.linearType,渐变色类型,可选值 none关闭渐变色,custom 自定义渐变色。使用自定义渐变色时请赋值serie.linearColor作为颜色值
+- uCharts.js 折线图line增加 serie.linearColor,渐变色数组,格式为2维数组[起始位置,颜色值],例如[[0,'#0EE2F8'],[0.3,'#2BDCA8'],[0.6,'#1890FF'],[1,'#9A60B4']]
+- uCharts.js 折线图line增加 opts.extra.line.onShadow,是否开启折线阴影,开启后请赋值serie.setShadow阴影设置
+- uCharts.js 折线图line增加 serie.setShadow,阴影配置,格式为4位数组:[offsetX,offsetY,blur,color]
+- uCharts.js 折线图line增加 opts.extra.line.animation,动画效果方向,可选值为vertical 垂直动画效果,horizontal 水平动画效果
+- uCharts.js X轴xAxis增加 opts.xAxis.lineHeight,X轴字体行高,默认20px
+- uCharts.js X轴xAxis增加 opts.xAxis.marginTop,X轴文字距离轴线的距离,默认0px
+- uCharts.js X轴xAxis增加 opts.xAxis.title,当前X轴标题
+- uCharts.js X轴xAxis增加 opts.xAxis.titleFontSize,标题字体大小,默认13px
+- uCharts.js X轴xAxis增加 opts.xAxis.titleOffsetY,标题纵向偏移距离,负数为向上偏移,正数向下偏移
+- uCharts.js X轴xAxis增加 opts.xAxis.titleOffsetX,标题横向偏移距离,负数为向左偏移,正数向右偏移
+- uCharts.js X轴xAxis增加 opts.xAxis.titleFontColor,标题字体颜色,默认#666666
+
+## 报错TypeError: Cannot read properties of undefined (reading 'length')
+- 如果是uni-modules版本组件,请先登录HBuilderX账号;
+- 在HBuilderX中的manifest.json,点击重新获取uniapp的appid,或者删除appid重新粘贴,重新运行;
+- 如果是cli项目请使用码云上的非uniCloud版本组件;
+- 或者添加uniCloud的依赖;
+- 或者使用原生uCharts;
+## 2.4.5-20221130(2022-11-30)
+- uCharts.js 优化tooltip当文字很多变为左侧显示时,如果画布仍显显示不下,提示框错位置变为以左侧0位置起画
+- uCharts.js 折线图修复特殊情况下只有单点数据,并改变线宽后点变为圆形的bug
+- uCharts.js 修复Y轴disabled启用后无效并报错的bug
+- uCharts.js 修复仪表盘起始结束角度特殊情况下显示不正确的bug
+- uCharts.js 雷达图新增参数 opts.extra.radar.radius , 自定义雷达图半径
+- uCharts.js 折线图、区域图增加tooltip指示点,opts.extra.line.activeType/opts.extra.area.activeType,可选值"none"不启用激活指示点,"hollow"空心点模式,"solid"实心点模式
+## 2.4.4-20221102(2022-11-02)
+- 秋云图表组件 修复使用echarts时reload、reshow无法调用重新渲染的bug,[详见码云PR](https://gitee.com/uCharts/uCharts/pulls/40)
+- 秋云图表组件 修复使用echarts时,初始化时宽高不正确的bug,[详见码云PR](https://gitee.com/uCharts/uCharts/pulls/42)
+- 秋云图表组件 修复uniapp的h5使用history模式时,无法加载echarts的bug
+- 秋云图表组件 小程序端@complete、@scrollLeft、@scrollRight、@getTouchStart、@getTouchMove、@getTouchEnd事件增加opts参数传出,方便一些特殊需求的交互获取数据。
+
+- uCharts.js 修复calTooltipYAxisData方法内formatter格式化方法未与y轴方法同步的问题,[详见码云PR](https://gitee.com/uCharts/uCharts/pulls/43)
+- uCharts.js 地图新增参数opts.series[i].fillOpacity,以透明度方式来设置颜色过度效果,[详见码云PR](https://gitee.com/uCharts/uCharts/pulls/38)
+- uCharts.js 地图新增参数opts.extra.map.active,是否启用点击激活变色
+- uCharts.js 地图新增参数opts.extra.map.activeTextColor,是否启用点击激活变色
+- uCharts.js 地图新增渲染完成事件renderComplete
+- uCharts.js 漏斗图修复当部分数据相同时tooltip提示窗点击错误的bug
+- uCharts.js 漏斗图新增参数series.data[i].centerText 居中标签文案
+- uCharts.js 漏斗图新增参数series.data[i].centerTextSize 居中标签文案字体大小,默认opts.fontSize
+- uCharts.js 漏斗图新增参数series.data[i].centerTextColor 居中标签文案字体颜色,默认#FFFFFF
+- uCharts.js 漏斗图新增参数opts.extra.funnel.minSize 最小值的最小宽度,默认0
+- uCharts.js 进度条新增参数opts.extra.arcbar.direction,动画方向,可选值为cw顺时针、ccw逆时针
+- uCharts.js 混合图新增参数opts.extra.mix.line.width,折线的宽度,默认2
+- uCharts.js 修复tooltip开启horizentalLine水平横线标注时,图表显示错位的bug
+- uCharts.js 优化tooltip当文字很多变为左侧显示时,如果画布仍显显示不下,提示框错位置变为以左侧0位置起画
+- uCharts.js 修复开启滚动条后X轴文字超出绘图区域后的隐藏逻辑
+- uCharts.js 柱状图、条状图修复堆叠模式不能通过{value,color}赋值单个柱子颜色的问题
+- uCharts.js 气泡图修复不识别series.textSize和series.textColor的bug
+
+## 报错TypeError: Cannot read properties of undefined (reading 'length')
+1. 如果是uni-modules版本组件,请先登录HBuilderX账号;
+2. 在HBuilderX中的manifest.json,点击重新获取uniapp的appid,或者删除appid重新粘贴,重新运行;
+3. 如果是cli项目请使用码云上的非uniCloud版本组件;
+4. 或者添加uniCloud的依赖;
+5. 或者使用原生uCharts;
+## 2.4.3-20220505(2022-05-05)
+- 秋云图表组件 修复开启canvas2d后将series赋值为空数组显示加载图标时,再次赋值后画布闪动的bug
+- 秋云图表组件 修复升级hbx最新版后ECharts的highlight方法报错的bug
+- uCharts.js 雷达图新增参数opts.extra.radar.gridEval,数据点位网格抽希,默认1
+- uCharts.js 雷达图新增参数opts.extra.radar.axisLabel,	是否显示刻度点值,默认false
+- uCharts.js 雷达图新增参数opts.extra.radar.axisLabelTofix,刻度点值小数位数,默认0
+- uCharts.js 雷达图新增参数opts.extra.radar.labelPointShow,是否显示末端刻度圆点,默认false
+- uCharts.js 雷达图新增参数opts.extra.radar.labelPointRadius,刻度圆点的半径,默认3
+- uCharts.js 雷达图新增参数opts.extra.radar.labelPointColor,刻度圆点的颜色,默认#cccccc
+- uCharts.js 雷达图新增参数opts.extra.radar.linearType,渐变色类型,可选值"none"关闭渐变,"custom"开启渐变
+- uCharts.js 雷达图新增参数opts.extra.radar.customColor,自定义渐变颜色,数组类型对应series的数组长度以匹配不同series颜色的不同配色方案,例如["#FA7D8D", "#EB88E2"]
+- uCharts.js 雷达图优化支持series.textColor、series.textSize属性
+- uCharts.js 柱状图中温度计式图标,优化支持全圆角类型,修复边框有缝隙的bug,详见官网【演示】中的温度计图表
+- uCharts.js 柱状图新增参数opts.extra.column.activeWidth,当前点击柱状图的背景宽度,默认一个单元格单位
+- uCharts.js 混合图增加opts.extra.mix.area.gradient 区域图是否开启渐变色
+- uCharts.js 混合图增加opts.extra.mix.area.opacity 区域图透明度,默认0.2
+- uCharts.js 饼图、圆环图、玫瑰图、漏斗图,增加opts.series[0].data[i].labelText,自定义标签文字,避免formatter格式化的繁琐,详见官网【演示】中的饼图
+- uCharts.js 饼图、圆环图、玫瑰图、漏斗图,增加opts.series[0].data[i].labelShow,自定义是否显示某一个指示标签,避免因饼图类别太多导致标签重复或者居多导致图形变形的问题,详见官网【演示】中的饼图
+- uCharts.js 增加opts.series[i].legendText/opts.series[0].data[i].legendText(与series.name同级)自定义图例显示文字的方法
+- uCharts.js 优化X轴、Y轴formatter格式化方法增加形参,统一为fromatter:function(value,index,opts){}
+- uCharts.js 修复横屏模式下无法使用双指缩放方法的bug
+- uCharts.js 修复当只有一条数据或者多条数据值相等的时候Y轴自动计算的最大值错误的bug
+- 【官网模板】增加外部自定义图例与图表交互的例子,[点击跳转](https://www.ucharts.cn/v2/#/layout/info?id=2)
+
+## 注意:非unimodules 版本如因更新 hbx 至 3.4.7 导致报错如下,请到码云更新非 unimodules 版本组件,[点击跳转](https://gitee.com/uCharts/uCharts/tree/master/uni-app/uCharts-%E7%BB%84%E4%BB%B6)
+> Error in callback for immediate watcher "uchartsOpts": "SyntaxError: Unexpected token u in JSON at position 0"
+## 2.4.2-20220421(2022-04-21)
+- 秋云图表组件 修复HBX升级3.4.6.20220420版本后echarts报错的问题
+## 2.4.2-20220420(2022-04-20)
+## 重要!此版本uCharts新增了很多功能,修复了诸多已知问题
+- 秋云图表组件 新增onzoom开启双指缩放功能(仅uCharts),前提需要直角坐标系类图表类型,并且ontouch为true、opts.enableScroll为true,详见实例项目K线图
+- 秋云图表组件 新增optsWatch是否监听opts变化,关闭optsWatch后,动态修改opts不会触发图表重绘
+- 秋云图表组件 修复开启canvas2d功能后,动态更新数据后画布闪动的bug
+- 秋云图表组件 去除directory属性,改为自动获取echarts.min.js路径(升级不受影响)
+- 秋云图表组件 增加getImage()方法及@getImage事件,通过ref调用getImage()方法获,触发@getImage事件获取当前画布的base64图片文件流。
+- 秋云图表组件 支付宝、字节跳动、飞书、快手小程序支持开启canvas2d同层渲染设置。
+- 秋云图表组件 新增加【非uniCloud】版本组件,避免有些不需要uniCloud的使用组件发布至小程序需要提交隐私声明问题,请到码云[【非uniCloud版本】](https://gitee.com/uCharts/uCharts/tree/master/uni-app/uCharts-%E7%BB%84%E4%BB%B6),或npm[【非uniCloud版本】](https://www.npmjs.com/package/@qiun/uni-ucharts)下载使用。
+- uCharts.js 新增dobuleZoom双指缩放功能
+- uCharts.js 新增山峰图type="mount",数据格式为饼图类格式,不需要传入categories,具体详见新版官网在线演示
+- uCharts.js 修复折线图当数据中存在null时tooltip报错的bug
+- uCharts.js 修复饼图类当画布比较小时自动计算的半径是负数报错的bug
+- uCharts.js 统一各图表类型的series.formatter格式化方法的形参为(val, index, series, opts),方便格式化时有更多参数可用
+- uCharts.js 标记线功能增加labelText自定义显示文字,增加labelAlign标签显示位置(左侧或右侧),增加标签显示位置微调labelOffsetX、labelOffsetY
+- uCharts.js 修复条状图当数值很小时开启圆角后样式错误的bug
+- uCharts.js 修复X轴开启disabled后,X轴仍占用空间的bug
+- uCharts.js 修复X轴开启滚动条并且开启rotateLabel后,X轴文字与滚动条重叠的bug
+- uCharts.js 增加X轴rotateAngle文字旋转自定义角度,取值范围(-90至90)
+- uCharts.js 修复地图文字标签层级显示不正确的bug
+- uCharts.js 修复饼图、圆环图、玫瑰图当数据全部为0的时候不显示数据标签的bug
+- uCharts.js 修复当opts.padding上边距为0时,Y轴顶部刻度标签位置不正确的bug
+
+## 另外我们还开发了各大原生小程序组件,已发布至码云和npm
+[https://gitee.com/uCharts/uCharts](https://gitee.com/uCharts/uCharts)
+[https://www.npmjs.com/~qiun](https://www.npmjs.com/~qiun)
+
+## 对于原生uCharts文档我们已上线新版官方网站,详情点击下面链接进入官网
+[https://www.uCharts.cn/v2/](https://www.ucharts.cn/v2/)
+## 2.3.7-20220122(2022-01-22)
+## 重要!使用vue3编译,请使用cli模式并升级至最新依赖,HbuilderX编译需要使用3.3.8以上版本
+- uCharts.js 修复uni-app平台组件模式使用vue3编译到小程序报错的bug。
+## 2.3.7-20220118(2022-01-18)
+## 注意,使用vue3的前提是需要3.3.8.20220114-alpha版本的HBuilder!
+## 2.3.67-20220118(2022-01-18)
+- 秋云图表组件 组件初步支持vue3,全端编译会有些问题,具体详见下面修改:
+1. 小程序端运行时,在uni_modules文件夹的qiun-data-charts.js中搜索 new uni_modules_qiunDataCharts_js_sdk_uCharts_uCharts.uCharts,将.uCharts去掉。
+2. 小程序端发行时,在uni_modules文件夹的qiun-data-charts.js中搜索 new e.uCharts,将.uCharts去掉,变为 new e。
+3. 如果觉得上述步骤比较麻烦,如果您的项目只编译到小程序端,可以修改u-charts.js最后一行导出方式,将 export default uCharts;变更为 export default { uCharts: uCharts }; 这样变更后,H5和App端的renderjs会有问题,请开发者自行选择。(此问题非组件问题,请等待DC官方修复Vue3的小程序端)
+## 2.3.6-20220111(2022-01-11)
+- 秋云图表组件 修改组件 props 属性中的 background 默认值为 rgba(0,0,0,0)
+## 2.3.6-20211201(2021-12-01)
+- uCharts.js 修复bar条状图开启圆角模式时,值很小时圆角渲染错误的bug
+## 2.3.5-20211014(2021-10-15)
+- uCharts.js 增加vue3的编译支持(仅原生uCharts,qiun-data-charts组件后续会支持,请关注更新)
+## 2.3.4-20211012(2021-10-12)
+- 秋云图表组件 修复 mac os x 系统 mouseover 事件丢失的 bug
+## 2.3.3-20210706(2021-07-06)
+- uCharts.js 增加雷达图开启数据点值(opts.dataLabel)的显示
+## 2.3.2-20210627(2021-06-27)
+- 秋云图表组件 修复tooltipCustom个别情况下传值不正确报错TypeError: Cannot read property 'name' of undefined的bug
+## 2.3.1-20210616(2021-06-16)
+- uCharts.js 修复圆角柱状图使用4角圆角时,当数值过大时不正确的bug
+## 2.3.0-20210612(2021-06-12)
+- uCharts.js 【重要】uCharts增加nvue兼容,可在nvue项目中使用gcanvas组件渲染uCharts,[详见码云uCharts-demo-nvue](https://gitee.com/uCharts/uCharts)
+- 秋云图表组件 增加tapLegend属性,是否开启图例点击交互事件
+- 秋云图表组件 getIndex事件中增加返回uCharts实例中的opts参数,以便在页面中调用参数
+- 示例项目 pages/other/other.vue增加app端自定义tooltip的方法,详见showOptsTooltip方法
+## 2.2.1-20210603(2021-06-03)
+- uCharts.js 修复饼图、圆环图、玫瑰图,当起始角度不为0时,tooltip位置不准确的bug
+- uCharts.js 增加温度计式柱状图开启顶部半圆形的配置
+## 2.2.0-20210529(2021-05-29)
+- uCharts.js 增加条状图type="bar"
+- 示例项目 pages/ucharts/ucharts.vue增加条状图的demo
+## 2.1.7-20210524(2021-05-24)
+- uCharts.js 修复大数据量模式下曲线图不平滑的bug
+## 2.1.6-20210523(2021-05-23)
+- 秋云图表组件 修复小程序端开启滚动条更新数据后滚动条位置不符合预期的bug
+## 2.1.5-2021051702(2021-05-17)
+- uCharts.js 修复自定义Y轴min和max值为0时不能正确显示的bug
+## 2.1.5-20210517(2021-05-17)
+- uCharts.js 修复Y轴自定义min和max时,未按指定的最大值最小值显示坐标轴刻度的bug
+## 2.1.4-20210516(2021-05-16)
+- 秋云图表组件 优化onWindowResize防抖方法
+- 秋云图表组件 修复APP端uCharts更新数据时,清空series显示loading图标后再显示图表,图表抖动的bug
+- uCharts.js 修复开启canvas2d后,x轴、y轴、series自定义字体大小未按比例缩放的bug
+- 示例项目 修复format-e.vue拼写错误导致app端使用uCharts渲染图表
+## 2.1.3-20210513(2021-05-13)
+- 秋云图表组件 修改uCharts变更chartData数据为updateData方法,支持带滚动条的数据动态打点
+- 秋云图表组件 增加onWindowResize防抖方法 fix by ど誓言,如尘般染指流年づ 
+- 秋云图表组件 H5或者APP变更chartData数据显示loading图表时,原数据闪现的bug
+- 秋云图表组件 props增加errorReload禁用错误点击重新加载的方法
+- uCharts.js 增加tooltip显示category(x轴对应点位)标题的功能,opts.extra.tooltip.showCategory,默认为false
+- uCharts.js 修复mix混合图只有柱状图时,tooltip的分割线显示位置不正确的bug
+- uCharts.js 修复开启滚动条,图表在拖动中动态打点,滚动条位置不正确的bug
+- uCharts.js 修复饼图类数据格式为echarts数据格式,series为空数组报错的bug
+- 示例项目 修改uCharts.js更新到v2.1.2版本后,@getIndex方法获取索引值变更为e.currentIndex.index
+- 示例项目 pages/updata/updata.vue增加滚动条拖动更新(数据动态打点)的demo
+- 示例项目 pages/other/other.vue增加errorReload禁用错误点击重新加载的demo
+## 2.1.2-20210509(2021-05-09)
+秋云图表组件 修复APP端初始化时就传入chartData或lacaldata不显示图表的bug
+## 2.1.1-20210509(2021-05-09)
+- 秋云图表组件 变更ECharts的eopts配置在renderjs内执行,支持在config-echarts.js配置文件内写function配置。
+- 秋云图表组件 修复APP端报错Prop being mutated: "onmouse"错误的bug。
+- 秋云图表组件 修复APP端报错Error: Not Found:Page[6][-1,27] at view.umd.min.js:1的bug。
+## 2.1.0-20210507(2021-05-07)
+- 秋云图表组件 修复初始化时就有数据或者数据更新的时候loading加载动画闪动的bug
+- uCharts.js 修复x轴format方法categories为字符串类型时返回NaN的bug
+- uCharts.js 修复series.textColor、legend.fontColor未执行全局默认颜色的bug
+## 2.1.0-20210506(2021-05-06)
+- 秋云图表组件 修复极个别情况下报错item.properties undefined的bug
+- 秋云图表组件 修复极个别情况下关闭加载动画reshow不起作用,无法显示图表的bug
+- 示例项目 pages/ucharts/ucharts.vue 增加时间轴折线图(type="tline")、时间轴区域图(type="tarea")、散点图(type="scatter")、气泡图demo(type="bubble")、倒三角形漏斗图(opts.extra.funnel.type="triangle")、金字塔形漏斗图(opts.extra.funnel.type="pyramid")
+- 示例项目 pages/format-u/format-u.vue 增加X轴format格式化示例
+- uCharts.js 升级至v2.1.0版本
+- uCharts.js 修复 玫瑰图面积模式点击tooltip位置不正确的bug
+- uCharts.js 修复 玫瑰图点击图例,只剩一个类别显示空白的bug
+- uCharts.js 修复 饼图类图点击图例,其他图表tooltip位置某些情况下不准的bug
+- uCharts.js 修复 x轴为矢量轴(时间轴)情况下,点击tooltip位置不正确的bug
+- uCharts.js 修复 词云图获取点击索引偶尔不准的bug
+- uCharts.js 增加 直角坐标系图表X轴format格式化方法(原生uCharts.js用法请使用formatter)
+- uCharts.js 增加 漏斗图扩展配置,倒三角形(opts.extra.funnel.type="triangle"),金字塔形(opts.extra.funnel.type="pyramid")
+- uCharts.js 增加 散点图(opts.type="scatter")、气泡图(opts.type="bubble")
+- 后期计划 完善散点图、气泡图,增加markPoints标记点,增加横向条状图。
+## 2.0.0-20210502(2021-05-02)
+- uCharts.js 修复词云图获取点击索引不正确的bug
+## 2.0.0-20210501(2021-05-01)
+- 秋云图表组件 修复QQ小程序、百度小程序在关闭动画效果情况下,v-for循环使用图表,显示不正确的bug
+## 2.0.0-20210426(2021-04-26)
+- 秋云图表组件 修复QQ小程序不支持canvas2d的bug
+- 秋云图表组件 修复钉钉小程序某些情况点击坐标计算错误的bug
+- uCharts.js 增加 extra.column.categoryGap 参数,柱状图类每个category点位(X轴点)柱子组之间的间距
+- uCharts.js 增加 yAxis.data[i].titleOffsetY 参数,标题纵向偏移距离,负数为向上偏移,正数向下偏移
+- uCharts.js 增加 yAxis.data[i].titleOffsetX 参数,标题横向偏移距离,负数为向左偏移,正数向右偏移
+- uCharts.js 增加 extra.gauge.labelOffset 参数,仪表盘标签文字径向便宜距离,默认13px
+## 2.0.0-20210422-2(2021-04-22)
+秋云图表组件 修复 formatterAssign 未判断 args[key] == null 的情况导致栈溢出的 bug
+## 2.0.0-20210422(2021-04-22)
+- 秋云图表组件 修复H5、APP、支付宝小程序、微信小程序canvas2d模式下横屏模式的bug
+## 2.0.0-20210421(2021-04-21)
+- uCharts.js 修复多行图例的情况下,图例在上方或者下方时,图例float为左侧或者右侧时,第二行及以后的图例对齐方式不正确的bug
+## 2.0.0-20210420(2021-04-20)
+- 秋云图表组件 修复微信小程序开启canvas2d模式后,windows版微信小程序不支持canvas2d模式的bug
+- 秋云图表组件 修改非uni_modules版本为v2.0版本qiun-data-charts组件
+## 2.0.0-20210419(2021-04-19)
+## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。
+## 初次使用如果提示未注册&lt;qiun-data-charts&gt;组件,请重启HBuilderX,如仍不好用,请重启电脑;
+## 如果是cli项目,请尝试清理node_modules,重新install,还不行就删除项目,再重新install。
+## 此问题已于DCloud官方确认,HBuilderX下个版本会修复。
+## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn)
+## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍,右侧蓝色按钮示例项目请看2遍! </font> 
+## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn)
+## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) 
+- uCharts.js 修复混合图中柱状图单独设置颜色不生效的bug
+- uCharts.js 修复多Y轴单独设置fontSize时,开启canvas2d后,未对应放大字体的bug
+## 2.0.0-20210418(2021-04-18)
+- 秋云图表组件 增加directory配置,修复H5端history模式下如果发布到二级目录无法正确加载echarts.min.js的bug
+## 2.0.0-20210416(2021-04-16)
+## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。
+## 初次使用如果提示未注册&lt;qiun-data-charts&gt;组件,请重启HBuilderX,如仍不好用,请重启电脑;
+## 如果是cli项目,请尝试清理node_modules,重新install,还不行就删除项目,再重新install。
+## 此问题已于DCloud官方确认,HBuilderX下个版本会修复。
+## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn)
+## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍,右侧蓝色按钮示例项目请看2遍! </font> 
+## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn)
+## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) 
+- 秋云图表组件 修复APP端某些情况下报错`Not Found Page`的bug,fix by 高级bug开发技术员
+- 示例项目 修复APP端v-for循环某些情况下报错`Not Found Page`的bug,fix by 高级bug开发技术员
+- uCharts.js 修复非直角坐标系tooltip提示窗右侧超出未变换方向显示的bug
+## 2.0.0-20210415(2021-04-15)
+- 秋云图表组件 修复H5端发布到二级目录下echarts无法加载的bug
+- 秋云图表组件 修复某些情况下echarts.off('finished')移除监听事件报错的bug
+## 2.0.0-20210414(2021-04-14)
+## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。
+## 初次使用如果提示未注册&lt;qiun-data-charts&gt;组件,请重启HBuilderX,如仍不好用,请重启电脑;
+## 如果是cli项目,请尝试清理node_modules,重新install,还不行就删除项目,再重新install。
+## 此问题已于DCloud官方确认,HBuilderX下个版本会修复。
+## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn)
+## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍,右侧蓝色按钮示例项目请看2遍! </font> 
+## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn)
+## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) 
+- 秋云图表组件 修复H5端在cli项目下ECharts引用地址错误的bug
+- 示例项目 增加ECharts的formatter用法的示例(详见示例项目format-e.vue)
+- uCharts.js 增加圆环图中心背景色的配置extra.ring.centerColor
+- uCharts.js 修复微信小程序安卓端柱状图开启透明色后显示不正确的bug
+## 2.0.0-20210413(2021-04-13)
+- 秋云图表组件 修复百度小程序多个图表真机未能正确获取根元素dom尺寸的bug
+- 秋云图表组件 修复百度小程序横屏模式方向不正确的bug
+- 秋云图表组件 修改ontouch时,@getTouchStart@getTouchMove@getTouchEnd的触发条件
+- uCharts.js 修复饼图类数据格式series属性不生效的bug
+- uCharts.js 增加时序区域图 详见示例项目中ucharts.vue
+## 2.0.0-20210412-2(2021-04-12)
+## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。
+## 初次使用如果提示未注册&lt;qiun-data-charts&gt;组件,请重启HBuilderX。如仍不好用,请重启电脑,此问题已于DCloud官方确认,HBuilderX下个版本会修复。
+## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn)
+## [图表组件在uniCloudAdmin中的应用 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) 
+- 秋云图表组件 修复uCharts在APP端横屏模式下不能正确渲染的bug
+- 示例项目 增加ECharts柱状图渐变色、圆角柱状图、横向柱状图(条状图)的示例
+## 2.0.0-20210412(2021-04-12)
+- 秋云图表组件 修复created中判断echarts导致APP端无法识别,改回mounted中判断echarts初始化
+- uCharts.js 修复2d模式下series.textOffset未乘像素比的bug
+## 2.0.0-20210411(2021-04-11)
+## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。
+## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX,并清空小程序开发者工具缓存。
+## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn)
+## [图表组件在uniCloudAdmin中的应用 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) 
+- uCharts.js 折线图区域图增加connectNulls断点续连的功能,详见示例项目中ucharts.vue
+- 秋云图表组件 变更初始化方法为created,变更type2d默认值为true,优化2d模式下组件初始化后dom获取不到的bug
+- 秋云图表组件 修复左右布局时,右侧图表点击坐标错误的bug,修复tooltip柱状图自定义颜色显示object的bug
+## 2.0.0-20210410(2021-04-10)
+- 修复左右布局时,右侧图表点击坐标错误的bug,修复柱状图自定义颜色tooltip显示object的bug
+- 增加标记线及柱状图自定义颜色的demo
+## 2.0.0-20210409(2021-04-08)
+## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧【使用HBuilderX导入插件】即可体验,DEMO演示及在线生成工具(v2.0文档)[https://demo.ucharts.cn](https://demo.ucharts.cn)
+## 图表组件在uniCloudAdmin中的应用 [UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) 
+- uCharts.js 修复钉钉小程序百度小程序measureText不准确的bug,修复2d模式下饼图类activeRadius为按比例放大的bug
+- 修复组件在支付宝小程序端点击位置不准确的bug
+## 2.0.0-20210408(2021-04-07)
+- 修复组件在支付宝小程序端不能显示的bug(目前支付宝小程不能点击交互,后续修复)
+- uCharts.js 修复高分屏下柱状图类,圆弧进度条 自定义宽度不能按比例放大的bug
+## 2.0.0-20210407(2021-04-06)
+## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧【使用HBuilderX导入插件】即可体验,DEMO演示及在线生成工具(v2.0文档)[https://demo.ucharts.cn](https://demo.ucharts.cn)
+## 增加 通过tofix和unit快速格式化y轴的demo add by `howcode`
+## 增加 图表组件在uniCloudAdmin中的应用 [UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) 
+## 2.0.0-20210406(2021-04-05)
+# 秋云图表组件+uCharts v2.0版本同步上线,使用方法详见https://demo.ucharts.cn帮助页
+## 2.0.0(2021-04-05)
+# 秋云图表组件+uCharts v2.0版本同步上线,使用方法详见https://demo.ucharts.cn帮助页

+ 1618 - 0
uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue

@@ -0,0 +1,1618 @@
+<!-- 
+ * qiun-data-charts 秋云高性能跨全端图表组件
+ * Copyright (c) 2021 QIUN® 秋云 https://www.ucharts.cn All rights reserved.
+ * Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+ * 复制使用请保留本段注释,感谢支持开源!
+ * 为方便更多开发者使用,如有更好的建议请提交码云 Pull Requests !
+ *
+ * uCharts®官方网站
+ * https://www.uCharts.cn
+ * 
+ * 开源地址:
+ * https://gitee.com/uCharts/uCharts
+ * 
+ * uni-app插件市场地址:
+ * http://ext.dcloud.net.cn/plugin?id=271
+ * 
+ -->
+<template>
+  <view class="chartsview" :id="'ChartBoxId'+cid">
+    <view v-if="mixinDatacomLoading">
+      <!-- 自定义加载状态,请改这里 -->
+      <qiun-loading :loadingType="loadingType" />
+    </view>
+    <view v-if="mixinDatacomErrorMessage && errorShow" @tap="reloading">
+      <!-- 自定义错误提示,请改这里 -->
+      <qiun-error :errorMessage="errorMessage" />
+    </view>
+    <!-- APP和H5采用renderjs渲染图表 -->
+    <!-- #ifdef APP-VUE || H5 -->
+    <block v-if="echarts">
+      <view
+        :style="{ background: background }"
+        style="width: 100%;height: 100%;"
+        :data-directory="directory"
+        :id="'EC'+cid" 
+        :prop="echartsOpts" 
+        :change:prop="rdcharts.ecinit" 
+        :resize="echartsResize"
+        :change:resize="rdcharts.ecresize"
+        v-show="showchart"
+      />
+    </block>
+    <block v-else>
+      <view
+        v-on:tap="rdcharts.tap"
+        v-on:mousemove="rdcharts.mouseMove"
+        v-on:mousedown="rdcharts.mouseDown"
+        v-on:mouseup="rdcharts.mouseUp"
+        v-on:touchstart="rdcharts.touchStart"
+        v-on:touchmove="rdcharts.touchMove"
+        v-on:touchend="rdcharts.touchEnd"
+        :id="'UC'+cid"
+        :prop="uchartsOpts"
+        :change:prop="rdcharts.ucinit"
+      >
+        <canvas
+          :id="cid"
+          :canvasId="cid"
+          :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
+          :disable-scroll="disableScroll"
+          @error="_error"
+          v-show="showchart"
+        />
+      </view>
+    </block>
+    <!-- #endif -->
+    <!-- 支付宝小程序 -->
+    <!-- #ifdef MP-ALIPAY -->
+    <block v-if="ontouch">
+      <canvas
+        :id="cid"
+        :canvasId="cid"
+        :width="cWidth * pixel"
+        :height="cHeight * pixel"
+        :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
+        :disable-scroll="disScroll"
+        @tap="_tap"
+        @touchstart="_touchStart"
+        @touchmove="_touchMove"
+        @touchend="_touchEnd"
+        @error="_error"
+        v-show="showchart"
+      />
+    </block>
+    <block v-if="!ontouch">
+      <canvas
+        :id="cid"
+        :canvasId="cid"
+        :width="cWidth * pixel"
+        :height="cHeight * pixel"
+        :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
+        :disable-scroll="disScroll"
+        @tap="_tap"
+        @error="_error"
+        v-show="showchart"
+      />
+    </block>
+    <!-- #endif -->
+    <!-- 其他小程序通过vue渲染图表 -->
+    <!-- #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO || MP-KUAISHOU || MP-LARK || MP-JD || MP-360 -->
+    <block v-if="type2d">
+      <view v-if="ontouch" @tap="_tap">
+        <canvas
+          :id="cid"
+          :canvasId="cid"
+          :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
+          type="2d"
+          :disable-scroll="disScroll"
+          @touchstart="_touchStart"
+          @touchmove="_touchMove"
+          @touchend="_touchEnd"
+          @error="_error"
+          v-show="showchart"
+        />
+      </view>
+      <view v-if="!ontouch" @tap="_tap">
+        <canvas
+          :id="cid"
+          :canvasId="cid"
+          :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
+          type="2d"
+          :disable-scroll="disScroll"
+          @error="_error"
+          v-show="showchart"
+        />
+      </view>
+    </block>
+    <block v-if="!type2d">
+      <view v-if="ontouch" @tap="_tap">
+        <canvas
+          :id="cid"
+          :canvasId="cid"
+          :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
+          @touchstart="_touchStart"
+          @touchmove="_touchMove"
+          @touchend="_touchEnd"
+          :disable-scroll="disScroll"
+          @error="_error"
+          v-if="showchart"
+        />
+      </view>
+      <view v-if="!ontouch" >
+        <canvas
+          :id="cid"
+          :canvasId="cid"
+          :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
+          :disable-scroll="disScroll"
+          @tap="_tap"
+          @error="_error"
+          v-if="showchart"
+        />
+      </view>
+    </block>
+    <!-- #endif -->
+  </view>
+</template>
+
+<script>
+import uCharts from '../../js_sdk/u-charts/u-charts.js';
+import cfu from '../../js_sdk/u-charts/config-ucharts.js';
+// #ifdef APP-VUE || H5
+import cfe from '../../js_sdk/u-charts/config-echarts.js';
+// #endif
+
+function deepCloneAssign(origin = {}, ...args) {
+  for (let i in args) {
+    for (let key in args[i]) {
+      if (args[i].hasOwnProperty(key)) {
+        origin[key] = args[i][key] && typeof args[i][key] === 'object' ? deepCloneAssign(Array.isArray(args[i][key]) ? [] : {}, origin[key], args[i][key]) : args[i][key];
+      }
+    }
+  }
+  return origin;
+}
+
+function formatterAssign(args,formatter) {
+  for (let key in args) {
+    if(args.hasOwnProperty(key) && args[key] !== null && typeof args[key] === 'object'){
+      formatterAssign(args[key],formatter)
+    }else if(key === 'format' && typeof args[key] === 'string'){
+      args['formatter'] = formatter[args[key]] ? formatter[args[key]] : undefined;
+    }
+  }
+  return args;
+}
+
+// 时间转换函数,为了匹配uniClinetDB读取出的时间与categories不同
+function getFormatDate(date) {
+	var seperator = "-";
+	var year = date.getFullYear();
+	var month = date.getMonth() + 1;
+	var strDate = date.getDate();
+	if (month >= 1 && month <= 9) {
+			month = "0" + month;
+	}
+	if (strDate >= 0 && strDate <= 9) {
+			strDate = "0" + strDate;
+	}
+	var currentdate = year + seperator + month + seperator + strDate;
+	return currentdate;
+}
+
+var lastMoveTime = null;
+/**
+ * 防抖
+ *
+ * @param { Function } fn 要执行的方法
+ * @param { Number } wait  防抖多少毫秒
+ *
+ * 在 vue 中使用(注意:不能使用箭头函数,否则this指向不对,并且不能再次封装如:
+ * move(){  // 错误调用方式
+ *   debounce(function () {
+ *   console.log(this.title);
+ * }, 1000)});
+ * 应该直接使用:// 正确调用方式
+ * move: debounce(function () {
+ *   console.log(this.title);
+ * }, 1000)
+ */
+function debounce(fn, wait) {
+  let timer = false;
+  return function() {
+    clearTimeout(timer);
+    timer && clearTimeout(timer);
+    timer = setTimeout(() => {
+      timer = false;
+      fn.apply(this, arguments); // 把参数传进去
+    }, wait);
+  };
+}
+
+export default {
+  name: 'qiun-data-charts',
+  mixins: [uniCloud.mixinDatacom],
+  props: {
+    type: {
+      type: String,
+      default: null
+    },
+    canvasId: {
+      type: String,
+      default: 'uchartsid'
+    },
+    canvas2d: {
+      type: Boolean,
+      default: false
+    },
+    background: {
+      type: String,
+      default: 'rgba(0,0,0,0)'
+    },
+    animation: {
+      type: Boolean,
+      default: true
+    },
+    chartData: {
+      type: Object,
+      default() {
+        return {
+          categories: [],
+          series: []
+        };
+      }
+    },
+    opts: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    eopts: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    loadingType: {
+      type: Number,
+      default: 2
+    },
+    errorShow: {
+      type: Boolean,
+      default: true
+    },
+    errorReload: {
+      type: Boolean,
+      default: true
+    },
+    errorMessage: {
+      type: String,
+      default: null
+    },
+    inScrollView: {
+      type: Boolean,
+      default: false
+    },
+    reshow: {
+      type: Boolean,
+      default: false
+    },
+    reload: {
+      type: Boolean,
+      default: false
+    },
+    disableScroll: {
+      type: Boolean,
+      default: false
+    },
+    optsWatch: {
+      type: Boolean,
+      default: true
+    },
+    onzoom: {
+      type: Boolean,
+      default: false
+    },
+    ontap: {
+      type: Boolean,
+      default: true
+    },
+    ontouch: {
+      type: Boolean,
+      default: false
+    },
+    onmouse: {
+      type: Boolean,
+      default: true
+    },
+    onmovetip: {
+      type: Boolean,
+      default: false
+    },
+    echartsH5: {
+      type: Boolean,
+      default: false
+    },
+    echartsApp: {
+      type: Boolean,
+      default: false
+    },
+    tooltipShow: {
+      type: Boolean,
+      default: true
+    },
+    tooltipFormat: {
+      type: String,
+      default: undefined
+    },
+    tooltipCustom: {
+      type: Object,
+      default: undefined
+    },
+    startDate: {
+      type: String,
+      default: undefined
+    },
+    endDate: {
+      type: String,
+      default: undefined
+    },
+    textEnum: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    groupEnum: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    pageScrollTop: {
+      type: Number,
+      default: 0
+    },
+    directory: {
+      type: String,
+      default: '/'
+    },
+    tapLegend: {
+      type: Boolean,
+      default: true
+    },
+    menus: {
+      type: Array,
+      default () {
+        return []
+      }
+    }
+  },
+  data() {
+    return {
+      cid: 'uchartsid',
+      inWx: false,
+      inAli: false,
+      inTt: false,
+      inBd: false,
+      inH5: false,
+      inApp: false,
+      inWin: false,
+      type2d: true,
+      disScroll: false,
+      openmouse: false,
+      pixel: 1,
+      cWidth: 375,
+      cHeight: 250,
+      showchart: false,
+      echarts: false,
+      echartsResize:{
+        state:false
+      },
+      uchartsOpts: {},
+      echartsOpts: {},
+      drawData:{},
+      lastDrawTime:null,
+    };
+  },
+  created(){
+    this.cid = this.canvasId
+    if (this.canvasId == 'uchartsid' || this.canvasId == '') {
+      let t = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
+      let len = t.length
+      let id = ''
+      for (let i = 0; i < 32; i++) {
+        id += t.charAt(Math.floor(Math.random() * len))
+      }
+      this.cid = id
+    }
+    const systemInfo = uni.getSystemInfoSync()
+    if(systemInfo.platform === 'windows' || systemInfo.platform === 'mac'){
+      this.inWin = true;
+    }
+    // #ifdef MP-WEIXIN
+    this.inWx = true;
+    if (this.canvas2d === false || systemInfo.platform === 'windows' || systemInfo.platform === 'mac') {
+      this.type2d = false;
+    }else{
+      this.type2d = true;
+      this.pixel = systemInfo.pixelRatio;
+    }
+    // #endif
+    //非微信小程序端强制关闭canvas2d模式
+    // #ifndef MP-WEIXIN
+    this.type2d = false;
+    // #endif
+    // #ifdef  MP-TOUTIAO || MP-LARK || MP-ALIPAY
+    this.type2d = this.canvas2d;
+    // #endif
+    // #ifdef MP-ALIPAY
+    this.inAli = true;
+    this.pixel = systemInfo.pixelRatio;
+    // #endif
+    // #ifdef MP-BAIDU
+    this.inBd = true;
+    // #endif
+    // #ifdef MP-TOUTIAO
+    this.inTt = true;
+    // #endif
+    this.disScroll = this.disableScroll;
+  },
+  mounted() {
+    // #ifdef APP-VUE
+    this.inApp = true;
+    if (this.echartsApp === true) {
+      this.echarts = true;
+      this.openmouse = false;
+    }
+    // #endif
+    // #ifdef APP-NVUE
+    this.inApp = true;
+    this.mixinDatacomLoading = false
+    this.mixinDatacomErrorMessage = "暂不支持NVUE"
+    // #endif
+    // #ifdef H5
+    this.inH5 = true;
+    if(this.inWin === true){
+      this.openmouse = this.onmouse;
+    }
+    if (this.echartsH5 === true) {
+      this.echarts = true;
+    }
+    // #endif
+    this.$nextTick(()=>{
+      this.beforeInit();
+    })
+    // #ifndef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || APP-VUE
+    const time = this.inH5 ? 500 : 200;
+    const _this = this;
+    uni.onWindowResize(
+      debounce(function(res) {
+        if (_this.mixinDatacomLoading == true) {
+          return;
+        }
+        let errmsg = _this.mixinDatacomErrorMessage;
+        if (errmsg !== null && errmsg !== 'null' && errmsg !== '') {
+          return;
+        }
+        if (_this.echarts) {
+          _this.echartsResize.state = !_this.echartsResize.state;
+        } else {
+          _this.resizeHandler();
+        }
+      }, time)
+    );
+    // #endif
+  },
+  destroyed(){
+    if(this.echarts === true){
+      delete cfe.option[this.cid]
+      delete cfe.instance[this.cid]
+    }else{
+      delete cfu.option[this.cid]
+      delete cfu.instance[this.cid]
+    }
+    // #ifndef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO
+    uni.offWindowResize(()=>{})
+    // #endif
+  },
+  watch: {
+    chartDataProps: {
+      handler(val, oldval) {
+        if (typeof val === 'object') {
+          if (JSON.stringify(val) !== JSON.stringify(oldval)) {
+            this._clearChart();
+            if (val.series && val.series.length > 0) {
+              this.beforeInit();
+            }else{
+              this.mixinDatacomLoading = true;
+              this.showchart = false;
+              this.mixinDatacomErrorMessage = null;
+            }
+          }
+        } else {
+          this.mixinDatacomLoading = false;
+          this._clearChart();
+          this.showchart = false;
+          this.mixinDatacomErrorMessage = '参数错误:chartData数据类型错误';
+        }
+      },
+      immediate: false,
+      deep: true
+    },
+    localdata:{
+      handler(val, oldval) {
+        if (JSON.stringify(val) !== JSON.stringify(oldval)) {
+          if (val.length > 0) {
+            this.beforeInit();
+          }else{
+            this.mixinDatacomLoading = true;
+            this._clearChart();
+            this.showchart = false;
+            this.mixinDatacomErrorMessage = null;
+          }
+        }
+      },
+      immediate: false,
+      deep: true
+    },
+    optsProps: {
+      handler(val, oldval) {
+        if (typeof val === 'object') {
+          if (JSON.stringify(val) !== JSON.stringify(oldval) && this.echarts === false && this.optsWatch == true) {
+            this.checkData(this.drawData);
+          }
+        } else {
+          this.mixinDatacomLoading = false;
+          this._clearChart();
+          this.showchart = false;
+          this.mixinDatacomErrorMessage = '参数错误:opts数据类型错误';
+        }
+      },
+      immediate: false,
+      deep: true
+    },
+    eoptsProps: {
+      handler(val, oldval) {
+        if (typeof val === 'object') {
+          if (JSON.stringify(val) !== JSON.stringify(oldval) && this.echarts === true) {
+            this.checkData(this.drawData);
+          }
+        } else {
+          this.mixinDatacomLoading = false;
+          this.showchart = false;
+          this.mixinDatacomErrorMessage = '参数错误:eopts数据类型错误';
+        }
+      },
+      immediate: false,
+      deep: true
+    },
+    reshow(val, oldval) {
+      if (val === true && this.mixinDatacomLoading === false) {
+        setTimeout(() => {
+          this.mixinDatacomErrorMessage = null;
+          this.echartsResize.state = !this.echartsResize.state;
+          this.checkData(this.drawData);
+        }, 200);
+      }
+    },
+    reload(val, oldval) {
+      if (val === true) {
+        this.showchart = false;
+        this.mixinDatacomErrorMessage = null;
+        this.reloading();
+      }
+    },
+    mixinDatacomErrorMessage(val, oldval) {
+      if (val) {
+        this.emitMsg({name: 'error', params: {type:"error", errorShow: this.errorShow, msg: val, id: this.cid}});
+        if(this.errorShow){
+          console.log('[秋云图表组件]' + val);
+        }
+      }
+    },
+    errorMessage(val, oldval) {
+      if (val && this.errorShow && val !== null && val !== 'null' && val !== '') {
+        this.showchart = false;
+        this.mixinDatacomLoading = false;
+        this.mixinDatacomErrorMessage = val;
+      } else {
+        this.showchart = false;
+        this.mixinDatacomErrorMessage = null;
+        this.reloading();
+      }
+    }
+  },
+  computed: {
+    optsProps() {
+      return JSON.parse(JSON.stringify(this.opts));
+    },
+    eoptsProps() {
+      return JSON.parse(JSON.stringify(this.eopts));
+    },
+    chartDataProps() {
+      return JSON.parse(JSON.stringify(this.chartData));
+    },
+  },
+  methods: {
+    beforeInit(){
+      this.mixinDatacomErrorMessage = null;
+      if (typeof this.chartData === 'object' && this.chartData != null && this.chartData.series !== undefined && this.chartData.series.length > 0) {
+        //拷贝一下chartData,为了opts变更后统一数据来源
+        this.drawData = deepCloneAssign({}, this.chartData);
+        this.mixinDatacomLoading = false;
+        this.showchart = true;
+        this.checkData(this.chartData);
+      }else if(this.localdata.length>0){
+        this.mixinDatacomLoading = false;
+        this.showchart = true;
+        this.localdataInit(this.localdata);
+      }else if(this.collection !== ''){
+        this.mixinDatacomLoading = false;
+        this.getCloudData();
+      }else{
+        this.mixinDatacomLoading = true;
+      }
+    },
+    localdataInit(resdata){
+      //替换enum类型为正确的描述
+      if(this.groupEnum.length>0){
+        for (let i = 0; i < resdata.length; i++) {
+          for (let j = 0; j < this.groupEnum.length; j++) {
+            if(resdata[i].group === this.groupEnum[j].value){
+              resdata[i].group = this.groupEnum[j].text
+            }
+          }
+        }
+      }
+      if(this.textEnum.length>0){
+        for (let i = 0; i < resdata.length; i++) {
+          for (let j = 0; j < this.textEnum.length; j++) {
+            if(resdata[i].text === this.textEnum[j].value){
+              resdata[i].text = this.textEnum[j].text
+            }
+          }
+        }
+      }
+      let needCategories = false;
+      let tmpData = {categories:[], series:[]}
+      let tmpcategories = []
+      let tmpseries = [];
+      //拼接categories
+      if(this.echarts === true){
+        needCategories = cfe.categories.includes(this.type)
+      }else{
+        needCategories = cfu.categories.includes(this.type)
+      }
+      if(needCategories === true){
+        //如果props中的chartData带有categories,则优先使用chartData的categories
+        if(this.chartData && this.chartData.categories && this.chartData.categories.length>0){
+          tmpcategories = this.chartData.categories
+        }else{
+          //如果是日期类型的数据,不管是本地数据还是云数据,都按起止日期自动拼接categories
+          if(this.startDate && this.endDate){
+            let idate = new Date(this.startDate)
+            let edate = new Date(this.endDate)
+            while (idate <= edate) {
+            	tmpcategories.push(getFormatDate(idate))
+            	idate = idate.setDate(idate.getDate() + 1)
+            	idate = new Date(idate)
+            }
+          //否则从结果中去重并拼接categories
+          }else{
+            let tempckey = {};
+            resdata.map(function(item, index) {
+              if (item.text != undefined && !tempckey[item.text]) {
+                tmpcategories.push(item.text)
+                tempckey[item.text] = true
+              }
+            });
+          }
+        }
+        tmpData.categories = tmpcategories
+      }
+      //拼接series
+      let tempskey = {};
+      resdata.map(function(item, index) {
+        if (item.group != undefined && !tempskey[item.group]) {
+          tmpseries.push({ name: item.group, data: [] });
+          tempskey[item.group] = true;
+        }
+      });
+      //如果没有获取到分组名称(可能是带categories的数据,也可能是不带的饼图类)
+      if (tmpseries.length == 0) {
+        tmpseries = [{ name: '默认分组', data: [] }];
+        //如果是需要categories的图表类型
+        if(needCategories === true){
+          for (let j = 0; j < tmpcategories.length; j++) {
+            let seriesdata = 0;
+            for (let i = 0; i < resdata.length; i++) {
+              if (resdata[i].text == tmpcategories[j]) {
+                seriesdata = resdata[i].value;
+              }
+            }
+            tmpseries[0].data.push(seriesdata);
+          }
+        //如果是饼图类的图表类型
+        }else{
+          for (let i = 0; i < resdata.length; i++) {
+            tmpseries[0].data.push({"name": resdata[i].text,"value": resdata[i].value});
+          }
+        }
+      //如果有分组名
+      } else {
+        for (let k = 0; k < tmpseries.length; k++) {
+          //如果有categories
+          if (tmpcategories.length > 0) {
+            for (let j = 0; j < tmpcategories.length; j++) {
+              let seriesdata = 0;
+              for (let i = 0; i < resdata.length; i++) {
+                if (tmpseries[k].name == resdata[i].group && resdata[i].text == tmpcategories[j]) {
+                  seriesdata = resdata[i].value;
+                }
+              }
+              tmpseries[k].data.push(seriesdata);
+            }
+          //如果传了group而没有传text,即没有categories(正常情况下这种数据是不符合数据要求规范的)
+          } else {
+            for (let i = 0; i < resdata.length; i++) {
+              if (tmpseries[k].name == resdata[i].group) {
+                tmpseries[k].data.push(resdata[i].value);
+              }
+            }
+          }
+        }
+      }
+      tmpData.series = tmpseries
+      //拷贝一下chartData,为了opts变更后统一数据来源
+      this.drawData = deepCloneAssign({}, tmpData);
+      this.checkData(tmpData)
+    },
+    reloading() {
+      if(this.errorReload === false){
+        return;
+      }
+      this.showchart = false;
+      this.mixinDatacomErrorMessage = null;
+      if (this.collection !== '') {
+        this.mixinDatacomLoading = false;
+        this.onMixinDatacomPropsChange(true);
+      } else {
+        this.beforeInit();
+      }
+    },
+    checkData(anyData) {
+      let cid = this.cid
+      //复位opts或eopts
+      if(this.echarts === true){
+        cfe.option[cid] = deepCloneAssign({}, this.eopts);
+        cfe.option[cid].id = cid;
+        cfe.option[cid].type = this.type;
+      }else{
+        if (this.type && cfu.type.includes(this.type)) {
+          cfu.option[cid] = deepCloneAssign({}, cfu[this.type], this.opts);
+          cfu.option[cid].canvasId = cid;
+        } else {
+          this.mixinDatacomLoading = false;
+          this.showchart = false;
+          this.mixinDatacomErrorMessage = '参数错误:props参数中type类型不正确';
+        }
+      }
+      //挂载categories和series
+      let newData = deepCloneAssign({}, anyData);
+      if (newData.series !== undefined && newData.series.length > 0) {
+        this.mixinDatacomErrorMessage = null;
+        if (this.echarts === true) {
+          cfe.option[cid].chartData = newData;
+          this.$nextTick(()=>{
+            this.init()
+          })
+        }else{
+          cfu.option[cid].categories = newData.categories;
+          cfu.option[cid].series = newData.series;
+          this.$nextTick(()=>{
+            this.init()
+          })
+        }
+      }
+    },
+    resizeHandler() {
+      //渲染防抖
+      let currTime = Date.now();
+      let lastDrawTime = this.lastDrawTime?this.lastDrawTime:currTime-3000;
+      let duration = currTime - lastDrawTime;
+      if (duration < 1000) return;
+      let chartdom = uni
+        .createSelectorQuery()
+        // #ifndef MP-ALIPAY
+        .in(this)
+        // #endif
+        .select('#ChartBoxId'+this.cid)
+        .boundingClientRect(data => {
+          this.showchart = true;
+          if (data.width > 0 && data.height > 0) {
+            if (data.width !== this.cWidth || data.height !== this.cHeight) {
+              this.checkData(this.drawData)
+            }
+          }
+        })
+        .exec();
+    },
+    getCloudData() {
+      if (this.mixinDatacomLoading == true) {
+        return;
+      }
+      this.mixinDatacomLoading = true;
+      this.mixinDatacomGet()
+        .then(res => {
+          this.mixinDatacomResData = res.result.data;
+          this.localdataInit(this.mixinDatacomResData);
+        })
+        .catch(err => {
+          this.mixinDatacomLoading = false;
+          this.showchart = false;
+          this.mixinDatacomErrorMessage = '请求错误:' + err;
+        });
+    },
+    onMixinDatacomPropsChange(needReset, changed) {
+      if (needReset == true && this.collection !== '') {
+        this.showchart = false;
+        this.mixinDatacomErrorMessage = null;
+        this._clearChart();
+        this.getCloudData();
+      }
+    },
+    _clearChart() {
+      let cid = this.cid
+      if (this.echarts !== true && cfu.option[cid] && cfu.option[cid].context) {
+        const ctx = cfu.option[cid].context;
+        if(typeof ctx === "object" && !!!cfu.option[cid].update){
+          ctx.clearRect(0, 0, this.cWidth*this.pixel, this.cHeight*this.pixel);
+          ctx.draw();
+        }
+      }
+    },
+    init() {
+      let cid = this.cid
+      let chartdom = uni
+        .createSelectorQuery()
+        // #ifndef MP-ALIPAY
+        .in(this)
+        // #endif
+        .select('#ChartBoxId'+cid)
+        .boundingClientRect(data => {
+          if (data.width > 0 && data.height > 0) {
+            this.mixinDatacomLoading = false;
+            this.showchart = true;
+            this.lastDrawTime = Date.now();
+            this.cWidth = data.width;
+            this.cHeight = data.height;
+            if(this.echarts !== true){
+              cfu.option[cid].background = this.background == 'rgba(0,0,0,0)' ? '#FFFFFF' : this.background;
+              cfu.option[cid].canvas2d = this.type2d;
+              cfu.option[cid].pixelRatio = this.pixel;
+              cfu.option[cid].animation = this.animation;
+              cfu.option[cid].width = data.width * this.pixel;
+              cfu.option[cid].height = data.height * this.pixel;
+              cfu.option[cid].onzoom = this.onzoom;
+              cfu.option[cid].ontap = this.ontap;
+              cfu.option[cid].ontouch = this.ontouch;
+              cfu.option[cid].onmouse = this.openmouse;
+              cfu.option[cid].onmovetip = this.onmovetip;
+              cfu.option[cid].tooltipShow = this.tooltipShow;
+              cfu.option[cid].tooltipFormat = this.tooltipFormat;
+              cfu.option[cid].tooltipCustom = this.tooltipCustom;
+              cfu.option[cid].inScrollView = this.inScrollView;
+              cfu.option[cid].lastDrawTime = this.lastDrawTime;
+              cfu.option[cid].tapLegend = this.tapLegend;
+            }
+            //如果是H5或者App端,采用renderjs渲染图表
+            if (this.inH5 || this.inApp) {
+              if (this.echarts == true) {
+                cfe.option[cid].ontap = this.ontap;
+                cfe.option[cid].onmouse = this.openmouse;
+                cfe.option[cid].tooltipShow = this.tooltipShow;
+                cfe.option[cid].tooltipFormat = this.tooltipFormat;
+                cfe.option[cid].tooltipCustom = this.tooltipCustom;
+                cfe.option[cid].lastDrawTime = this.lastDrawTime;
+                this.echartsOpts = deepCloneAssign({}, cfe.option[cid]);
+              } else {
+                cfu.option[cid].rotateLock = cfu.option[cid].rotate;
+                this.uchartsOpts = deepCloneAssign({}, cfu.option[cid]);
+              }
+            //如果是小程序端,采用uCharts渲染
+            } else {
+              cfu.option[cid] = formatterAssign(cfu.option[cid],cfu.formatter)
+              this.mixinDatacomErrorMessage = null;
+              this.mixinDatacomLoading = false;
+              this.showchart = true;
+              this.$nextTick(()=>{
+                if (this.type2d === true) {
+                  const query = uni.createSelectorQuery().in(this)
+                  query
+                    .select('#' + cid)
+                    .fields({ node: true, size: true })
+                    .exec(res => {
+                      if (res[0]) {
+                        const canvas = res[0].node;
+                        const ctx = canvas.getContext('2d');
+                        cfu.option[cid].context = ctx;
+                        cfu.option[cid].rotateLock = cfu.option[cid].rotate;
+                        if(cfu.instance[cid] && cfu.option[cid] && cfu.option[cid].update === true){
+                          this._updataUChart(cid)
+                        }else{
+                          canvas.width = data.width * this.pixel;
+                          canvas.height = data.height * this.pixel;
+                          canvas._width = data.width * this.pixel;
+                          canvas._height = data.height * this.pixel;
+                          setTimeout(()=>{
+                            cfu.option[cid].context.restore();
+                            cfu.option[cid].context.save();
+                            this._newChart(cid)
+                          },100)
+                        }
+                      } else {
+                        this.showchart = false;
+                        this.mixinDatacomErrorMessage = '参数错误:开启2d模式后,未获取到dom节点,canvas-id:' + cid;
+                      }
+                    });
+                } else {
+                  if(this.inAli){
+                    cfu.option[cid].rotateLock = cfu.option[cid].rotate;
+                  }
+                  cfu.option[cid].context = uni.createCanvasContext(cid, this);
+                  if(cfu.instance[cid] && cfu.option[cid] && cfu.option[cid].update === true){
+                    this._updataUChart(cid)
+                  }else{
+                    setTimeout(()=>{
+                      cfu.option[cid].context.restore();
+                      cfu.option[cid].context.save();
+                      this._newChart(cid)
+                    },100)
+                  }
+                }
+              })
+            }
+          } else {
+            this.mixinDatacomLoading = false;
+            this.showchart = false;
+            if (this.reshow == true) {
+              this.mixinDatacomErrorMessage = '布局错误:未获取到父元素宽高尺寸!canvas-id:' + cid;
+            }
+          }
+        })
+        .exec();
+    },
+    saveImage(){
+    	uni.canvasToTempFilePath({
+    	  canvasId: this.cid,
+    	  success: res=>{
+    	    //#ifdef H5
+    			var a = document.createElement("a");
+    			a.href = res.tempFilePath;
+    			a.download = this.cid;
+    			a.target = '_blank'
+    			a.click();
+    	    //#endif
+    	    //#ifndef H5
+    	      uni.saveImageToPhotosAlbum({
+              filePath: res.tempFilePath,
+              success: function () {
+                uni.showToast({
+                  title: '保存成功',
+                  duration: 2000
+                });
+              }
+    	      });
+    	    //#endif
+    	  } 
+    	},this);
+    },
+    getImage(){
+      if(this.type2d == false){
+        uni.canvasToTempFilePath({
+          canvasId: this.cid,
+          success: res=>{
+            this.emitMsg({name: 'getImage', params: {type:"getImage", base64: res.tempFilePath}});
+          }
+        },this);
+      }else{
+        const query = uni.createSelectorQuery().in(this)
+        query
+          .select('#' + this.cid)
+          .fields({ node: true, size: true })
+          .exec(res => {
+            if (res[0]) {
+              const canvas = res[0].node;
+              this.emitMsg({name: 'getImage', params: {type:"getImage", base64: canvas.toDataURL('image/png')}});
+            }
+          });
+      }
+    },
+    // #ifndef APP-VUE || H5
+    _newChart(cid) {
+      if (this.mixinDatacomLoading == true) {
+        return;
+      }
+      this.showchart = true;
+      cfu.instance[cid] = new uCharts(cfu.option[cid]);
+      cfu.instance[cid].addEventListener('renderComplete', () => {
+        this.emitMsg({name: 'complete', params: {type:"complete", complete: true, id: cid, opts: cfu.instance[cid].opts}});
+        cfu.instance[cid].delEventListener('renderComplete')
+      });
+      cfu.instance[cid].addEventListener('scrollLeft', () => {
+        this.emitMsg({name: 'scrollLeft', params: {type:"scrollLeft", scrollLeft: true, id: cid, opts: cfu.instance[cid].opts}});
+      });
+      cfu.instance[cid].addEventListener('scrollRight', () => {
+        this.emitMsg({name: 'scrollRight', params: {type:"scrollRight", scrollRight: true, id: cid, opts: cfu.instance[cid].opts}});
+      });
+    },
+    _updataUChart(cid) {
+      cfu.instance[cid].updateData(cfu.option[cid])
+    },
+    _tooltipDefault(item, category, index, opts) {
+      if (category) {
+        let data = item.data
+        if(typeof item.data === "object"){
+          data = item.data.value
+        }
+        return category + ' ' + item.name + ':' + data;
+      } else {
+        if (item.properties && item.properties.name) {
+          return item.properties.name;
+        } else {
+          return item.name + ':' + item.data;
+        }
+      }
+    },
+    _showTooltip(e) {
+      let cid = this.cid
+      let tc = cfu.option[cid].tooltipCustom
+      if (tc && tc !== undefined && tc !== null) {
+        let offset = undefined;
+        if (tc.x >= 0 && tc.y >= 0) {
+          offset = { x: tc.x, y: tc.y + 10 };
+        }
+        cfu.instance[cid].showToolTip(e, {
+          index: tc.index,
+          offset: offset,
+          textList: tc.textList,
+          formatter: (item, category, index, opts) => {
+            if (typeof cfu.option[cid].tooltipFormat === 'string' && cfu.formatter[cfu.option[cid].tooltipFormat]) {
+              return cfu.formatter[cfu.option[cid].tooltipFormat](item, category, index, opts);
+            } else {
+              return this._tooltipDefault(item, category, index, opts);
+            }
+          }
+        });
+      } else {
+        cfu.instance[cid].showToolTip(e, {
+          formatter: (item, category, index, opts) => {
+            if (typeof cfu.option[cid].tooltipFormat === 'string' && cfu.formatter[cfu.option[cid].tooltipFormat]) {
+              return cfu.formatter[cfu.option[cid].tooltipFormat](item, category, index, opts);
+            } else {
+              return this._tooltipDefault(item, category, index, opts);
+            }
+          }
+        });
+      }
+    },
+    _tap(e,move) {
+      let cid = this.cid
+      let currentIndex = null;
+      let legendIndex = null;
+      if (this.inScrollView === true || this.inAli) {
+        let chartdom = uni
+          .createSelectorQuery()
+          // #ifndef MP-ALIPAY
+          .in(this)
+          .select('#ChartBoxId'+cid)
+          // #endif
+          // #ifdef MP-ALIPAY
+          .select('#'+this.cid)
+          // #endif
+          .boundingClientRect(data => {
+            e.changedTouches=[];
+            if (this.inAli) {
+              e.changedTouches.unshift({ x: e.detail.clientX - data.left, y: e.detail.clientY - data.top});
+            }else{
+              e.changedTouches.unshift({ x: e.detail.x - data.left, y: e.detail.y - data.top - this.pageScrollTop});
+            }
+            if(move){
+              if (this.tooltipShow === true) {
+                this._showTooltip(e);
+              }
+            }else{
+              currentIndex = cfu.instance[cid].getCurrentDataIndex(e);
+              legendIndex = cfu.instance[cid].getLegendDataIndex(e);
+              if(this.tapLegend === true){
+                cfu.instance[cid].touchLegend(e);
+              }
+              if (this.tooltipShow === true) {
+                this._showTooltip(e);
+              }
+              this.emitMsg({name: 'getIndex', params: { type:"getIndex", event:{ x: e.detail.x - data.left, y: e.detail.y - data.top }, currentIndex: currentIndex, legendIndex: legendIndex, id: cid, opts: cfu.instance[cid].opts}});
+            }
+          })
+          .exec();
+      } else {
+        if(move){
+          if (this.tooltipShow === true) {
+            this._showTooltip(e);
+          }
+        }else{
+          e.changedTouches=[];
+          e.changedTouches.unshift({ x: e.detail.x - e.currentTarget.offsetLeft, y: e.detail.y - e.currentTarget.offsetTop });
+          currentIndex = cfu.instance[cid].getCurrentDataIndex(e);
+          legendIndex = cfu.instance[cid].getLegendDataIndex(e);
+          if(this.tapLegend === true){
+            cfu.instance[cid].touchLegend(e);
+          }
+          if (this.tooltipShow === true) {
+            this._showTooltip(e);
+          }
+          this.emitMsg({name: 'getIndex', params: {type:"getIndex", event:{ x: e.detail.x, y: e.detail.y - e.currentTarget.offsetTop }, currentIndex: currentIndex, legendIndex: legendIndex, id: cid, opts: cfu.instance[cid].opts}});
+        }
+      }
+    },
+    _touchStart(e) {
+      let cid = this.cid
+      lastMoveTime=Date.now();
+      if(cfu.option[cid].enableScroll === true && e.touches.length == 1){
+        cfu.instance[cid].scrollStart(e);
+      }
+      this.emitMsg({name:'getTouchStart', params:{type:"touchStart", event:e.changedTouches[0], id:cid, opts: cfu.instance[cid].opts}});
+    },
+    _touchMove(e) {
+      let cid = this.cid
+      let currMoveTime = Date.now();
+      let duration = currMoveTime - lastMoveTime;
+      let touchMoveLimit = cfu.option[cid].touchMoveLimit || 24;
+      if (duration < Math.floor(1000 / touchMoveLimit)) return;//每秒60帧
+      lastMoveTime = currMoveTime;
+      if(cfu.option[cid].enableScroll === true && e.changedTouches.length == 1){
+        cfu.instance[cid].scroll(e);
+      }
+      if(this.ontap === true && cfu.option[cid].enableScroll === false && this.onmovetip === true){
+        this._tap(e,true)
+      }
+      if(this.ontouch === true && cfu.option[cid].enableScroll === true && this.onzoom === true && e.changedTouches.length == 2){
+        cfu.instance[cid].dobuleZoom(e);
+      }
+      this.emitMsg({name: 'getTouchMove', params: {type:"touchMove", event:e.changedTouches[0], id: cid, opts: cfu.instance[cid].opts}});
+    },
+    _touchEnd(e) {
+      let cid = this.cid
+      if(cfu.option[cid].enableScroll === true && e.touches.length == 0){
+        cfu.instance[cid].scrollEnd(e);
+      }
+      this.emitMsg({name:'getTouchEnd', params:{type:"touchEnd", event:e.changedTouches[0], id:cid, opts: cfu.instance[cid].opts}});
+      if(this.ontap === true && cfu.option[cid].enableScroll === false && this.onmovetip === true){
+        this._tap(e,true)
+      }
+    },
+    // #endif
+    _error(e) {
+      this.mixinDatacomErrorMessage = e.detail.errMsg;
+    },
+    emitMsg(msg) {
+      this.$emit(msg.name, msg.params);
+    },
+    getRenderType() {
+      //防止如果开启echarts且父元素为v-if的情况renderjs监听不到prop变化的问题
+      if(this.echarts===true && this.mixinDatacomLoading===false){
+        this.beforeInit()
+      }
+    },
+    toJSON(){
+      return this
+    }
+  }
+};
+</script>
+
+<!-- #ifdef APP-VUE || H5 -->
+<script module="rdcharts" lang="renderjs">
+import uChartsRD from '../../js_sdk/u-charts/u-charts.js';
+import cfu from '../../js_sdk/u-charts/config-ucharts.js';
+import cfe from '../../js_sdk/u-charts/config-echarts.js';
+
+var that = {};
+var rootdom = null;
+
+function rddeepCloneAssign(origin = {}, ...args) {
+  for (let i in args) {
+    for (let key in args[i]) {
+      if (args[i].hasOwnProperty(key)) {
+        origin[key] = args[i][key] && typeof args[i][key] === 'object' ? rddeepCloneAssign(Array.isArray(args[i][key]) ? [] : {}, origin[key], args[i][key]) : args[i][key];
+      }
+    }
+  }
+  return origin;
+}
+
+function rdformatterAssign(args,formatter) {
+  for (let key in args) {
+    if(args.hasOwnProperty(key) && args[key] !== null && typeof args[key] === 'object'){
+      rdformatterAssign(args[key],formatter)
+    }else if(key === 'format' && typeof args[key] === 'string'){
+      args['formatter'] = formatter[args[key]] ? formatter[args[key]] : undefined;
+    }
+  }
+  return args;
+}
+
+export default {
+  data() {
+    return {
+      rid:null
+    }
+  },
+  mounted() {
+    rootdom = {top:0,left:0}
+    // #ifdef H5
+    let dm = document.querySelectorAll('uni-main')[0]
+    if(dm === undefined){
+      dm = document.querySelectorAll('uni-page-wrapper')[0]
+    }
+    rootdom = {top:dm.offsetTop,left:dm.offsetLeft}
+    // #endif
+    setTimeout(()=>{
+      if(this.rid === null){
+        this.$ownerInstance && this.$ownerInstance.callMethod('getRenderType')
+      }
+    },200)
+  },
+  destroyed(){
+    delete cfu.option[this.rid]
+    delete cfu.instance[this.rid]
+    delete cfe.option[this.rid]
+    delete cfe.instance[this.rid]
+  },
+  methods: {
+    //==============以下是ECharts的方法====================
+    ecinit(newVal, oldVal, owner, instance){
+      let cid = JSON.stringify(newVal.id)
+      this.rid = cid
+      that[cid] = this.$ownerInstance || instance
+      let eopts = JSON.parse(JSON.stringify(newVal))
+      let type = eopts.type;
+      //载入并覆盖默认配置
+      if (type && cfe.type.includes(type)) {
+        cfe.option[cid] = rddeepCloneAssign({}, cfe[type], eopts);
+      }else{
+        cfe.option[cid] = rddeepCloneAssign({}, eopts);
+      }
+      let newData = eopts.chartData;
+      if(newData){
+        //挂载categories和series
+        if(cfe.option[cid].xAxis && cfe.option[cid].xAxis.type && cfe.option[cid].xAxis.type === 'category'){
+          cfe.option[cid].xAxis.data = newData.categories
+        }
+        if(cfe.option[cid].yAxis && cfe.option[cid].yAxis.type && cfe.option[cid].yAxis.type === 'category'){
+          cfe.option[cid].yAxis.data = newData.categories
+        }
+        cfe.option[cid].series = []
+        for (var i = 0; i < newData.series.length; i++) {
+          cfe.option[cid].seriesTemplate = cfe.option[cid].seriesTemplate ? cfe.option[cid].seriesTemplate : {}
+          let Template = rddeepCloneAssign({},cfe.option[cid].seriesTemplate,newData.series[i])
+          cfe.option[cid].series.push(Template)
+        }
+      }
+      
+      if (typeof window.echarts === 'object') {
+          this.newEChart()
+      }else{
+        const script = document.createElement('script')
+        // #ifdef APP-VUE
+        script.src = './uni_modules/qiun-data-charts/static/app-plus/echarts.min.js'
+        // #endif
+        // #ifdef H5
+        const rooturl = window.location.origin
+        const directory = instance.getDataset().directory
+        script.src = rooturl + directory + 'uni_modules/qiun-data-charts/static/h5/echarts.min.js'
+        // #endif
+        script.onload = this.newEChart
+        document.head.appendChild(script)
+      }
+    },
+    ecresize(newVal, oldVal, owner, instance){
+      if(cfe.instance[this.rid]){
+        cfe.instance[this.rid].resize()
+      }
+    },
+    newEChart(){
+      let cid = this.rid
+      if(cfe.instance[cid] === undefined){
+        cfe.instance[cid] = echarts.init(that[cid].$el.children[0])
+        //ontap开启后才触发click事件
+        if(cfe.option[cid].ontap === true){
+          cfe.instance[cid].on('click', resdata => {
+            let event = JSON.parse(JSON.stringify({
+              x:resdata.event.offsetX,y:resdata.event.offsetY
+            }))
+            that[cid].callMethod('emitMsg',{name:"getIndex", params:{type:"getIndex", event:event, currentIndex:resdata.dataIndex, value:resdata.data, seriesName: resdata.seriesName,id:cid}})
+          })
+          // 增加ECharts的highlight消息,实现按下移动返回索引功能。add by onefish 创建于 2021-12-11 09:50
+          cfe.instance[cid].on('highlight', resdata => {
+            that[cid].callMethod('emitMsg',{name:"getHighlight", params:{type:"highlight", res:resdata, id:cid}})
+          })
+        }
+        this.updataEChart(cid,cfe.option[cid])
+      }else{
+        this.updataEChart(cid,cfe.option[cid])
+      }
+    },
+    updataEChart(cid,option){
+      //替换option内format属性为formatter的预定义方法
+      option = rdformatterAssign(option,cfe.formatter)
+      if(option.tooltip){
+        option.tooltip.show = option.tooltipShow?true:false;
+        option.tooltip.position = this.tooltipPosition()
+        //tooltipFormat方法,替换组件的tooltipFormat为config-echarts.js内对应的方法
+        if (typeof option.tooltipFormat === 'string' && cfe.formatter[option.tooltipFormat]) {
+          option.tooltip.formatter = option.tooltip.formatter ? option.tooltip.formatter : cfe.formatter[option.tooltipFormat]
+        }
+      }
+      // 颜色渐变添加的方法
+      if (option.series) {
+      	for (let i in option.series) {
+      		let linearGradient = option.series[i].linearGradient
+      		if (linearGradient) {
+      			option.series[i].color = new echarts.graphic.LinearGradient(linearGradient[0],linearGradient[1],linearGradient[2],linearGradient[3],linearGradient[4])
+      		}
+      	}
+      }
+      cfe.instance[cid].setOption(option, option.notMerge)
+      cfe.instance[cid].on('finished', function(){
+        that[cid].callMethod('emitMsg',{name:"complete",params:{type:"complete",complete:true,id:cid}})
+        if(cfe.instance[cid]){
+          cfe.instance[cid].off('finished')
+        }
+      });
+
+      //修复init初始化实例获取宽高不正确问题
+      if(
+        typeof that[cid].$el.children[0].clientWidth != 'undefined' &&
+          (
+            Math.abs( that[cid].$el.children[0].clientWidth - cfe.instance[cid].getWidth() )>3 ||
+            Math.abs( that[cid].$el.children[0].clientHeight - cfe.instance[cid].getHeight() )>3
+          )
+      ){this.ecresize();}
+    },
+    tooltipPosition(){
+      return (point, params, dom, rect, size) => {
+      	let x = point[0]
+      	let y = point[1]
+      	let viewWidth = size.viewSize[0]
+      	let viewHeight = size.viewSize[1]
+      	let boxWidth = size.contentSize[0]
+      	let boxHeight = size.contentSize[1]
+      	let posX = x + 30 
+      	let posY = y + 30 
+      	if (posX + boxWidth > viewWidth) { 
+      		posX = x - boxWidth - 30
+      	}
+      	if (posY + boxHeight > viewHeight) {
+      		posY = y - boxHeight - 30
+      	}
+      	return [posX, posY]
+      }
+    },
+    //==============以下是uCharts的方法====================
+    ucinit(newVal, oldVal, owner, instance){
+      if(JSON.stringify(newVal) == JSON.stringify(oldVal)){
+        return;
+      }
+      if(!newVal.canvasId){
+        return;
+      }
+      let cid = JSON.parse(JSON.stringify(newVal.canvasId))
+      this.rid = cid
+      that[cid] = this.$ownerInstance || instance
+      cfu.option[cid] = JSON.parse(JSON.stringify(newVal))
+      cfu.option[cid] = rdformatterAssign(cfu.option[cid],cfu.formatter)
+      let canvasdom = document.getElementById(cid)
+      if(canvasdom && canvasdom.children[0]){
+        cfu.option[cid].context = canvasdom.children[0].getContext("2d")
+        if(cfu.instance[cid] && cfu.option[cid] && cfu.option[cid].update === true){
+          this.updataUChart()
+        }else{
+          setTimeout(()=>{
+            cfu.option[cid].context.restore();
+            cfu.option[cid].context.save();
+            this.newUChart()
+          },100)
+        }
+      }
+    },
+    newUChart() {
+      let cid = this.rid
+      cfu.instance[cid] = new uChartsRD(cfu.option[cid])
+      cfu.instance[cid].addEventListener('renderComplete', () => {
+        that[cid].callMethod('emitMsg',{name:"complete",params:{type:"complete",complete:true,id:cid, opts: cfu.instance[cid].opts}})
+        cfu.instance[cid].delEventListener('renderComplete')
+      });
+      cfu.instance[cid].addEventListener('scrollLeft', () => {
+        that[cid].callMethod('emitMsg',{name:"scrollLeft",params:{type:"scrollLeft",scrollLeft:true,id:cid, opts: cfu.instance[cid].opts}})
+      });
+      cfu.instance[cid].addEventListener('scrollRight', () => {
+        that[cid].callMethod('emitMsg',{name:"scrollRight",params:{type:"scrollRight",scrollRight:true,id:cid, opts: cfu.instance[cid].opts}})
+      });
+    },
+    updataUChart() {
+      let cid = this.rid
+      cfu.instance[cid].updateData(cfu.option[cid])
+    },
+    tooltipDefault(item, category, index, opts) {
+      if (category) {
+        let data = item.data
+        if(typeof item.data === "object"){
+          data = item.data.value
+        }
+        return category + ' ' + item.name + ':' + data;
+      } else {
+        if (item.properties && item.properties.name) {
+          return item.properties.name ;
+        } else {
+          return item.name + ':' + item.data;
+        }
+      }
+    },
+    showTooltip(e,cid) {
+      let tc = cfu.option[cid].tooltipCustom
+      if (tc && tc !== undefined && tc !== null) {
+        let offset = undefined;
+        if (tc.x >= 0 && tc.y >= 0) {
+          offset = { x: tc.x, y: tc.y + 10 };
+        }
+        cfu.instance[cid].showToolTip(e, {
+          index: tc.index,
+          offset: offset,
+          textList: tc.textList,
+          formatter: (item, category, index, opts) => {
+            if (typeof cfu.option[cid].tooltipFormat === 'string' && cfu.formatter[cfu.option[cid].tooltipFormat]) {
+              return cfu.formatter[cfu.option[cid].tooltipFormat](item, category, index, opts);
+            } else {
+              return this.tooltipDefault(item, category, index, opts);
+            }
+          }
+        });
+      } else {
+        cfu.instance[cid].showToolTip(e, {
+          formatter: (item, category, index, opts) => {
+            if (typeof cfu.option[cid].tooltipFormat === 'string' && cfu.formatter[cfu.option[cid].tooltipFormat]) {
+              return cfu.formatter[cfu.option[cid].tooltipFormat](item, category, index, opts);
+            } else {
+              return this.tooltipDefault(item, category, index, opts);
+            }
+          }
+        });
+      }
+    },
+    tap(e) {
+      let cid = this.rid
+      let ontap = cfu.option[cid].ontap
+      let tooltipShow = cfu.option[cid].tooltipShow
+      let tapLegend = cfu.option[cid].tapLegend
+      if(ontap == false) return;
+      let currentIndex=null
+      let legendIndex=null
+      let rchartdom = document.getElementById('UC'+cid).getBoundingClientRect()
+      let tmpe = {}
+      if(e.detail.x){//tap或者click的事件
+        tmpe = { x: e.detail.x - rchartdom.left, y:e.detail.y - rchartdom.top + rootdom.top}
+      }else{//mouse的事件
+        tmpe = { x: e.clientX - rchartdom.left, y:e.clientY - rchartdom.top + rootdom.top}
+      }
+      e.changedTouches = [];
+      e.changedTouches.unshift(tmpe)
+      currentIndex=cfu.instance[cid].getCurrentDataIndex(e)
+      legendIndex=cfu.instance[cid].getLegendDataIndex(e)
+      if(tapLegend === true){
+        cfu.instance[cid].touchLegend(e);
+      }
+      if(tooltipShow==true){
+        this.showTooltip(e,cid)
+      }
+      that[cid].callMethod('emitMsg',{name:"getIndex",params:{type:"getIndex",event:tmpe,currentIndex:currentIndex,legendIndex:legendIndex,id:cid, opts: cfu.instance[cid].opts}})
+    },
+    touchStart(e) {
+      let cid = this.rid
+      let ontouch = cfu.option[cid].ontouch
+      if(ontouch == false) return;
+      if(cfu.option[cid].enableScroll === true && e.touches.length == 1){
+        cfu.instance[cid].scrollStart(e);
+      }
+      that[cid].callMethod('emitMsg',{name:"getTouchStart",params:{type:"touchStart",event:e.changedTouches[0],id:cid, opts: cfu.instance[cid].opts}})
+    },
+    touchMove(e) {
+      let cid = this.rid
+      let ontouch = cfu.option[cid].ontouch
+      if(ontouch == false) return;
+      if(cfu.option[cid].enableScroll === true && e.changedTouches.length == 1){
+        cfu.instance[cid].scroll(e);
+      }
+      if(cfu.option[cid].ontap === true && cfu.option[cid].enableScroll === false && cfu.option[cid].onmovetip === true){
+        let rchartdom = document.getElementById('UC'+cid).getBoundingClientRect()
+        let tmpe = { x: e.changedTouches[0].clientX - rchartdom.left, y:e.changedTouches[0].clientY - rchartdom.top + rootdom.top}
+        e.changedTouches.unshift(tmpe)
+        if(cfu.option[cid].tooltipShow === true){
+          this.showTooltip(e,cid)
+        }
+      }
+      if(ontouch === true && cfu.option[cid].enableScroll === true && cfu.option[cid].onzoom === true && e.changedTouches.length == 2){
+        cfu.instance[cid].dobuleZoom(e);
+      }
+      that[cid].callMethod('emitMsg',{name:"getTouchMove",params:{type:"touchMove",event:e.changedTouches[0],id:cid, opts: cfu.instance[cid].opts}})
+    },
+    touchEnd(e) {
+      let cid = this.rid
+      let ontouch = cfu.option[cid].ontouch
+      if(ontouch == false) return;
+      if(cfu.option[cid].enableScroll === true && e.touches.length == 0){
+        cfu.instance[cid].scrollEnd(e);
+      }
+      that[cid].callMethod('emitMsg',{name:"getTouchEnd",params:{type:"touchEnd",event:e.changedTouches[0],id:cid, opts: cfu.instance[cid].opts}})
+    },
+    mouseDown(e) {
+      let cid = this.rid
+      let onmouse = cfu.option[cid].onmouse
+      if(onmouse == false) return;
+      let rchartdom = document.getElementById('UC'+cid).getBoundingClientRect()
+      let tmpe = {}
+      tmpe = { x: e.clientX - rchartdom.left, y:e.clientY - rchartdom.top + rootdom.top}
+      e.changedTouches = [];
+      e.changedTouches.unshift(tmpe)
+      cfu.instance[cid].scrollStart(e)
+      cfu.option[cid].mousedown=true;
+      that[cid].callMethod('emitMsg',{name:"getTouchStart",params:{type:"mouseDown",event:tmpe,id:cid, opts: cfu.instance[cid].opts}})
+    },
+    mouseMove(e) {
+      let cid = this.rid
+      let onmouse = cfu.option[cid].onmouse
+      let tooltipShow = cfu.option[cid].tooltipShow
+      if(onmouse == false) return;
+      let rchartdom = document.getElementById('UC'+cid).getBoundingClientRect()
+      let tmpe = {}
+      tmpe = { x: e.clientX - rchartdom.left, y:e.clientY - rchartdom.top + rootdom.top}
+      e.changedTouches = [];
+      e.changedTouches.unshift(tmpe)
+      if(cfu.option[cid].mousedown){
+        cfu.instance[cid].scroll(e)
+        that[cid].callMethod('emitMsg',{name:"getTouchMove",params:{type:"mouseMove",event:tmpe,id:cid, opts: cfu.instance[cid].opts}})
+      }else if(cfu.instance[cid]){
+        if(tooltipShow==true){
+          this.showTooltip(e,cid)
+        }
+      }
+    },
+    mouseUp(e) {
+      let cid = this.rid
+      let onmouse = cfu.option[cid].onmouse
+      if(onmouse == false) return;
+      let rchartdom = document.getElementById('UC'+cid).getBoundingClientRect()
+      let tmpe = {}
+      tmpe = { x: e.clientX - rchartdom.left, y:e.clientY - rchartdom.top + rootdom.top}
+      e.changedTouches = [];
+      e.changedTouches.unshift(tmpe)
+      cfu.instance[cid].scrollEnd(e)
+      cfu.option[cid].mousedown=false;
+      that[cid].callMethod('emitMsg',{name:"getTouchEnd",params:{type:"mouseUp",event:tmpe,id:cid, opts: cfu.instance[cid].opts}})
+    },
+  }
+}
+</script>
+<!-- #endif -->
+
+<style scoped>
+.chartsview {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex: 1;
+  justify-content: center;
+  align-items: center;
+}
+</style>

Разлика између датотеке није приказан због своје велике величине
+ 42 - 0
uni_modules/qiun-data-charts/components/qiun-error/qiun-error.vue


+ 162 - 0
uni_modules/qiun-data-charts/components/qiun-loading/loading1.vue

@@ -0,0 +1,162 @@
+<template>
+	 <view class="container loading1">
+		<view class="shape shape1"></view>
+		<view class="shape shape2"></view>
+		<view class="shape shape3"></view>
+		<view class="shape shape4"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'loading1',
+		data() {
+			return {
+				
+			};
+		}
+	}
+</script>
+
+<style scoped="true">
+.container {
+  width: 30px;
+  height: 30px;
+  position: relative;
+}
+.container.loading1 {
+  -webkit-transform: rotate(45deg);
+          transform: rotate(45deg);
+}
+
+.container .shape {
+  position: absolute;
+  width: 10px;
+  height: 10px;
+  border-radius: 1px;
+}
+.container .shape.shape1 {
+  left: 0;
+  background-color: #1890FF;
+}
+.container .shape.shape2 {
+  right: 0;
+  background-color: #91CB74;
+}
+.container .shape.shape3 {
+  bottom: 0;
+  background-color: #FAC858;
+}
+.container .shape.shape4 {
+  bottom: 0;
+  right: 0;
+  background-color: #EE6666;
+}
+
+.loading1 .shape1 {
+  -webkit-animation: animation1shape1 0.5s ease 0s infinite alternate;
+          animation: animation1shape1 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation1shape1 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(16px, 16px);
+            transform: translate(16px, 16px);
+  }
+}
+
+@keyframes animation1shape1 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(16px, 16px);
+            transform: translate(16px, 16px);
+  }
+}
+.loading1 .shape2 {
+  -webkit-animation: animation1shape2 0.5s ease 0s infinite alternate;
+          animation: animation1shape2 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation1shape2 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-16px, 16px);
+            transform: translate(-16px, 16px);
+  }
+}
+
+@keyframes animation1shape2 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-16px, 16px);
+            transform: translate(-16px, 16px);
+  }
+}
+.loading1 .shape3 {
+  -webkit-animation: animation1shape3 0.5s ease 0s infinite alternate;
+          animation: animation1shape3 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation1shape3 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(16px, -16px);
+            transform: translate(16px, -16px);
+  }
+}
+
+@keyframes animation1shape3 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(16px, -16px);
+            transform: translate(16px, -16px);
+  }
+}
+.loading1 .shape4 {
+  -webkit-animation: animation1shape4 0.5s ease 0s infinite alternate;
+          animation: animation1shape4 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation1shape4 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-16px, -16px);
+            transform: translate(-16px, -16px);
+  }
+}
+
+@keyframes animation1shape4 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-16px, -16px);
+            transform: translate(-16px, -16px);
+  }
+}
+
+
+</style>

+ 170 - 0
uni_modules/qiun-data-charts/components/qiun-loading/loading2.vue

@@ -0,0 +1,170 @@
+<template>
+	 <view class="container loading2">
+		<view class="shape shape1"></view>
+		<view class="shape shape2"></view>
+		<view class="shape shape3"></view>
+		<view class="shape shape4"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'loading2',
+		data() {
+			return {
+				
+			};
+		}
+	}
+</script>
+
+<style scoped="true">
+.container {
+  width: 30px;
+  height: 30px;
+  position: relative;
+}
+
+.container.loading2 {
+  -webkit-transform: rotate(10deg);
+          transform: rotate(10deg);
+}
+.container.loading2 .shape {
+  border-radius: 5px;
+}
+.container.loading2{
+  -webkit-animation: rotation 1s infinite;
+          animation: rotation 1s infinite;
+}
+
+.container .shape {
+  position: absolute;
+  width: 10px;
+  height: 10px;
+  border-radius: 1px;
+}
+.container .shape.shape1 {
+  left: 0;
+  background-color: #1890FF;
+}
+.container .shape.shape2 {
+  right: 0;
+  background-color: #91CB74;
+}
+.container .shape.shape3 {
+  bottom: 0;
+  background-color: #FAC858;
+}
+.container .shape.shape4 {
+  bottom: 0;
+  right: 0;
+  background-color: #EE6666;
+}
+
+
+.loading2 .shape1 {
+  -webkit-animation: animation2shape1 0.5s ease 0s infinite alternate;
+          animation: animation2shape1 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation2shape1 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(20px, 20px);
+            transform: translate(20px, 20px);
+  }
+}
+
+@keyframes animation2shape1 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(20px, 20px);
+            transform: translate(20px, 20px);
+  }
+}
+.loading2 .shape2 {
+  -webkit-animation: animation2shape2 0.5s ease 0s infinite alternate;
+          animation: animation2shape2 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation2shape2 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-20px, 20px);
+            transform: translate(-20px, 20px);
+  }
+}
+
+@keyframes animation2shape2 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-20px, 20px);
+            transform: translate(-20px, 20px);
+  }
+}
+.loading2 .shape3 {
+  -webkit-animation: animation2shape3 0.5s ease 0s infinite alternate;
+          animation: animation2shape3 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation2shape3 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(20px, -20px);
+            transform: translate(20px, -20px);
+  }
+}
+
+@keyframes animation2shape3 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(20px, -20px);
+            transform: translate(20px, -20px);
+  }
+}
+.loading2 .shape4 {
+  -webkit-animation: animation2shape4 0.5s ease 0s infinite alternate;
+          animation: animation2shape4 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation2shape4 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-20px, -20px);
+            transform: translate(-20px, -20px);
+  }
+}
+
+@keyframes animation2shape4 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-20px, -20px);
+            transform: translate(-20px, -20px);
+  }
+}
+
+</style>

+ 173 - 0
uni_modules/qiun-data-charts/components/qiun-loading/loading3.vue

@@ -0,0 +1,173 @@
+<template>
+	 <view class="container loading3">
+		<view class="shape shape1"></view>
+		<view class="shape shape2"></view>
+		<view class="shape shape3"></view>
+		<view class="shape shape4"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'loading3',
+		data() {
+			return {
+				
+			};
+		}
+	}
+</script>
+
+<style scoped="true">
+.container {
+  width: 30px;
+  height: 30px;
+  position: relative;
+}
+
+ .container.loading3 {
+  -webkit-animation: rotation 1s infinite;
+          animation: rotation 1s infinite;
+}
+.container.loading3 .shape1 {
+  border-top-left-radius: 10px;
+}
+.container.loading3 .shape2 {
+  border-top-right-radius: 10px;
+}
+.container.loading3 .shape3 {
+  border-bottom-left-radius: 10px;
+}
+.container.loading3 .shape4 {
+  border-bottom-right-radius: 10px;
+}
+
+.container .shape {
+  position: absolute;
+  width: 10px;
+  height: 10px;
+  border-radius: 1px;
+}
+.container .shape.shape1 {
+  left: 0;
+  background-color: #1890FF;
+}
+.container .shape.shape2 {
+  right: 0;
+  background-color: #91CB74;
+}
+.container .shape.shape3 {
+  bottom: 0;
+  background-color: #FAC858;
+}
+.container .shape.shape4 {
+  bottom: 0;
+  right: 0;
+  background-color: #EE6666;
+}
+
+.loading3 .shape1 {
+  -webkit-animation: animation3shape1 0.5s ease 0s infinite alternate;
+          animation: animation3shape1 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation3shape1 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(5px, 5px);
+            transform: translate(5px, 5px);
+  }
+}
+
+@keyframes animation3shape1 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(5px, 5px);
+            transform: translate(5px, 5px);
+  }
+}
+.loading3 .shape2 {
+  -webkit-animation: animation3shape2 0.5s ease 0s infinite alternate;
+          animation: animation3shape2 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation3shape2 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-5px, 5px);
+            transform: translate(-5px, 5px);
+  }
+}
+
+@keyframes animation3shape2 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-5px, 5px);
+            transform: translate(-5px, 5px);
+  }
+}
+.loading3 .shape3 {
+  -webkit-animation: animation3shape3 0.5s ease 0s infinite alternate;
+          animation: animation3shape3 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation3shape3 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(5px, -5px);
+            transform: translate(5px, -5px);
+  }
+}
+
+@keyframes animation3shape3 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(5px, -5px);
+            transform: translate(5px, -5px);
+  }
+}
+.loading3 .shape4 {
+  -webkit-animation: animation3shape4 0.5s ease 0s infinite alternate;
+          animation: animation3shape4 0.5s ease 0s infinite alternate;
+}
+
+@-webkit-keyframes animation3shape4 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-5px, -5px);
+            transform: translate(-5px, -5px);
+  }
+}
+
+@keyframes animation3shape4 {
+  from {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  to {
+    -webkit-transform: translate(-5px, -5px);
+            transform: translate(-5px, -5px);
+  }
+}
+</style>

+ 222 - 0
uni_modules/qiun-data-charts/components/qiun-loading/loading4.vue

@@ -0,0 +1,222 @@
+<template>
+	 <view class="container loading5">
+		<view class="shape shape1"></view>
+		<view class="shape shape2"></view>
+		<view class="shape shape3"></view>
+		<view class="shape shape4"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'loading5',
+		data() {
+			return {
+				
+			};
+		}
+	}
+</script>
+
+<style scoped="true">
+.container {
+  width: 30px;
+  height: 30px;
+  position: relative;
+}
+
+.container.loading5 .shape {
+  width: 15px;
+  height: 15px;
+}
+
+.container .shape {
+  position: absolute;
+  width: 10px;
+  height: 10px;
+  border-radius: 1px;
+}
+.container .shape.shape1 {
+  left: 0;
+  background-color: #1890FF;
+}
+.container .shape.shape2 {
+  right: 0;
+  background-color: #91CB74;
+}
+.container .shape.shape3 {
+  bottom: 0;
+  background-color: #FAC858;
+}
+.container .shape.shape4 {
+  bottom: 0;
+  right: 0;
+  background-color: #EE6666;
+}
+
+.loading5 .shape1 {
+  animation: animation5shape1 2s ease 0s infinite reverse;
+}
+
+@-webkit-keyframes animation5shape1 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(0, 15px);
+            transform: translate(0, 15px);
+  }
+  50% {
+    -webkit-transform: translate(15px, 15px);
+            transform: translate(15px, 15px);
+  }
+  75% {
+    -webkit-transform: translate(15px, 0);
+            transform: translate(15px, 0);
+  }
+}
+
+@keyframes animation5shape1 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(0, 15px);
+            transform: translate(0, 15px);
+  }
+  50% {
+    -webkit-transform: translate(15px, 15px);
+            transform: translate(15px, 15px);
+  }
+  75% {
+    -webkit-transform: translate(15px, 0);
+            transform: translate(15px, 0);
+  }
+}
+.loading5 .shape2 {
+  animation: animation5shape2 2s ease 0s infinite reverse;
+}
+
+@-webkit-keyframes animation5shape2 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(-15px, 0);
+            transform: translate(-15px, 0);
+  }
+  50% {
+    -webkit-transform: translate(-15px, 15px);
+            transform: translate(-15px, 15px);
+  }
+  75% {
+    -webkit-transform: translate(0, 15px);
+            transform: translate(0, 15px);
+  }
+}
+
+@keyframes animation5shape2 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(-15px, 0);
+            transform: translate(-15px, 0);
+  }
+  50% {
+    -webkit-transform: translate(-15px, 15px);
+            transform: translate(-15px, 15px);
+  }
+  75% {
+    -webkit-transform: translate(0, 15px);
+            transform: translate(0, 15px);
+  }
+}
+.loading5 .shape3 {
+  animation: animation5shape3 2s ease 0s infinite reverse;
+}
+
+@-webkit-keyframes animation5shape3 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(15px, 0);
+            transform: translate(15px, 0);
+  }
+  50% {
+    -webkit-transform: translate(15px, -15px);
+            transform: translate(15px, -15px);
+  }
+  75% {
+    -webkit-transform: translate(0, -15px);
+            transform: translate(0, -15px);
+  }
+}
+
+@keyframes animation5shape3 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(15px, 0);
+            transform: translate(15px, 0);
+  }
+  50% {
+    -webkit-transform: translate(15px, -15px);
+            transform: translate(15px, -15px);
+  }
+  75% {
+    -webkit-transform: translate(0, -15px);
+            transform: translate(0, -15px);
+  }
+}
+.loading5 .shape4 {
+  animation: animation5shape4 2s ease 0s infinite reverse;
+}
+
+@-webkit-keyframes animation5shape4 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(0, -15px);
+            transform: translate(0, -15px);
+  }
+  50% {
+    -webkit-transform: translate(-15px, -15px);
+            transform: translate(-15px, -15px);
+  }
+  75% {
+    -webkit-transform: translate(-15px, 0);
+            transform: translate(-15px, 0);
+  }
+}
+
+@keyframes animation5shape4 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(0, -15px);
+            transform: translate(0, -15px);
+  }
+  50% {
+    -webkit-transform: translate(-15px, -15px);
+            transform: translate(-15px, -15px);
+  }
+  75% {
+    -webkit-transform: translate(-15px, 0);
+            transform: translate(-15px, 0);
+  }
+}
+
+</style>

+ 229 - 0
uni_modules/qiun-data-charts/components/qiun-loading/loading5.vue

@@ -0,0 +1,229 @@
+<template>
+	 <view class="container loading6">
+		<view class="shape shape1"></view>
+		<view class="shape shape2"></view>
+		<view class="shape shape3"></view>
+		<view class="shape shape4"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'loading6',
+		data() {
+			return {
+				
+			};
+		}
+	}
+</script>
+<style scoped="true">
+.container {
+  width: 30px;
+  height: 30px;
+  position: relative;
+}
+
+.container.loading6 {
+  -webkit-animation: rotation 1s infinite;
+          animation: rotation 1s infinite;
+}
+.container.loading6 .shape {
+  width: 12px;
+  height: 12px;
+  border-radius: 2px;
+}
+.container .shape {
+  position: absolute;
+  width: 10px;
+  height: 10px;
+  border-radius: 1px;
+}
+.container .shape.shape1 {
+  left: 0;
+  background-color: #1890FF;
+}
+.container .shape.shape2 {
+  right: 0;
+  background-color: #91CB74;
+}
+.container .shape.shape3 {
+  bottom: 0;
+  background-color: #FAC858;
+}
+.container .shape.shape4 {
+  bottom: 0;
+  right: 0;
+  background-color: #EE6666;
+}
+
+
+.loading6 .shape1 {
+  -webkit-animation: animation6shape1 2s linear 0s infinite normal;
+          animation: animation6shape1 2s linear 0s infinite normal;
+}
+
+@-webkit-keyframes animation6shape1 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(0, 18px);
+            transform: translate(0, 18px);
+  }
+  50% {
+    -webkit-transform: translate(18px, 18px);
+            transform: translate(18px, 18px);
+  }
+  75% {
+    -webkit-transform: translate(18px, 0);
+            transform: translate(18px, 0);
+  }
+}
+
+@keyframes animation6shape1 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(0, 18px);
+            transform: translate(0, 18px);
+  }
+  50% {
+    -webkit-transform: translate(18px, 18px);
+            transform: translate(18px, 18px);
+  }
+  75% {
+    -webkit-transform: translate(18px, 0);
+            transform: translate(18px, 0);
+  }
+}
+.loading6 .shape2 {
+  -webkit-animation: animation6shape2 2s linear 0s infinite normal;
+          animation: animation6shape2 2s linear 0s infinite normal;
+}
+
+@-webkit-keyframes animation6shape2 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(-18px, 0);
+            transform: translate(-18px, 0);
+  }
+  50% {
+    -webkit-transform: translate(-18px, 18px);
+            transform: translate(-18px, 18px);
+  }
+  75% {
+    -webkit-transform: translate(0, 18px);
+            transform: translate(0, 18px);
+  }
+}
+
+@keyframes animation6shape2 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(-18px, 0);
+            transform: translate(-18px, 0);
+  }
+  50% {
+    -webkit-transform: translate(-18px, 18px);
+            transform: translate(-18px, 18px);
+  }
+  75% {
+    -webkit-transform: translate(0, 18px);
+            transform: translate(0, 18px);
+  }
+}
+.loading6 .shape3 {
+  -webkit-animation: animation6shape3 2s linear 0s infinite normal;
+          animation: animation6shape3 2s linear 0s infinite normal;
+}
+
+@-webkit-keyframes animation6shape3 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(18px, 0);
+            transform: translate(18px, 0);
+  }
+  50% {
+    -webkit-transform: translate(18px, -18px);
+            transform: translate(18px, -18px);
+  }
+  75% {
+    -webkit-transform: translate(0, -18px);
+            transform: translate(0, -18px);
+  }
+}
+
+@keyframes animation6shape3 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(18px, 0);
+            transform: translate(18px, 0);
+  }
+  50% {
+    -webkit-transform: translate(18px, -18px);
+            transform: translate(18px, -18px);
+  }
+  75% {
+    -webkit-transform: translate(0, -18px);
+            transform: translate(0, -18px);
+  }
+}
+.loading6 .shape4 {
+  -webkit-animation: animation6shape4 2s linear 0s infinite normal;
+          animation: animation6shape4 2s linear 0s infinite normal;
+}
+
+@-webkit-keyframes animation6shape4 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(0, -18px);
+            transform: translate(0, -18px);
+  }
+  50% {
+    -webkit-transform: translate(-18px, -18px);
+            transform: translate(-18px, -18px);
+  }
+  75% {
+    -webkit-transform: translate(-18px, 0);
+            transform: translate(-18px, 0);
+  }
+}
+
+@keyframes animation6shape4 {
+  0% {
+    -webkit-transform: translate(0, 0);
+            transform: translate(0, 0);
+  }
+  25% {
+    -webkit-transform: translate(0, -18px);
+            transform: translate(0, -18px);
+  }
+  50% {
+    -webkit-transform: translate(-18px, -18px);
+            transform: translate(-18px, -18px);
+  }
+  75% {
+    -webkit-transform: translate(-18px, 0);
+            transform: translate(-18px, 0);
+  }
+}
+</style>

+ 36 - 0
uni_modules/qiun-data-charts/components/qiun-loading/qiun-loading.vue

@@ -0,0 +1,36 @@
+<template>
+	<view>
+	 <Loading1 v-if="loadingType==1"/>
+	 <Loading2 v-if="loadingType==2"/>
+	 <Loading3 v-if="loadingType==3"/>
+	 <Loading4 v-if="loadingType==4"/>
+	 <Loading5 v-if="loadingType==5"/>
+	</view>
+</template>
+
+<script>
+	import Loading1 from "./loading1.vue";
+	import Loading2 from "./loading2.vue";
+	import Loading3 from "./loading3.vue";
+	import Loading4 from "./loading4.vue";
+	import Loading5 from "./loading5.vue";
+	export default {
+		components:{Loading1,Loading2,Loading3,Loading4,Loading5},
+		name: 'qiun-loading',
+		props: {
+			loadingType: {
+				type: Number,
+				default: 2
+			},
+		},
+		data() {
+			return {
+				
+			};
+		},
+	}
+</script>
+
+<style>
+
+</style>

+ 422 - 0
uni_modules/qiun-data-charts/js_sdk/u-charts/config-echarts.js

@@ -0,0 +1,422 @@
+/*
+ * uCharts®
+ * 高性能跨平台图表库,支持H5、APP、小程序(微信/支付宝/百度/头条/QQ/360)、Vue、Taro等支持canvas的框架平台
+ * Copyright (c) 2021 QIUN®秋云 https://www.ucharts.cn All rights reserved.
+ * Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+ * 复制使用请保留本段注释,感谢支持开源!
+ * 
+ * uCharts®官方网站
+ * https://www.uCharts.cn
+ * 
+ * 开源地址:
+ * https://gitee.com/uCharts/uCharts
+ * 
+ * uni-app插件市场地址:
+ * http://ext.dcloud.net.cn/plugin?id=271
+ * 
+ */
+
+// 通用配置项
+
+// 主题颜色配置:如每个图表类型需要不同主题,请在对应图表类型上更改color属性
+const color = ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'];
+
+const cfe = {
+  //demotype为自定义图表类型
+	"type": ["pie", "ring", "rose", "funnel", "line", "column", "area", "radar", "gauge","candle","demotype"],
+  //增加自定义图表类型,如果需要categories,请在这里加入您的图表类型例如最后的"demotype"
+	"categories": ["line", "column", "area", "radar", "gauge", "candle","demotype"],
+  //instance为实例变量承载属性,option为eopts承载属性,不要删除
+	"instance": {},
+	"option": {},
+  //下面是自定义format配置,因除H5端外的其他端无法通过props传递函数,只能通过此属性对应下标的方式来替换
+  "formatter":{
+    "tooltipDemo1":function(res){
+      let result = ''
+      for (let i in res) {
+      	if (i == 0) {
+      		result += res[i].axisValueLabel + '年销售额'
+      	}
+      	let value = '--'
+      	if (res[i].data !== null) {
+      		value = res[i].data
+      	}
+      	// #ifdef H5
+      	result += '\n' + res[i].seriesName + ':' + value + ' 万元'
+      	// #endif
+      	
+      	// #ifdef APP-PLUS
+      	result += '<br/>' + res[i].marker + res[i].seriesName + ':' + value + ' 万元'
+      	// #endif
+      }
+      return result;
+    },
+    legendFormat:function(name){
+      return "自定义图例+"+name;
+    },
+    yAxisFormatDemo:function (value, index) {
+      return value + '元';
+    },
+    seriesFormatDemo:function(res){
+      return res.name + '年' + res.value + '元';
+    }
+  },
+  //这里演示了自定义您的图表类型的option,可以随意命名,之后在组件上 type="demotype" 后,组件会调用这个花括号里的option,如果组件上还存在eopts参数,会将demotype与eopts中option合并后渲染图表。
+  "demotype":{
+    "color": color,
+    //在这里填写echarts的option即可
+    
+  },
+  //下面是自定义配置,请添加项目所需的通用配置
+	"column": {
+		"color": color,
+		"title": {
+			"text": ''
+		},
+		"tooltip": {
+			"trigger": 'axis'
+		},
+		"grid": {
+			"top": 30,
+			"bottom": 50,
+			"right": 15,
+			"left": 40
+		},
+		"legend": {
+			"bottom": 'left',
+		},
+		"toolbox": {
+			"show": false,
+		},
+		"xAxis": {
+			"type": 'category',
+			"axisLabel": {
+				"color": '#666666'
+			},
+			"axisLine": {
+				"lineStyle": {
+					"color": '#CCCCCC'
+				}
+			},
+			"boundaryGap": true,
+			"data": []
+		},
+		"yAxis": {
+			"type": 'value',
+			"axisTick": {
+				"show": false,
+			},
+			"axisLabel": {
+				"color": '#666666'
+			},
+			"axisLine": {
+				"lineStyle": {
+					"color": '#CCCCCC'
+				}
+			},
+		},
+		"seriesTemplate": {
+			"name": '',
+			"type": 'bar',
+			"data": [],
+			"barwidth": 20,
+			"label": {
+				"show": true,
+        "color": "#666666",
+				"position": 'top',
+			},
+		},
+	},
+	"line": {
+		"color": color,
+		"title": {
+			"text": ''
+		},
+		"tooltip": {
+			"trigger": 'axis'
+		},
+		"grid": {
+			"top": 30,
+			"bottom": 50,
+			"right": 15,
+			"left": 40
+		},
+		"legend": {
+			"bottom": 'left',
+		},
+		"toolbox": {
+			"show": false,
+		},
+		"xAxis": {
+			"type": 'category',
+			"axisLabel": {
+				"color": '#666666'
+			},
+			"axisLine": {
+				"lineStyle": {
+					"color": '#CCCCCC'
+				}
+			},
+			"boundaryGap": true,
+			"data": []
+		},
+		"yAxis": {
+			"type": 'value',
+			"axisTick": {
+				"show": false,
+			},
+			"axisLabel": {
+				"color": '#666666'
+			},
+			"axisLine": {
+				"lineStyle": {
+					"color": '#CCCCCC'
+				}
+			},
+		},
+		"seriesTemplate": {
+			"name": '',
+			"type": 'line',
+			"data": [],
+			"barwidth": 20,
+			"label": {
+				"show": true,
+        "color": "#666666",
+				"position": 'top',
+			},
+		},
+	},
+	"area": {
+		"color": color,
+		"title": {
+			"text": ''
+		},
+		"tooltip": {
+			"trigger": 'axis'
+		},
+		"grid": {
+			"top": 30,
+			"bottom": 50,
+			"right": 15,
+			"left": 40
+		},
+		"legend": {
+			"bottom": 'left',
+		},
+		"toolbox": {
+			"show": false,
+		},
+		"xAxis": {
+			"type": 'category',
+			"axisLabel": {
+				"color": '#666666'
+			},
+			"axisLine": {
+				"lineStyle": {
+					"color": '#CCCCCC'
+				}
+			},
+			"boundaryGap": true,
+			"data": []
+		},
+		"yAxis": {
+			"type": 'value',
+			"axisTick": {
+				"show": false,
+			},
+			"axisLabel": {
+				"color": '#666666'
+			},
+			"axisLine": {
+				"lineStyle": {
+					"color": '#CCCCCC'
+				}
+			},
+		},
+		"seriesTemplate": {
+			"name": '',
+			"type": 'line',
+			"data": [],
+			"areaStyle": {},
+			"label": {
+				"show": true,
+        "color": "#666666",
+				"position": 'top',
+			},
+		},
+	},
+	"pie": {
+		"color": color,
+		"title": {
+			"text": ''
+		},
+		"tooltip": {
+			"trigger": 'item'
+		},
+		"grid": {
+			"top": 40,
+			"bottom": 30,
+			"right": 15,
+			"left": 15
+		},
+		"legend": {
+			"bottom": 'left',
+		},
+		"seriesTemplate": {
+			"name": '',
+			"type": 'pie',
+			"data": [],
+			"radius": '50%',
+			"label": {
+				"show": true,
+        "color": "#666666",
+				"position": 'top',
+			},
+		},
+	},
+	"ring": {
+		"color": color,
+		"title": {
+			"text": ''
+		},
+		"tooltip": {
+			"trigger": 'item'
+		},
+		"grid": {
+			"top": 40,
+			"bottom": 30,
+			"right": 15,
+			"left": 15
+		},
+		"legend": {
+			"bottom": 'left',
+		},
+		"seriesTemplate": {
+			"name": '',
+			"type": 'pie',
+			"data": [],
+			"radius": ['40%', '70%'],
+			"avoidLabelOverlap": false,
+			"label": {
+				"show": true,
+        "color": "#666666",
+				"position": 'top',
+			},
+			"labelLine": {
+				"show": true
+			},
+		},
+	},
+	"rose": {
+		"color": color,
+		"title": {
+			"text": ''
+		},
+		"tooltip": {
+			"trigger": 'item'
+		},
+		"legend": {
+			"top": 'bottom'
+		},
+		"seriesTemplate": {
+			"name": '',
+			"type": 'pie',
+			"data": [],
+			"radius": "55%",
+			"center": ['50%', '50%'],
+			"roseType": 'area',
+		},
+	},
+	"funnel": {
+		"color": color,
+		"title": {
+			"text": ''
+		},
+		"tooltip": {
+			"trigger": 'item',
+			"formatter": "{b} : {c}%"
+		},
+		"legend": {
+			"top": 'bottom'
+		},
+		"seriesTemplate": {
+			"name": '',
+			"type": 'funnel',
+			"left": '10%',
+			"top": 60,
+			"bottom": 60,
+			"width": '80%',
+			"min": 0,
+			"max": 100,
+			"minSize": '0%',
+			"maxSize": '100%',
+			"sort": 'descending',
+			"gap": 2,
+			"label": {
+				"show": true,
+				"position": 'inside'
+			},
+			"labelLine": {
+				"length": 10,
+				"lineStyle": {
+					"width": 1,
+					"type": 'solid'
+				}
+			},
+			"itemStyle": {
+				"bordercolor": '#fff',
+				"borderwidth": 1
+			},
+			"emphasis": {
+				"label": {
+					"fontSize": 20
+				}
+			},
+			"data": [],
+		},
+	},
+	"gauge": {
+		"color": color,
+		"tooltip": {
+        "formatter": '{a} <br/>{b} : {c}%'
+    },
+		"seriesTemplate": {
+			"name": '业务指标',
+      "type": 'gauge',
+      "detail": {"formatter": '{value}%'},
+      "data": [{"value": 50, "name": '完成率'}]
+		},
+	},
+	"candle": {
+		"xAxis": {
+			"data": []
+		},
+		"yAxis": {},
+		"color": color,
+		"title": {
+			"text": ''
+		},
+		"dataZoom": [{
+				"type": 'inside',
+				"xAxisIndex": [0, 1],
+				"start": 10,
+				"end": 100
+			},
+			{
+				"show": true,
+				"xAxisIndex": [0, 1],
+				"type": 'slider',
+				"bottom": 10,
+				"start": 10,
+				"end": 100
+			}
+		],
+		"seriesTemplate": {
+			"name": '',
+			"type": 'k',
+			"data": [],
+		},
+	}
+}
+
+export default cfe;

+ 606 - 0
uni_modules/qiun-data-charts/js_sdk/u-charts/config-ucharts.js

@@ -0,0 +1,606 @@
+/*
+ * uCharts®
+ * 高性能跨平台图表库,支持H5、APP、小程序(微信/支付宝/百度/头条/QQ/360)、Vue、Taro等支持canvas的框架平台
+ * Copyright (c) 2021 QIUN®秋云 https://www.ucharts.cn All rights reserved.
+ * Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+ * 复制使用请保留本段注释,感谢支持开源!
+ * 
+ * uCharts®官方网站
+ * https://www.uCharts.cn
+ * 
+ * 开源地址:
+ * https://gitee.com/uCharts/uCharts
+ * 
+ * uni-app插件市场地址:
+ * http://ext.dcloud.net.cn/plugin?id=271
+ * 
+ */
+
+// 主题颜色配置:如每个图表类型需要不同主题,请在对应图表类型上更改color属性
+const color = ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'];
+
+//事件转换函数,主要用作格式化x轴为时间轴,根据需求自行修改
+const formatDateTime = (timeStamp, returnType)=>{
+  var date = new Date();
+  date.setTime(timeStamp * 1000);
+  var y = date.getFullYear();
+  var m = date.getMonth() + 1;
+  m = m < 10 ? ('0' + m) : m;
+  var d = date.getDate();
+  d = d < 10 ? ('0' + d) : d;
+  var h = date.getHours();
+  h = h < 10 ? ('0' + h) : h;
+  var minute = date.getMinutes();
+  var second = date.getSeconds();
+  minute = minute < 10 ? ('0' + minute) : minute;
+  second = second < 10 ? ('0' + second) : second;
+  if(returnType == 'full'){return y + '-' + m + '-' + d + ' '+ h +':' + minute + ':' + second;}
+  if(returnType == 'y-m-d'){return y + '-' + m + '-' + d;}
+  if(returnType == 'h:m'){return  h +':' + minute;}
+  if(returnType == 'h:m:s'){return  h +':' + minute +':' + second;}
+  return [y, m, d, h, minute, second];
+}
+
+const cfu = {
+  //demotype为自定义图表类型,一般不需要自定义图表类型,只需要改根节点上对应的类型即可
+	"type":["pie","ring","rose","word","funnel","map","arcbar","line","column","mount","bar","area","radar","gauge","candle","mix","tline","tarea","scatter","bubble","demotype"],
+	"range":["饼状图","圆环图","玫瑰图","词云图","漏斗图","地图","圆弧进度条","折线图","柱状图","山峰图","条状图","区域图","雷达图","仪表盘","K线图","混合图","时间轴折线","时间轴区域","散点图","气泡图","自定义类型"],
+  //增加自定义图表类型,如果需要categories,请在这里加入您的图表类型,例如最后的"demotype"
+  //自定义类型时需要注意"tline","tarea","scatter","bubble"等时间轴(矢量x轴)类图表,没有categories,不需要加入categories
+	"categories":["line","column","mount","bar","area","radar","gauge","candle","mix","demotype"],
+  //instance为实例变量承载属性,不要删除
+  "instance":{},
+  //option为opts及eopts承载属性,不要删除
+  "option":{},
+  //下面是自定义format配置,因除H5端外的其他端无法通过props传递函数,只能通过此属性对应下标的方式来替换
+  "formatter":{
+    "yAxisDemo1":function(val, index, opts){return val+'元'},
+    "yAxisDemo2":function(val, index, opts){return val.toFixed(2)},
+    "xAxisDemo1":function(val, index, opts){return val+'年';},
+    "xAxisDemo2":function(val, index, opts){return formatDateTime(val,'h:m')},
+    "seriesDemo1":function(val, index, series, opts){return val+'元'},
+    "tooltipDemo1":function(item, category, index, opts){
+      if(index==0){
+      	return '随便用'+item.data+'年'
+      }else{
+      	return '其他我没改'+item.data+'天'
+      }
+    },
+    "pieDemo":function(val, index, series, opts){
+      if(index !== undefined){
+        return series[index].name+':'+series[index].data+'元'
+      }
+    },
+  },
+  //这里演示了自定义您的图表类型的option,可以随意命名,之后在组件上 type="demotype" 后,组件会调用这个花括号里的option,如果组件上还存在opts参数,会将demotype与opts中option合并后渲染图表。
+  "demotype":{
+    //我这里把曲线图当做了自定义图表类型,您可以根据需要随意指定类型或配置
+    "type": "line",
+    "color": color,
+    "padding": [15,10,0,15],
+    "xAxis": {
+      "disableGrid": true,
+    },
+    "yAxis": {
+      "gridType": "dash",
+      "dashLength": 2,
+    },
+    "legend": {
+    },
+    "extra": {
+    	"line": {
+    		"type": "curve",
+    		"width": 2
+    	},
+    }
+  },
+  //下面是自定义配置,请添加项目所需的通用配置
+	"pie":{
+		"type": "pie",
+    "color": color,
+		"padding": [5,5,5,5],
+		"extra": {
+			"pie": {
+				"activeOpacity": 0.5,
+				"activeRadius": 10,
+				"offsetAngle": 0,
+				"labelWidth": 15,
+				"border": true,
+				"borderWidth": 3,
+				"borderColor": "#FFFFFF"
+			},
+		}
+	},
+	"ring":{
+		"type": "ring",
+    "color": color,
+		"padding": [5,5,5,5],
+		"rotate": false,
+		"dataLabel": true,
+		"legend": {
+			"show": true,
+			"position": "right",
+      "lineHeight": 25,
+		},
+		"title": {
+			"name": "收益率",
+			"fontSize": 15,
+			"color": "#666666"
+		},
+		"subtitle": {
+			"name": "70%",
+			"fontSize": 25,
+			"color": "#7cb5ec"
+		},
+		"extra": {
+			"ring": {
+				"ringWidth":30,
+				"activeOpacity": 0.5,
+				"activeRadius": 10,
+				"offsetAngle": 0,
+				"labelWidth": 15,
+				"border": true,
+				"borderWidth": 3,
+				"borderColor": "#FFFFFF"
+			},
+		},
+	},
+	"rose":{
+		"type": "rose",
+    "color": color,
+		"padding": [5,5,5,5],
+		"legend": {
+			"show": true,
+			"position": "left",
+      "lineHeight": 25,
+		},
+		"extra": {
+			"rose": {
+				"type": "area",
+				"minRadius": 50,
+				"activeOpacity": 0.5,
+				"activeRadius": 10,
+				"offsetAngle": 0,
+				"labelWidth": 15,
+				"border": false,
+				"borderWidth": 2,
+				"borderColor": "#FFFFFF"
+			},
+		}
+	},
+	"word":{
+		"type": "word",
+    "color": color,
+		"extra": {
+			"word": {
+				"type": "normal",
+				"autoColors": false
+			}
+		}
+	},
+	"funnel":{
+		"type": "funnel",
+    "color": color,
+		"padding": [15,15,0,15],
+		"extra": {
+			"funnel": {
+				"activeOpacity": 0.3,
+				"activeWidth": 10,
+				"border": true,
+				"borderWidth": 2,
+				"borderColor": "#FFFFFF",
+				"fillOpacity": 1,
+				"labelAlign": "right"
+			},
+		}
+	},
+	"map":{
+		"type": "map",
+    "color": color,
+		"padding": [0,0,0,0],
+    "dataLabel": true,
+		"extra": {
+			"map": {
+				"border": true,
+				"borderWidth": 1,
+				"borderColor": "#666666",
+				"fillOpacity": 0.6,
+				"activeBorderColor": "#F04864",
+				"activeFillColor": "#FACC14",
+				"activeFillOpacity": 1
+			},
+		}
+	},
+	"arcbar":{
+		"type": "arcbar",
+    "color": color,
+		"title": {
+			"name": "百分比",
+			"fontSize": 25,
+			"color": "#00FF00"
+		},
+		"subtitle": {
+			"name": "默认标题",
+			"fontSize": 15,
+			"color": "#666666"
+		},
+		"extra": {
+			"arcbar": {
+				"type": "default",
+				"width": 12,
+				"backgroundColor": "#E9E9E9",
+				"startAngle": 0.75,
+				"endAngle": 0.25,
+				"gap": 2
+			}
+		}
+	},
+	"line":{
+		"type": "line",
+    "color": color,
+		"padding": [15,10,0,15],
+		"xAxis": {
+      "disableGrid": true,
+		},
+		"yAxis": {
+      "gridType": "dash",
+      "dashLength": 2,
+		},
+		"legend": {
+		},
+		"extra": {
+			"line": {
+				"type": "straight",
+				"width": 2,
+        "activeType": "hollow"
+			},
+		}
+	},
+  "tline":{
+  	"type": "line",
+    "color": color,
+  	"padding": [15,10,0,15],
+  	"xAxis": {
+      "disableGrid": false,
+      "boundaryGap":"justify",
+  	},
+  	"yAxis": {
+      "gridType": "dash",
+      "dashLength": 2,
+      "data":[
+        {
+          "min":0,
+          "max":80
+        }
+      ]
+  	},
+  	"legend": {
+  	},
+  	"extra": {
+  		"line": {
+  			"type": "curve",
+  			"width": 2,
+        "activeType": "hollow"
+  		},
+  	}
+  },
+  "tarea":{
+  	"type": "area",
+    "color": color,
+  	"padding": [15,10,0,15],
+  	"xAxis": {
+      "disableGrid": true,
+      "boundaryGap":"justify",
+  	},
+  	"yAxis": {
+      "gridType": "dash",
+      "dashLength": 2,
+      "data":[
+        {
+          "min":0,
+          "max":80
+        }
+      ]
+  	},
+  	"legend": {
+  	},
+  	"extra": {
+  		"area": {
+  			"type": "curve",
+  			"opacity": 0.2,
+  			"addLine": true,
+  			"width": 2,
+  			"gradient": true,
+        "activeType": "hollow"
+  		},
+  	}
+  },
+	"column":{
+		"type": "column",
+    "color": color,
+		"padding": [15,15,0,5],
+		"xAxis": {
+      "disableGrid": true,
+		},
+		"yAxis": {
+      "data":[{"min":0}]
+		},
+		"legend": {
+		},
+		"extra": {
+			"column": {
+				"type": "group",
+				"width": 30,
+				"activeBgColor": "#000000",
+				"activeBgOpacity": 0.08
+			},
+		}
+	},
+  "mount":{
+  	"type": "mount",
+    "color": color,
+  	"padding": [15,15,0,5],
+  	"xAxis": {
+      "disableGrid": true,
+  	},
+  	"yAxis": {
+      "data":[{"min":0}]
+  	},
+  	"legend": {
+  	},
+  	"extra": {
+  		"mount": {
+  			"type": "mount",
+  			"widthRatio": 1.5,
+  		},
+  	}
+  },
+  "bar":{
+  	"type": "bar",
+    "color": color,
+  	"padding": [15,30,0,5],
+  	"xAxis": {
+      "boundaryGap":"justify",
+      "disableGrid":false,
+      "min":0,
+      "axisLine":false
+  	},
+  	"yAxis": {
+  	},
+  	"legend": {
+  	},
+  	"extra": {
+  		"bar": {
+  			"type": "group",
+  			"width": 30,
+  			"meterBorde": 1,
+  			"meterFillColor": "#FFFFFF",
+  			"activeBgColor": "#000000",
+  			"activeBgOpacity": 0.08
+  		},
+  	}
+  },
+	"area":{
+		"type": "area",
+		"color": color,
+		"padding": [15,15,0,15],
+		"xAxis": {
+      "disableGrid": true,
+		},
+		"yAxis": {
+      "gridType": "dash",
+      "dashLength": 2,
+		},
+		"legend": {
+		},
+		"extra": {
+			"area": {
+				"type": "straight",
+				"opacity": 0.2,
+				"addLine": true,
+				"width": 2,
+				"gradient": false,
+        "activeType": "hollow"
+			},
+		}
+	},
+	"radar":{
+		"type": "radar",
+		"color": color,
+		"padding": [5,5,5,5],
+    "dataLabel": false,
+		"legend": {
+			"show": true,
+			"position": "right",
+      "lineHeight": 25,
+		},
+		"extra": {
+			"radar": {
+				"gridType": "radar",
+				"gridColor": "#CCCCCC",
+				"gridCount": 3,
+				"opacity": 0.2,
+				"max": 200,
+				"labelShow": true
+			},
+		}
+	},
+	"gauge":{
+		"type": "gauge",
+		"color": color,
+		"title": {
+			"name": "66Km/H",
+			"fontSize": 25,
+			"color": "#2fc25b",
+			"offsetY": 50
+		},
+		"subtitle": {
+			"name": "实时速度",
+			"fontSize": 15,
+			"color": "#1890ff",
+			"offsetY": -50
+		},
+		"extra": {
+			"gauge": {
+				"type": "default",
+				"width": 30,
+				"labelColor": "#666666",
+				"startAngle": 0.75,
+				"endAngle": 0.25,
+				"startNumber": 0,
+				"endNumber": 100,
+				"labelFormat": "",
+				"splitLine": {
+					"fixRadius": 0,
+					"splitNumber": 10,
+					"width": 30,
+					"color": "#FFFFFF",
+					"childNumber": 5,
+					"childWidth": 12
+				},
+				"pointer": {
+					"width": 24,
+					"color": "auto"
+				}
+			}
+		}
+	},
+	"candle":{
+		"type": "candle",
+		"color": color,
+		"padding": [15,15,0,15],
+		"enableScroll": true,
+		"enableMarkLine": true,
+		"dataLabel": false,
+		"xAxis": {
+			"labelCount": 4,
+			"itemCount": 40,
+			"disableGrid": true,
+			"gridColor": "#CCCCCC",
+			"gridType": "solid",
+			"dashLength": 4,
+			"scrollShow": true,
+			"scrollAlign": "left",
+			"scrollColor": "#A6A6A6",
+			"scrollBackgroundColor": "#EFEBEF"
+		},
+		"yAxis": {
+		},
+		"legend": {
+		},
+		"extra": {
+			"candle": {
+				"color": {
+					"upLine": "#f04864",
+					"upFill": "#f04864",
+					"downLine": "#2fc25b",
+					"downFill": "#2fc25b"
+				},
+				"average": {
+					"show": true,
+					"name": ["MA5","MA10","MA30"],
+					"day": [5,10,20],
+					"color": ["#1890ff","#2fc25b","#facc14"]
+				}
+			},
+			"markLine": {
+				"type": "dash",
+				"dashLength": 5,
+				"data": [
+					{
+						"value": 2150,
+						"lineColor": "#f04864",
+						"showLabel": true
+					},
+					{
+						"value": 2350,
+						"lineColor": "#f04864",
+						"showLabel": true
+					}
+				]
+			}
+		}
+	},
+	"mix":{
+		"type": "mix",
+		"color": color,
+		"padding": [15,15,0,15],
+		"xAxis": {
+      "disableGrid": true,
+		},
+		"yAxis": {
+			"disabled": false,
+			"disableGrid": false,
+			"splitNumber": 5,
+			"gridType": "dash",
+			"dashLength": 4,
+			"gridColor": "#CCCCCC",
+			"padding": 10,
+			"showTitle": true,
+			"data": []
+		},
+		"legend": {
+		},
+		"extra": {
+			"mix": {
+				"column": {
+					"width": 20
+				}
+			},
+		}
+	},
+	"scatter":{
+		"type": "scatter",
+		"color":color,
+		"padding":[15,15,0,15],
+    "dataLabel":false,
+    "xAxis": {
+      "disableGrid": false,
+      "gridType":"dash",
+      "splitNumber":5,
+      "boundaryGap":"justify",
+      "min":0
+    },
+    "yAxis": {
+      "disableGrid": false,
+      "gridType":"dash",
+    },
+    "legend": {
+    },
+    "extra": {
+    	"scatter": {
+    	},
+    }
+	},
+	"bubble":{
+		"type": "bubble",
+		"color":color,
+		"padding":[15,15,0,15],
+    "xAxis": {
+      "disableGrid": false,
+      "gridType":"dash",
+      "splitNumber":5,
+      "boundaryGap":"justify",
+      "min":0,
+      "max":250
+    },
+    "yAxis": {
+      "disableGrid": false,
+      "gridType":"dash",
+      "data":[{
+        "min":0,
+        "max":150
+      }]
+    },
+    "legend": {
+    },
+    "extra": {
+    	"bubble": {
+        "border":2,
+        "opacity": 0.5,
+    	},
+    }
+	}
+}
+
+export default cfu;

Неке датотеке нису приказане због велике количине промена