jwyu пре 3 година
родитељ
комит
1780cf4475

+ 44 - 0
api/chart.js

@@ -0,0 +1,44 @@
+// 图库模块
+
+import { httpGet, httpPost } from "@/utils/request.js";
+
+/**
+ * 图库列表
+ * @param Keywords 搜索关键词 
+ * @param ClassifyId 图表类型ID 
+ * @param Page 
+ * @param Limit
+ * @returns 
+ */
+export const apiChartList=params=>{
+    return httpGet('/my_chart/getChartList',params)
+}
+
+/**
+ * 图库分类
+ * @param Keywords 搜索关键词 
+ */
+export const apiChartClassifyList=params=>{
+    return httpGet('/my_chart/getChartChassify',params)
+}
+
+/**
+ * 图库移动排序
+ * @param MyChartId 
+ * @param MyChartClassifyId 
+ * @param PrevMyChartId 
+ * @param NextMyChartId 
+ */
+export const apiChartMove=params=>{
+    return httpPost('/my_chart/moveMyChart',params)
+}
+
+/**
+ * 分类移动排序
+ * @param MyChartClassifyId
+ * @param PrevClassifyId
+ * @param NextClassifyId
+ */
+export const apiClassifyMove=params=>{
+    return httpPost('/my_chart/moveMyChartClassify',params)
+}

+ 2 - 2
api/user.js

@@ -49,6 +49,6 @@ export const apiApplyPermission=params=>{
 /**
  * 获取用户最近一条申请单信息
  */
-export const apiLastApplyRecord=()=>{
-	return httpGet('/user/get_last_apply_record',{})
+export const apiLastApplyRecord=(params)=>{
+	return httpGet('/user/get_last_apply_record',params)
 }

Разлика између датотеке није приказан због своје велике величине
+ 537 - 0
components/chartClassifyItem/HM-dragSorts.vue


+ 267 - 0
components/chartClassifyItem/drag.wxs

@@ -0,0 +1,267 @@
+var row_Height = 0;			//行高
+var scrollOffsetTop = 0;	//滚动条位置
+var isAppH5 = false;		//是否APPH5端
+var isLongTouch = false;	//是否开启长按
+var longTouchTime = 350;	//触发长安事件事件
+var isMove = false;			//是否可拖动
+var touchTimer=false;		//长按事件定时器
+function scroll(event,instance){
+	scrollOffsetTop = event.detail.scrollTop;
+}
+function touchstart(event, instance) {
+	isMove = false;
+	if(row_Height==0){
+		var rowStyle = event.instance.getComputedStyle(['height']);
+		row_Height = parseInt(rowStyle.height);//获取行高
+	}
+	var rowData = event.instance.getDataset();
+	var rowtype = rowData.type == "A" ? "B" : "A";
+	//重置样式
+	resetRowStyle(state, instance, rowtype);
+	var state = instance.getState();
+	if (event.touches.length == 1) {
+		state.point = event.touches[0];
+		state.initscrollOffsetTop = scrollOffsetTop;
+		state.islongTap = true;
+		state.rowData = rowData;
+		//读取数据
+		var dataViewDOM = instance.selectComponent('#dataView');
+		var viewData = dataViewDOM.getDataset();
+		isAppH5 = viewData.isapph5&&JSON.parse(viewData.isapph5);
+		isLongTouch = viewData.islongtouch&&JSON.parse(viewData.islongtouch);
+		longTouchTime = parseInt(viewData.longtouchtime);
+		state.rowData.rownum = viewData.rownum;
+		state.rowData.listheight = viewData.listheight;
+	}
+	
+	// 计算shadowRow.style.top
+	var rowIndex = parseInt(rowData.index);
+	var shadowRowTop = rowIndex*row_Height;
+	shadowRowTop = shadowRowTop - scrollOffsetTop;
+	// 加载shadowRow数据
+	instance.callMethod("loadShadowRow", {rowIndex : rowIndex,shadowRowTop:shadowRowTop});
+	state.shadowRowTop = shadowRowTop;
+	var shadowBoxComponent = instance.selectComponent('#shadowRowBox');
+	shadowBoxComponent.setStyle({'top': shadowRowTop + 'px'})
+	//长按事件
+	if(isLongTouch){
+		if(typeof setTimeout !== "undefined"){
+			touchTimer && clearTimeout(touchTimer);
+			touchTimer = setTimeout(function(){
+				longpress(event,instance);
+			},longTouchTime)
+		}
+	}
+}
+function longpress(event,instance){
+	if(isLongTouch){
+		isMove = true;
+		moveRow(instance, 0)
+	}
+}
+function touchmove(event, instance) {
+	
+	var state = instance.getState();
+	var rowData = event.instance.getDataset(); 
+	var movePoint = event.touches[0];
+	var initPoint = state.point;
+	var moveY = movePoint.pageY - initPoint.pageY;	
+	if(isLongTouch){
+		if(typeof setTimeout !== "undefined" && Math.abs(moveY)>10){
+			clearTimeout(touchTimer);
+		}
+		if (!isMove) {
+			return ;
+		}
+	}
+	moveRow(instance, moveY);
+	//阻止滚动页面
+	if(event.preventDefault){
+		event.preventDefault();
+	}
+	return false;
+}
+
+function touchend(event, instance) {
+	if(isLongTouch && typeof setTimeout !== "undefined"){
+		clearTimeout(touchTimer);
+	}
+	
+	if(lastCommand!="stop"){
+		lastCommand = "stop";
+		instance.callMethod("pageScroll", {'command':"stop"});
+	}
+	var state = instance.getState();
+	var rowtype = state.rowData.type;
+	
+	if (typeof state.offset !== "undefined" && state.rowData.index != state.offset && state.offset != null) {
+		instance.callMethod("sort", {
+			index: state.rowData.index,
+			offset: state.offset
+		});
+	} else {
+		resetRowStyle(state, instance, rowtype);
+		resetShadowRowStyle(instance)
+		feedbackGenerator(instance); //震动反馈
+		return false;
+	}
+	resetShadowRowStyle(instance)
+	typeof setTimeout !== "undefined" && setTimeout(function() {
+		resetRowStyle(state, instance, rowtype);
+	}, 500);
+	state.offset = null;
+	oldOffset = null;
+	feedbackGenerator(instance); //震动反馈
+	return false;
+}
+
+function resetRowStyle(state, instance, rowtype) {
+	var blockList = instance.selectAllComponents('.row'+rowtype);
+	for (var i = 0; i < blockList.length; i++) {
+		blockList[i].setStyle({
+			'height': row_Height+'px',
+			'transform': 'none',
+			'-webkit-transform': 'none'
+			});
+		blockList[i].removeClass('ani');
+		blockList[i].removeClass('hide');
+	}
+}
+function resetShadowRowStyle(instance) {
+	var shadowBoxComponent = instance.selectComponent('#shadowRowBox');
+	shadowBoxComponent.removeClass('show');
+	shadowBoxComponent.setStyle({});
+	shadowBoxComponent.removeClass('move');
+}
+var lastCommand = '';
+// move Row 
+function moveRow(instance, moveY) {
+	var state = instance.getState();
+	var initIndex = parseInt(state.rowData.index);
+	var rowtype = state.rowData.type;
+	//显示拖拽行Box
+	var shadowBoxComponent = instance.selectComponent('#shadowRowBox');
+	shadowBoxComponent.hasClass('show') || shadowBoxComponent.addClass('show');
+	//隐藏列表对应行
+	var rowDom = instance.selectComponent('#row' + rowtype + state.rowData.index);
+	rowDom.hasClass('hide') || rowDom.addClass('hide');
+	//拖动shadowRow
+	var shadowRowDom = instance.selectComponent('#shadowRow');
+	shadowRowDom.hasClass('move') || shadowRowDom.addClass('move');
+	shadowRowDom.removeClass('ani');
+	var style = {
+		'transform': 'translateY(' + moveY + 'px) translateZ(10px)',
+		'-webkit-transform': 'translateY(' + moveY + 'px) translateZ(10px)'
+	}
+	shadowRowDom.setStyle(style); 
+	
+	var listheight = state.rowData.listheight
+	var listClientY = state.shadowRowTop + moveY;
+	var tmpscrollListTop = scrollOffsetTop;
+	// 拖拽至边缘滚动视图 距离顶部距离1.5行高触发上滚动 下滚动同理
+	var callMethodData = {
+		command:listClientY<row_Height*1.5?"up":listClientY>listheight-(row_Height*1.5)?"down":"stop",
+		scrollTop:tmpscrollListTop,
+	}
+	//把滚动指令发给逻辑层
+	if(lastCommand!=callMethodData.command){
+		lastCommand = callMethodData.command;
+		instance.callMethod("pageScroll", callMethodData);
+	}
+	
+	var moveOffset = moveY + scrollOffsetTop - state.initscrollOffsetTop;
+	var offset = calcOffset(initIndex, moveOffset);
+	if(offset<=2 || offset>=state.rowData.rownum-2){
+		callMethodData.command = 'stop';
+	}
+	//为保证体验,非APP和H5端,在滚动视图期间不进行位置交换
+	if((!isAppH5) && callMethodData.command!='stop'){
+		return;
+	}
+	oldOffset = oldOffset == null ? initIndex : oldOffset;
+	if (offset < 0 || offset >= state.rowData.rownum) {
+		return;
+	}
+	if (offset == oldOffset) {
+		return;
+	}
+	
+	oldOffset = offset;
+	state.offset = offset;
+	//触发change事件
+	instance.callMethod("change", {
+		index: state.rowData.index,
+		moveTo: state.offset
+	});
+	feedbackGenerator(instance); //震动反馈
+	//根据offset对行进行位置交换
+	var blockList = instance.selectAllComponents('.row' + rowtype);
+	for (var i = 0; i < blockList.length; i++) {
+		if (i == initIndex) {
+			continue;
+		}
+		var translateY = 0;
+		if ((i >= offset && i < initIndex) || (i <= offset && i > initIndex)) {
+			translateY = i < initIndex ? row_Height : -row_Height;
+		}
+		var style = {
+			'height': row_Height+'px',
+			'transform': 'translateY(' + translateY + 'px) translateZ(5px)',
+			'-webkit-transform': 'translateY(' + translateY + 'px) translateZ(5px)'
+		}
+		blockList[i].hasClass('ani') || blockList[i].addClass('ani');
+		blockList[i].setStyle(style);
+	}
+	
+}
+//计算偏移index
+var oldOffset = null;
+function calcOffset(initIndex, moveY) {
+	var offset = initIndex + parseInt(moveY / row_Height); //偏移 行高的倍数
+	var rest = moveY % row_Height;
+	if (rest > 0) {
+		offset = offset + (rest / row_Height >= 0.6 ? 1 : 0);
+		if (offset < oldOffset) {
+			offset = rest / row_Height <= 0.4 ? offset : oldOffset;
+		}
+	} else {
+		offset = offset + (rest / row_Height <= -0.6 ? -1 : 0);
+		if (offset > oldOffset) {
+			offset = rest / row_Height >= -0.4 ? offset : oldOffset;
+		}
+	}
+	return offset;
+}
+
+//触感反馈
+//wxs 不支持条件编译,所以用此方法判断
+var isiOSAPP = typeof plus != "undefined" && plus.os.name == 'iOS';
+var UISelectionFeedbackGenerator;
+var UIImpactFeedbackGenerator;
+var impact
+
+if (isiOSAPP) {
+	UISelectionFeedbackGenerator = plus.ios.importClass("UISelectionFeedbackGenerator");
+	impact = new UISelectionFeedbackGenerator();
+	impact.init();
+}
+
+function feedbackGenerator(instance) {
+	if (isiOSAPP) {
+		impact.selectionChanged();
+	} else {
+		if (typeof plus != "undefined") {
+			plus.device.vibrate(12)
+		} else {
+			instance.callMethod("vibrate");
+		}
+	}
+}
+
+module.exports = {
+	scroll:scroll,
+	longpress:longpress,
+	touchstart: touchstart,
+	touchmove: touchmove,
+	touchend: touchend
+}

+ 13 - 0
package.json

@@ -0,0 +1,13 @@
+{
+    "id": "evils-drag-sorts",
+    "name": "drag-sorts 拖动排序组件",
+    "version": "1.0.2",
+    "description": " 拖动排序组件,",
+    "keywords": [
+        "拖动排序",
+        "拖动",
+        "drag",
+        "sorts",
+        "movable"
+    ]
+}

+ 2 - 2
pages-activity/noAuthority.vue

@@ -63,7 +63,7 @@ export default {
                 }else{
                     if(!this.info.customer_info.status||this.info.customer_info.status!='流失'){
                         uni.redirectTo({
-                            url:"/pages-applyPermission/applyPermission"
+                            url:"/pages-applyPermission/applyPermission?source=2"
                         })
                     }else{//主动调一次申请权限接口 
                         const res=await apiApplyPermission({
@@ -102,7 +102,7 @@ export default {
 }
 </script>
 
-<style lang="scss">
+<style lang="scss" scoped>
 .noauthority-page{
     padding: 34rpx;
     text-align: center;

+ 5 - 2
pages-applyPermission/applyPermission.vue

@@ -76,11 +76,13 @@ export default {
                 name: "",
                 companyName: '',
                 tel: '',
-                permission: ''
+                permission: '',
             },
+            source:"",//来源 1-我的 2-活动 3-图库
         }
     },
-    onLoad(){
+    onLoad(options){
+        this.source=options.source
         this.addEventListenerPermission()
     },
     onUnload(){
@@ -125,6 +127,7 @@ export default {
                 company_name:this.form.companyName,
                 permission:this.form.permission,
                 real_name:this.form.name,
+                source:Number(this.source)
             }
             if(!params.company_name){
                 uni.showToast({

+ 206 - 0
pages-chart/allTypes.vue

@@ -0,0 +1,206 @@
+<template>
+    <view class="chart-alltype-page">
+        <van-sticky style="background: #fff;width:100%">
+            <van-search
+                shape="round"
+                :value="searchVal"
+                placeholder="图表分类搜索"
+                @change="searchValChange"
+                @search="onSearch"
+                @clear="onClearSearch"
+                clear-trigger="always"
+                style="width:100%"
+            />
+        </van-sticky>
+        <view class="empty-box" v-if="myClassifyList.length==0&&pubClassifyList.length==0">
+          <image
+            :src="globalImgUrls.chartEmpty"
+            mode="widthFix"
+          />
+          <view>暂时找不到对应分类,试试别的搜索词吧~</view>
+        </view>
+        <view class="list-wrap" v-else>
+          <block v-if="userInfo.is_inner==1">
+          <van-collapse :value="active" @change="change" :border="false">
+            <van-collapse-item title="公共图库" name="common" :border="false">
+              <view class="list">
+                <view 
+                  :class="['item',selectId==item.myChartClassifyId?'active':'']" 
+                  v-for="item in pubClassifyList" 
+                  :key="item.myChartClassifyName"
+                  @click="handleSelectClassify(item,true)"
+                >{{item.myChartClassifyName}}</view>
+              </view>
+            </van-collapse-item>
+            <van-collapse-item title="我的图库" name="mine" :border="false" custom-class="self-wrap">
+              <view class="list" v-if="myClassifyList.length>0">
+                <dragSorts
+                  :list="myClassifyList"
+                  :selectId="selectId"
+                  :rowHeight="rowHeight"
+                  :listHeight="classifyListHeight+20"
+                  @onclick="myClassifyClick"
+                  @confirm="myClassifyMoveEnd"
+                ></dragSorts>
+              </view>
+            </van-collapse-item>
+          </van-collapse>
+          </block>
+          <block v-else>
+          <view class="list">
+            <view 
+              :class="['item',selectId==item.myChartClassifyId?'active':'']" 
+              v-for="item in pubClassifyList" 
+              :key="item.myChartClassifyName"
+              @click="handleSelectClassify(item,true)"
+            >{{item.myChartClassifyName}}</view>
+          </view>
+          </block>
+        </view>
+    </view>
+</template>
+
+<script>
+import {apiChartClassifyList,apiClassifyMove} from '@/api/chart'
+import dragSorts from '@/components/chartClassifyItem/HM-dragSorts.vue'
+export default {
+  components: {
+    dragSorts
+  },
+  data () {
+    return {
+      searchVal:'',
+      active:['common','mine'],
+      selectId:0,
+      myClassifyList:[],
+      pubClassifyList:[],
+
+      classifyListHeight:0,//我的分类默认高度
+      rowHeight:44,
+    }
+  },
+  onLoad(options) {
+    this.selectId=options.selectId
+    this.getList()
+  },
+  methods: {
+    // 搜索关键词变化
+    searchValChange(e){
+      this.searchVal=e.detail
+    },
+
+    onSearch(){
+      this.getList()
+    },
+
+    onClearSearch(){
+      this.searchVal=''
+      this.getList()
+    },
+
+
+    change(e){
+      this.active=e.detail
+    },
+
+    // 获取分类数据
+    async getList(){
+      const res=await apiChartClassifyList({Keywords:this.searchVal})
+      if(res.code===200){
+        this.myClassifyList=res.data.private_classify||[]
+        this.pubClassifyList=res.data.public_classify||[]
+        this.classifyListHeight=this.rowHeight*this.myClassifyList.length
+      }
+    },
+
+    // 点击我的分类项
+    myClassifyClick(e){
+      this.handleSelectClassify(e.value,false)
+    },
+
+    handleSelectClassify(item,isPublic){
+      this.selectId=item.myChartClassifyId
+      uni.$emit('classifyPageSelect', {selectId:this.selectId,isPublic:isPublic})
+      uni.navigateBack()
+    },
+
+    // 分类移动
+      async myClassifyMoveEnd(e){
+        const moveTarget=e.moveRow
+        const index=e.moveTo// 拖动后的序号
+        const list=e.list//拖动后的列表
+        // console.log(moveTarget.myChartClassifyId);
+        // console.log(index);
+        // console.log(list);
+        const PrevClassifyId=list[index-1]&&list[index-1].myChartClassifyId||0
+        const NextClassifyId=list[index+1]&&list[index+1].myChartClassifyId||0
+        const res=await apiClassifyMove({
+          MyChartClassifyId:moveTarget.myChartClassifyId,
+          PrevClassifyId:PrevClassifyId,
+          NextClassifyId:NextClassifyId
+        })
+        if(res.code===200){
+          this.myClassifyList=list
+        }else{
+          uni.showToast({
+            title: '移动失败',
+            icon: 'none'
+          })
+        }
+      },
+  }
+};
+</script>
+
+<style lang="scss">
+.chart-alltype-page{  
+  .list-wrap{
+    .van-cell__title, .van-cell__value{
+      flex: none !important;
+    }
+    .van-cell:after{
+      border: none !important;
+    }
+    .van-collapse-item__content{
+      padding: 0;
+    }
+    .van-cell__title{
+      font-size: 16px;
+      font-weight: bold;
+    }
+    .van-hairline--top:after{
+      border-top-width: 0 !important;
+    }
+    .self-wrap{
+      margin-top: 60rpx;
+    }
+  }
+  
+  .list{
+    .item{
+      &:first-child{
+        border-top: 1px solid #E5E5E5;
+      }
+      padding: 20rpx 34rpx;
+      border-bottom: 1px solid #E5E5E5;
+      color: #1F243A;
+      font-size: 14px;
+    }
+    .active{
+      color: #E3B377;
+    }
+  }
+
+  .empty-box{
+    text-align: center;
+    font-size: 32rpx;
+    color: #999;
+    padding-top: 150rpx;
+    image{
+      width: 346rpx;
+      margin-bottom: 57rpx;
+    }
+  }
+}
+
+</style>

+ 18 - 0
pages-chart/chartDetail.vue

@@ -0,0 +1,18 @@
+<template>
+  <web-view :src="url"></web-view>
+</template>
+
+<script>
+import {h5BaseUrl} from '../utils/config'
+export default {
+    data () {
+        return {
+            url:''
+        }
+    },
+    onLoad(options) {
+        const timestamp=new Date().getTime()
+        this.url=`${h5BaseUrl}/hzyb/chart/detail?ChartInfoId=${options.chartInfoId}&token=${this.$store.state.user.token}&searchVal=${options.searchVal}&MyChartId=${options.MyChartId}&MyChartClassifyId=${options.MyChartClassifyId}&timestamp=${timestamp}`
+    }
+}
+</script>

+ 25 - 2
pages.json

@@ -22,7 +22,8 @@
 		{
 			"path": "pages/chart/chart",
 			"style": {
-				"navigationBarTitleText": "图库"
+				"navigationBarTitleText": "ETA图库",
+				"enablePullDownRefresh": true
 			}
 		},
 		{
@@ -98,6 +99,24 @@
 					}
 				}
 			]
+		},
+		// 图库模块
+		{
+			"root": "pages-chart",
+			"pages": [
+				{
+					"path": "allTypes",
+					"style":{
+						"navigationBarTitleText": "图表分类"
+					}
+				},
+				{
+					"path": "chartDetail",
+					"style":{
+						"navigationBarTitleText": "图表详情"
+					}
+				}
+			]
 		}
 	],
 		
@@ -146,6 +165,7 @@
 		"navigationBarBackgroundColor": "#FFFFFF",
 		"backgroundColor": "#FFFFFF",
 		"usingComponents":{
+			"drag":"/wxcomponents/drag/index",
 			"van-button":"/wxcomponents/vant/button/index",
 			"van-toast":"/wxcomponents/vant/toast/index",
 			"van-popup":"/wxcomponents/vant/popup/index",
@@ -158,7 +178,10 @@
   		"van-tabs": "/wxcomponents/vant/tabs/index",
 			"van-count-down": "/wxcomponents/vant/count-down/index",
 			"van-empty": "/wxcomponents/vant/empty/index",
-			"van-checkbox": "/wxcomponents/vant/checkbox/index"
+			"van-checkbox": "/wxcomponents/vant/checkbox/index",
+			"van-transition": "/wxcomponents/vant/transition/index",
+			"van-collapse": "/wxcomponents/vant/collapse/index",
+  		"van-collapse-item": "/wxcomponents/vant/collapse-item/index"
 		}
 	}
 }

+ 5 - 1
pages/activity/activity.vue

@@ -350,6 +350,7 @@ export default {
                                 apiApplyPermission({
                                     company_name:res.data.customer_info.company_name,
                                     real_name:res.data.customer_info.name,
+                                    source:2,
                                 }).then(res=>{
                                     if(res.code===200){
                                         console.log('主动申请成功');
@@ -641,6 +642,7 @@ export default {
                             apiApplyPermission({
                                 company_name:res.data.customer_info.company_name,
                                 real_name:res.data.customer_info.name,
+                                source:2,
                             }).then(res=>{
                                 if(res.code===200){
                                     console.log('主动申请成功');
@@ -717,6 +719,7 @@ export default {
                             apiApplyPermission({
                                 company_name:res.data.customer_info.company_name,
                                 real_name:res.data.customer_info.name,
+                                source:2,
                             }).then(res=>{
                                 if(res.code===200){
                                     console.log('主动申请成功');
@@ -768,12 +771,13 @@ export default {
             }else{
                 if(!this.pupData.customer_info.status||this.pupData.customer_info.status!='流失'){
                     uni.navigateTo({
-                        url:"/pages-applyPermission/applyPermission"
+                        url:"/pages-applyPermission/applyPermission?source=2"
                     })
                 }else{//主动调一次申请权限接口 
                     const res=await apiApplyPermission({
                         company_name:this.pupData.customer_info.company_name,
                         real_name:this.pupData.customer_info.name,
+                        source:2,
                     })
                     if(res.code===200){
                         this.pupData.content=`<p>申请已提交</p><p>请等待销售人员与您联系</p>`

+ 432 - 4
pages/chart/chart.vue

@@ -1,14 +1,442 @@
 <template>
-  <view>chart</view>
+    <page-meta :page-style="showFilter? 'overflow: hidden;' : ''" :scroll-top="pageMetaScrollTop" />
+    <view class="chart-page" v-if="hasAuth">
+      <van-sticky style="background: #fff">
+        <view class="flex search-wrap">
+            <van-search
+                shape="round"
+                :value="searchVal"
+                placeholder="图表名称搜索"
+                @change="searchValChange"
+                @search="onSearch"
+                @clear="onClearSearch"
+                clear-trigger="always"
+                style="flex:1"
+            />
+            <image
+              src="../../static/chart/menu.png"
+              mode="widthFix"
+              class="menu-icon"
+              @click="showFilter=true"
+            />
+        </view>
+      </van-sticky>
+      <view class="empty-box" v-if="list.length==0&&finished">
+        <image
+          :src="globalImgUrls.chartEmpty"
+          mode="widthFix"
+        />
+        <view v-if="searchVal">暂时找不到对应图,试试别的搜索词吧~</view>
+        <view v-else>
+          <text v-if="myClassifyList.length==0&&pubClassifyList.length==0">暂时没有图分类,请耐心等待</text>
+          <text v-else>暂时找不到对应图,试试别的分类吧~</text>
+        </view>
+      </view>
+      <view class="chart-list-wrap" v-else>
+        <drag
+          ref="chartDragIns"
+          generic:item="chart-item"
+          :columns="2"
+          :list-data="list"
+          :itemHeight="400"
+          :searchVal="searchVal"
+          @sortend="chartSortend"
+          @click="chartClick"
+          @scroll="chartScroll"
+          :scroll-top="scrollTop"
+        ></drag>
+      </view>
+      
+    </view>
+
+    <!-- 无权限 -->
+    <noAuth :info="noAuthData" v-else></noAuth>
+    
+    <!-- 筛选弹窗 -->
+      <van-popup 
+        :show="showFilter" 
+        @close="showFilter=false"
+        position="bottom"
+        closeable
+        round
+        z-index="99999"
+      >
+        <view class="filter-wrap">
+          <view class="top">
+            <text class="left" @click="goAllTypes">展开</text>
+            <text class="center">全部筛选</text>
+          </view>
+          <view class="filter-list" style="padding-top:200rpx;text-align:center" v-if="myClassifyList.length==0&&pubClassifyList.length==0">
+            暂时没有图分类,请耐心等待
+          </view>
+          <view class="filter-list" v-else>
+            <view class="title" v-if="userInfo.is_inner==1">公共图库</view>
+            <view 
+              :class="['filter-item',selectClassifyId==item.myChartClassifyId?'active':'']" 
+              v-for="item in pubClassifyList" 
+              :key="item.myChartClassifyName"
+              @click="handleSelectClassify(item,true)"
+            >{{item.myChartClassifyName}}</view>
+            <block v-if="myClassifyList.length>0">
+            <view class="title" style="border:none">我的图库</view>
+            <dragSorts
+              :list="myClassifyList"
+              :selectId="selectClassifyId"
+              :rowHeight="rowHeight"
+              :listHeight="classifyListHeight+20"
+              @onclick="myClassifyClick"
+              @confirm="myClassifyMoveEnd"
+            ></dragSorts>
+            </block>
+          </view>
+        </view>
+      </van-popup>
 </template>
 
 <script>
+import chartItem from './component/chartItem.vue'
+import dragSorts from '../../components/chartClassifyItem/HM-dragSorts.vue'
+import noAuth from './component/noAuth.vue'
+import {apiChartList,apiChartClassifyList,apiChartMove,apiClassifyMove} from '@/api/chart'
 export default {
-  onLoad(){
-  }
+    components: {
+      'chart-item':chartItem,
+      dragSorts,
+      noAuth
+    },
+    data() {
+        return {
+          list:[],
+          page:1,
+          finished:false,
+          chartDragIns:null,//图表拖拽实例
+          pageMetaScrollTop:0,
+          scrollTop:0,
+
+          showFilter:false,//显示筛选弹窗
+          pubClassifyList:[],//公共图库分类数据
+          myClassifyList:[],//我的图库分类数据
+          selectClassifyId:0,//选中的分类id
+          isPublic:false,//是否当前显示的列表数据为公共图库数据
+          classifyListHeight:0,//我的分类默认高度
+          rowHeight:44,
+
+          searchVal:'',//图库搜素关键词
+
+          hasAuth:true,//是否有权限
+          noAuthData:null,//没有权限时传给无权限组件的值
+        }
+    },
+    onLoad() {
+      this.getClassifyList('first')
+      this.addEventListenerSelectClassify()
+    },
+    onShow() {
+      this.getClassifyList()
+      // if(!this.hasAuth){
+      //   this.getClassifyList()
+      // }
+    },
+    onUnload(){
+      uni.$off('classifyPageSelect')
+    },
+    onPullDownRefresh() {
+      this.initPage()
+      this.getClassifyList('first')
+      setTimeout(() => {
+        uni.stopPullDownRefresh()
+      }, 1500)
+    },
+    onReachBottom() {
+        if (this.finished) return
+        this.page++
+        this.getList()
+    },
+    onShareAppMessage(res) {
+        return {
+            title: '弘则研报',
+            path: ''
+        }
+    },
+    onPageScroll({ scrollTop }) {
+      this.scrollTop=scrollTop
+    },
+    methods: {
+      // 获取列表
+      async getList(){
+        const res=await apiChartList({
+          Page:this.page,
+          Limit:20,
+          ClassifyId:this.selectClassifyId,
+          Keywords:this.searchVal
+        })
+        if(res.code===200){
+          this.hasAuth=true
+          if(res.data){
+            // 公共图库分类下数据不允许拖动
+            let arr=res.data.map(item=>{
+              return {...item,dragId:item.UniqueCode,fixed: this.isPublic?true:false}
+            })
+            this.list=[...this.list,...arr]
+            setTimeout(() => {
+              this.chartDragIns=this.$refs.chartDragIns
+              this.chartDragIns.init();// 初始化列表
+            }, 100);
+          }else{
+            this.finished=true
+          }
+        }else if(res.code===403){//无权限
+          this.hasAuth=false
+          this.noAuthData=res.data
+        }
+      },
+
+      //图表移动排序结束
+      async chartSortend(e){
+        // curIndex 为排序前元素所在位置  listData为排序后的数组
+        let {curIndex,listData}=e.detail
+        const moveTarget=this.list[curIndex]
+        console.log(moveTarget.ChartName);
+        // 找到移动后所在位置
+        const endIndex=listData.findIndex(item=>item.MyChartId===moveTarget.MyChartId)
+        console.log('移动结束后序号:',endIndex);
+
+        const NextMyChartId=listData[endIndex+1]&&listData[endIndex+1].MyChartId||0
+        const PrevMyChartId=listData[endIndex-1]&&listData[endIndex-1].MyChartId||0
+
+        const res=await apiChartMove({
+          MyChartId:moveTarget.MyChartId,
+          MyChartClassifyId:this.selectClassifyId,
+          PrevMyChartId:PrevMyChartId,
+          NextMyChartId:NextMyChartId,
+        })
+        if(res.code===200){
+          this.list=listData
+          setTimeout(() => {
+            this.chartDragIns.init();
+          }, 100);
+        }else{
+          uni.showToast({
+            title: '移动失败',
+            icon: 'none'
+          })
+        }
+      },
+
+      // 图表点击某项
+      chartClick(e){
+        console.log(e.detail.data);
+        uni.navigateTo({
+          url:`/pages-chart/chartDetail?chartInfoId=${e.detail.data.ChartInfoId}&searchVal=${this.searchVal}&MyChartId=${e.detail.data.MyChartId}&MyChartClassifyId=${this.selectClassifyId}`
+        })
+      },
+
+      // 图表移动时滚动
+      chartScroll(e){
+        uni.stopPullDownRefresh()
+        this.pageMetaScrollTop=e.detail.scrollTop
+      },
+
+      // 获取分类数据
+      async getClassifyList(no){
+        const res=await apiChartClassifyList()
+        if(res.code===200){
+          this.myClassifyList=res.data.private_classify||[]
+          this.pubClassifyList=res.data.public_classify||[]
+          this.classifyListHeight=this.rowHeight*this.myClassifyList.length
+          if(no=='first'){
+            if(res.data.private_classify){
+              this.selectClassifyId=res.data.private_classify&&res.data.private_classify[0].myChartClassifyId||''
+              this.isPublic=false
+            }else{
+              this.selectClassifyId=res.data.public_classify&&res.data.public_classify[0].myChartClassifyId||''
+              this.isPublic=true 
+            }
+            this.getList()
+          }
+          
+        }else if(res.code===403){//无权限
+          this.hasAuth=false
+          this.noAuthData=res.data
+        }
+        
+      },
+
+      // 我的分类点击
+      myClassifyClick(e){
+        this.handleSelectClassify(e.value,false)
+      },
+
+      // 选中分类
+      handleSelectClassify(item,isPublic){
+        this.initPage()
+        this.searchVal=''
+        this.selectClassifyId=item.myChartClassifyId
+        this.isPublic=isPublic
+        this.getList()
+        this.showFilter=false
+      },
+
+      // 跳转全部分类
+      goAllTypes(){
+        uni.navigateTo({ 
+          url: '/pages-chart/allTypes?selectId='+this.selectClassifyId
+        })
+        this.showFilter=false
+      },
+
+      // 分类移动
+      async myClassifyMoveEnd(e){
+        const moveTarget=e.moveRow
+        const index=e.moveTo// 拖动后的序号
+        const list=e.list//拖动后的列表
+        // console.log(moveTarget.myChartClassifyId);
+        // console.log(index);
+        // console.log(list);
+        const PrevClassifyId=list[index-1]&&list[index-1].myChartClassifyId||0
+        const NextClassifyId=list[index+1]&&list[index+1].myChartClassifyId||0
+        const res=await apiClassifyMove({
+          MyChartClassifyId:moveTarget.myChartClassifyId,
+          PrevClassifyId:PrevClassifyId,
+          NextClassifyId:NextClassifyId
+        })
+        if(res.code===200){
+          this.myClassifyList=list
+        }else{
+          uni.showToast({
+            title: '移动失败',
+            icon: 'none'
+          })
+        }
+      },
+
+      // 搜索关键词变化
+      searchValChange(e){
+        this.searchVal=e.detail
+      },
+
+      // 确认搜索 搜索图表下的都不允许拖动排序
+      onSearch(){
+        if(!this.searchVal){
+          uni.showToast({
+            title:"请输入搜索关键词",
+            icon:'none'
+          })
+          return
+        }
+        this.initPage()
+        this.isPublic=true
+        this.getList()
+      },
+
+      // 清除搜索内容
+      onClearSearch(){
+        this.initPage()
+        this.searchVal=''
+        this.isPublic=false
+        this.getClassifyList('first')
+      },
+
+      // 初始化页面数据 
+      initPage(){
+        this.page=1
+        this.list=[]
+        this.finished=false
+        this.selectClassifyId=0
+        this.pageMetaScrollTop=0
+        this.scrollTop=0
+      },
+
+      // 监听分类页面选中分类事件
+      addEventListenerSelectClassify(){
+        uni.$on('classifyPageSelect',e=>{
+            console.log(e);
+            this.handleSelectClassify({myChartClassifyId:e.selectId},e.isPublic)
+        })
+      },
+    }
 }
 </script>
 
-<style>
+<style lang="scss" scoped>
+.chart-page {
+    min-height: calc(100vh - calc(50px + constant(safe-area-inset-bottom)));
+    min-height: calc(100vh - calc(50px + env(safe-area-inset-bottom)));
+    background-color: #f9f9f9;
+    position: relative;
+}
+.search-wrap {
+    background-color: #fff;
+    padding: 30rpx 34rpx;
+    align-items: center;
+    .menu-icon{
+      width: 52rpx;
+      height: 40rpx;
+      display: block;
+      flex-shrink: 0;
+      margin-left: 30rpx;
+    }
+    .van-search{
+      padding: 0;
+    }
+}
+.chart-list-wrap{
+  padding: 24rpx 15rpx;
+}
+
+.filter-wrap{
+  .top{
+    height: 105rpx;
+    background: #FFFFFF;
+    box-shadow: 0px 3rpx 6rpx rgba(206, 206, 206, 0.16);
+    position: relative;
+    .left{
+      position: absolute;
+      left: 34rpx;
+      top: 50%;
+      transform: translateY(-50%);
+      font-size: 16px;
+      color: #E3B377;
+    }
+    .center{
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%,-50%);
+      font-size: 16px;
+      color: #1F243A;
+    }
+  }
+  .filter-list{
+    height: 60vh;
+    overflow-y: auto;
+    .title{
+      font-size: 16px;
+      font-weight: bold;
+      color: #1F243A;
+      padding: 60rpx 0 30rpx 34rpx;
+      border-bottom: 1px solid #E5E5E5;
+    }
+    .filter-item{
+      padding: 20rpx 34rpx;
+      border-bottom: 1px solid #E5E5E5;
+      font-size: 14px;
+      color: #1F243A;
+    }
+    .active{
+      color: #E3B377;
+    }
+  }
+}
 
+.empty-box{
+  text-align: center;
+  font-size: 32rpx;
+  color: #999;
+  padding-top: 150rpx;
+  image{
+    width: 346rpx;
+    margin-bottom: 57rpx;
+  }
+}
 </style>

+ 90 - 0
pages/chart/component/chartItem.vue

@@ -0,0 +1,90 @@
+<template>
+  <view class="chart-item" :data-id="itemData.dragId" @click.stop="itemClick">
+      <rich-text 
+        class="van-multi-ellipsis--l2 title" 
+        :nodes="formatTitle(itemData.ChartName)"
+        @longpress.stop="chartTitle(itemData,$event)"
+      ></rich-text>
+      <view class="pop-title" v-if="showPopTitle">{{itemData.ChartName}}</view>
+      <image lazy-load class="img" :src="itemData.ChartImage" mode="aspectFill"/>
+  </view>
+</template>
+
+<script>
+export default {
+    name:"chartItem",
+    props: {
+        itemData:{
+          type: Object,
+			    value: {}
+        },
+        searchVal:""
+    },
+    data () {
+      return {
+        showPopTitle:false,
+        title:"",
+      }
+    },
+    methods: {
+      itemClick(){
+        this.$emit('click')
+        this.showPopTitle=false
+      },
+
+      formatTitle(e){
+        const reg=new RegExp(this.searchVal,'gi')
+        return e.replace(reg,`<span style="color:#FF0000">${this.searchVal}</span>`)
+      },
+
+      // 长按标题
+      chartTitle(data,e){
+        this.showPopTitle=true
+        this.title=data.ChartName
+        uni.setClipboardData({
+          data: data.ChartName,
+          success: (result) => {},
+          fail: (error) => {}
+        })
+      }
+    }
+}
+</script>
+
+<style lang='scss' scoped>
+.chart-item{
+    height: 380rpx;
+    background: #FFFFFF;
+    box-shadow: 0px 0px 12rpx rgba(154, 141, 123, 0.16);
+    border-radius: 8rpx;
+    margin-left: 10rpx;
+    margin-right: 10rpx;
+    margin-bottom: 20rpx;
+    padding: 20rpx 20rpx 33rpx 20rpx;
+    position: relative;
+    .title{
+      font-size: 26rpx;
+      min-height: 68rpx;
+    }
+    .pop-title{
+      position: absolute;
+      width: 80%;
+      font-size: 24rpx;
+      padding: 8rpx;
+      border-radius: 4rpx;
+      top: 80rpx;
+      left: 10%;
+      background-color: #fff;
+      box-shadow: 0px 0px 12rpx rgba(154, 141, 123, 0.16);
+    }
+    .img{
+      margin-top: 10rpx;
+      width: 100%;
+      height: 261rpx;
+      display: block;
+      margin-left: auto;
+      margin-right: auto;
+      background-color: #f6f6f6;
+    }
+}
+</style>

+ 104 - 0
pages/chart/component/noAuth.vue

@@ -0,0 +1,104 @@
+<template>
+  <view class="chart-no-auth">
+        <image class="img" :src="globalImgUrls.activityNoAuth" mode="widthFix" v-if="authType!=4"></image>
+        <image class="img-wait" :src="globalImgUrls.chartWait" mode="widthFix" v-else></image>
+        <block v-if="authType==1">
+            <view style="margin-bottom:15px">您暂无权限查看图库</view>
+            <view>若想查看请联系对口销售</view>
+            <!-- <view>{{info.name}}:{{info.mobile}}</view> -->
+            <view class="global-btn-yellow-change btn" style="margin-top:30px" @click="handleCall">联系销售</view>
+        </block>
+
+        <block v-if="authType==2">
+            <view style="margin-bottom:15px">您的权限已到期,暂时无法查看图库</view>
+            <view>若想继续查看请联系对口销售</view>
+            <!-- <view>{{info.name}}:{{info.mobile}}</view> -->
+            <view class="global-btn-yellow-change btn" style="margin-top:30px" @click="handleCall">联系销售</view>
+        </block>
+
+        <block v-if="authType==3">
+            <view style="margin-bottom:15px">您暂无权限查看图库</view>
+            <view>若想查看可以申请开通</view>
+            <view class="global-btn-yellow-change btn" style="margin-top:30px" @click="handleApply">立即申请</view>
+        </block>
+
+        <block v-if="authType==4">
+            <view style="margin-bottom:15px">您已提交申请</view>
+            <view>请等待销售人员与您联系</view>
+        </block>
+  </view>
+</template>
+
+<script>
+import {apiApplyPermission} from '@/api/user'
+export default {    
+    props: {
+        info:null
+    },
+    computed: {
+        authType(){
+            if(!this.info) return
+            // 该客户为冻结、试用暂停状态;该客户为正式、试用、永续状态,但联系人图表权限未开启或禁用
+            if(this.info.type==='contact'){
+                return 1
+            }
+            if(this.info.type==='expired'){
+                return 2
+            }
+            if(this.info.type==='apply'&&!this.info.customer_info.has_apply){
+                return 3
+            }
+            if(this.info.type==='apply'&&this.info.customer_info.has_apply){
+                return 4
+            }
+        }
+    },
+    methods: {
+        handleCall(){
+            uni.makePhoneCall({
+                phoneNumber: this.info.mobile,
+                success: (result) => {},
+                fail: (error) => {}
+            })
+        },
+
+        handleApply(){
+            if(this.info.customer_info.status=='流失'){
+                apiApplyPermission({
+                    company_name:this.info.customer_info.company_name,
+                    real_name:this.info.customer_info.name,
+                    source:3,
+                }).then(res=>{
+                    uni.navigateTo({url:'/pages-applyPermission/applyResult'})
+                })
+                return
+            }
+            uni.navigateTo({ url: '/pages-applyPermission/applyPermission?source=3' })
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.chart-no-auth{
+    padding: 34rpx;
+    text-align: center;
+    font-size: $global-font-size-lg;
+    .img{
+        width: 100%;
+        margin-bottom: 50rpx;
+    }
+    .img-wait{
+        margin-top: 200rpx;
+        width: 186rpx;
+        margin-bottom: 50rpx;
+    }
+    .btn{
+        width: 380rpx;
+        line-height: 70rpx;
+        margin-left: auto;
+        margin-right: auto;
+        margin-top: 40rpx;
+    }
+}
+</style>

+ 2 - 2
pages/user/user.vue

@@ -104,11 +104,11 @@
 		},
 		methods: {
 			async handleGoApplyPermission(){
-				const res=await apiLastApplyRecord()
+				const res=await apiLastApplyRecord({source:1})
 				if(res.code===200){
 					if(!res.data){
 						uni.navigateTo({
-							url:"/pages-applyPermission/applyPermission"
+							url:"/pages-applyPermission/applyPermission?source=1"
 						})
 					}else{
 						this.pupData.show=true

BIN
static/chart/drag-icon.png


BIN
static/chart/menu.png


+ 14 - 1
utils/config.js

@@ -11,8 +11,20 @@ if(env.envVersion==='develop'){//开发
     baseApiUrl='https://yanbao.hzinsights.com'
 }
 
+// h5页面根路径
+let h5BaseUrl=''
+if(env.envVersion==='develop'){//开发
+    h5BaseUrl='http://advisoryadmin.brilliantstart.cn/xcx_h5'
+}else if(env.envVersion==='trial'){//体验版
+    h5BaseUrl='http://advisoryadmin.brilliantstart.cn/xcx_h5'
+}else if(env.envVersion==='release'){//正式版
+    h5BaseUrl='https://details.hzinsights.com'
+}
+
 // 配置图片资源 https://hzstatic.hzinsights.com/static/icon/hzyb/
 const globalImgUrls={
+    chartWait:'https://hzstatic.hzinsights.com/static/icon/hzyb/chart_wait.png',
+    chartEmpty:'https://hzstatic.hzinsights.com/static/icon/hzyb/chart_empty.png',
     activityNoAuth:'https://hzstatic.hzinsights.com/static/icon/hzyb/activity_no_auth.png',
     imgSuccess:'https://hzstatic.hzinsights.com/static/icon/hzyb/success_icon.png',
     loginTop:'https://hzstatic.hzinsights.com/static/icon/hzyb/login_top_img.png',
@@ -62,5 +74,6 @@ const defaultTabBarListConfig=[
 module.exports={
     baseApiUrl,
     globalImgUrls,
-    defaultTabBarListConfig
+    defaultTabBarListConfig,
+    h5BaseUrl
 }

+ 18 - 2
utils/request.js

@@ -3,6 +3,8 @@ import {apiWechatLogin} from '@/api/user'
 import CryptoJS from './crypto'
 import store from '@/store/index'
 
+const ENV=uni.getAccountInfoSync().miniProgram
+
 // 请求错误消息提示
 const showError=error=>{
 	let errMsg=''
@@ -91,8 +93,22 @@ const http=(url,params,method)=>{
 				Authorization:store.state.user.token,
 			},
 			success(e) {
-				// let res=JSON.parse(CryptoJS.Des3Decrypt(e.data));//解密
-				let res=e.data
+				// 接口404
+				if(e.statusCode===404){
+					setTimeout(()=>{
+						uni.showToast({
+							title:'network:404',
+							icon:'none'
+						})
+					},0)
+					return
+				}
+				let res
+				if(ENV.envVersion==='release'){
+					res=JSON.parse(CryptoJS.Des3Decrypt(e.data));//解密
+				}else{
+					res=e.data
+				}
 				if(res.code!==200&&res.code!==403&&res.code!==4001&&res.code!==401){
 					showError(res)
 				}

+ 224 - 0
wxcomponents/drag/index.js

@@ -0,0 +1,224 @@
+/**
+ * 版本号比较
+ */
+const 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])
+		const num2 = parseInt(v2[i])
+
+		if (num1 > num2) {
+			return 1
+		} else if (num1 < num2) {
+			return -1
+		}
+	}
+
+	return 0
+}
+
+Component({
+	externalClasses: ['item-wrap-class'],
+	options: {
+		multipleSlots: true
+	},
+	properties: {
+		extraNodes: {            // 额外节点
+			type: Array,
+			value: []
+		},
+		listData: {              // 数据源
+			type: Array,
+			value: []
+		},
+		columns: {               // 列数
+			type: Number,
+			value: 1
+		},
+		topSize: {               // 顶部固定高度
+			type: Number,
+			value: 0
+		},
+		bottomSize: {            // 底部固定高度
+			type: Number,
+			value: 0
+		},
+		itemHeight: {            // 每个 item 高度, 用于计算 item-wrap 高度
+			type: Number,
+			value: 0
+		},
+		scrollTop: {             // 页面滚动高度
+			type: Number,
+			value: 0
+		},
+
+		searchVal:{				 //搜索值
+			type:String,
+			value:''
+		}
+	},
+	data: {
+		/* 未渲染数据 */
+		baseData: {},
+		pageMetaSupport: false,                                 // 当前版本是否支持 page-meta 标签
+		platform: '',                                           // 平台信息
+		listWxs: [],                                            // wxs 传回的最新 list 数据
+		rows: 0,                                                // 行数
+
+		/* 渲染数据 */
+		wrapStyle: '',                                          // item-wrap 样式
+		list: [],                                               // 渲染数据列
+		dragging: false,
+	},
+	methods: {
+		vibrate() {
+			if (this.data.platform !== "devtools") wx.vibrateShort();
+		},
+		pageScroll(e) {
+			if (this.data.pageMetaSupport) {
+				this.triggerEvent("scroll", {
+					scrollTop: e.scrollTop
+				});
+			} else {
+				wx.pageScrollTo({
+					scrollTop: e.scrollTop,
+					duration: 300
+				});
+			}
+		},
+		drag(e) {
+			this.setData({
+				dragging: e.dragging
+			})
+		},
+		listChange(e) {
+			this.data.listWxs = e.list;
+		},
+		itemClick(e) {
+			let index = e.currentTarget.dataset.index;
+			let item = this.data.listWxs[index];
+
+			this.triggerEvent('click', {
+				key: item.realKey,
+				data: item.data,
+				extra: e.detail
+			});
+		},
+		/**
+		 *  初始化获取 dom 信息
+		 */
+		initDom() {
+			let {windowWidth, windowHeight, platform, SDKVersion} = wx.getSystemInfoSync();
+			let remScale = (windowWidth || 375) / 375;
+
+			this.data.pageMetaSupport = compareVersion(SDKVersion, '2.9.0') >= 0;
+			this.data.platform = platform;
+
+			let baseData = {};
+			baseData.windowHeight = windowHeight;
+			baseData.realTopSize = this.data.topSize * remScale / 2;
+			baseData.realBottomSize = this.data.bottomSize * remScale / 2;
+			baseData.columns = this.data.columns;
+			baseData.rows =  this.data.rows;
+
+			const query = this.createSelectorQuery();
+			query.select(".item").boundingClientRect();
+			query.select(".item-wrap").boundingClientRect();
+			query.exec((res) => {
+				baseData.itemWidth = res[0].width;
+				baseData.itemHeight = res[0].height;
+				baseData.wrapLeft = res[1].left;
+				baseData.wrapTop = res[1].top + this.data.scrollTop;
+				this.setData({
+					dragging: false,
+					baseData
+				});
+			});
+		},
+		/**
+		 * column 改变时候需要清空 list, 以防页面溢出
+		 */
+		columnChange() {
+			this.setData({
+				list: []
+			})
+			this.init();
+		},
+		/**
+		 *  初始化函数
+		 *  {listData, topSize, bottomSize, itemHeight} 参数改变需要手动调用初始化方法
+		 */
+		init() {
+			// 初始必须为true以绑定wxs中的函数,
+			this.setData({dragging: true});
+
+			let delItem = (item, extraNode) => ({
+				id: item.dragId,
+				extraNode: extraNode,
+				fixed: item.fixed,
+				slot: item.slot,
+				data: item
+			});
+
+			let {listData, extraNodes} = this.data;
+			let _list = [], _before = [], _after = [], destBefore = [], destAfter = [];
+
+			extraNodes.forEach((item, index) => {
+				if (item.type === "before") {
+					_before.push(delItem(item, true));
+				} else if (item.type === "after") {
+					_after.push(delItem(item, true));
+				} else if (item.type === "destBefore") {
+					destBefore.push(delItem(item, true));
+				} else if (item.type === "destAfter") {
+					destAfter.push(delItem(item, true));
+				}
+			});
+
+			// 遍历数据源增加扩展项, 以用作排序使用
+			listData.forEach((item, index) => {
+				destBefore.forEach((i) => {
+					if (i.data.destKey === index) _list.push(i);
+				});
+				_list.push(delItem(item, false));
+				destAfter.forEach((i) => {
+					if (i.data.destKey === index) _list.push(i);
+				});
+			});
+
+			let i = 0, columns = this.data.columns;
+			let list = (_before.concat(_list, _after) || []).map((item, index) => {
+				item.realKey = item.extraNode ? -1 : i++; // 真实顺序
+				item.sortKey = index; // 整体顺序
+				item.tranX = `${(item.sortKey % columns) * 100}%`;
+				item.tranY = `${Math.floor(item.sortKey / columns) * 100}%`;
+				return item;
+			});
+
+			this.data.rows = Math.ceil(list.length / columns);
+
+			this.setData({
+				list,
+				listWxs: list,
+				wrapStyle: `height: ${this.data.rows * this.data.itemHeight}rpx`
+			});
+			if (list.length === 0) return;
+
+			// 异步加载数据时候, 延迟执行 initDom 方法, 防止基础库 2.7.1 版本及以下无法正确获取 dom 信息
+			setTimeout(() => this.initDom(), 0);
+		}
+	},
+	ready() {
+		this.init();
+	}
+});

+ 6 - 0
wxcomponents/drag/index.json

@@ -0,0 +1,6 @@
+{
+	"component": true,
+	"componentGenerics": {
+		"item": true
+	}
+}

+ 20 - 0
wxcomponents/drag/index.scss

@@ -0,0 +1,20 @@
+// @import "../../assets/css/variables";
+
+.item-wrap {
+	position: relative;
+	.item {
+		position: absolute;
+		z-index: 1;
+		top: 0;
+		left: 0;
+		&.tran {
+			transition: transform 0.3s !important;
+		}
+		&.cur {
+			z-index: 2;
+		}
+		&.fixed {
+			z-index: 0 !important;
+		}
+	}
+}

+ 28 - 0
wxcomponents/drag/index.wxml

@@ -0,0 +1,28 @@
+<wxs module="handler" src="./index.wxs"></wxs>
+
+<view class="item-wrap item-wrap-class"
+			list="{{list}}"
+			style="{{wrapStyle}}"
+			baseData="{{baseData}}"
+			change:list="{{handler.listObserver}}"
+			change:baseData="{{handler.baseDataObserver}}">
+
+	<view
+		class="item"
+		wx:for="{{list}}"
+		wx:key="id"
+		data-index="{{index}}"
+		style="width: {{100/columns}}%"
+		bind:longpress="{{handler.longPress}}"
+		catch:touchmove="{{dragging ? handler.touchMove : ''}}"
+		catch:touchend="{{dragging ? handler.touchEnd : ''}}">
+
+		<block wx:if="{{item.extraNode}}">
+			<slot name="{{item.slot}}"></slot>
+		</block>
+		<block wx:else>
+			<item data-index="{{index}}" searchVal="{{searchVal}}" columns="{{columns}}" item-data="{{item.data}}" bind:click="itemClick"/>
+		</block>
+
+	</view>
+</view>

+ 223 - 0
wxcomponents/drag/index.wxs

@@ -0,0 +1,223 @@
+var isOutRange = function (x1, y1, x2, y2, x3, y3) {
+	return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3
+};
+
+var sortCore = function (sKey, eKey, st) {
+	var _ = st.baseData;
+
+	var excludeFix = function (cKey, type) {
+		if (st.list[cKey].fixed) { // fixed 元素位置不会变化, 这里直接用 cKey(sortKey) 获取, 更加快捷
+			type ? --cKey : ++cKey;
+			return excludeFix(cKey, type);
+		}
+		return cKey;
+	}
+
+	// 先获取到 endKey 对应的 realKey, 防止下面排序过程中该 realKey 被修改
+	var endRealKey = -1;
+	st.list.forEach(function (item) {
+		if(item.sortKey === eKey) endRealKey = item.realKey;
+	});
+
+	return st.list.map(function (item) {
+		if (item.fixed) return item;
+		var cKey = item.sortKey;
+		var rKey = item.realKey;
+
+		if (sKey < eKey) {
+			// 正序拖动
+			if (cKey > sKey && cKey <= eKey) {
+				--rKey;
+				cKey = excludeFix(--cKey, true);
+			} else if (cKey === sKey) {
+				rKey = endRealKey;
+				cKey = eKey;
+			}
+		} else if (sKey > eKey) {
+			// 倒序拖动
+			if (cKey >= eKey && cKey < sKey) {
+				++rKey
+				cKey = excludeFix(++cKey, false);
+			} else if (cKey === sKey) {
+				rKey = endRealKey;
+				cKey = eKey;
+			}
+		}
+
+		if (item.sortKey !== cKey) {
+			item.tranX = (cKey % _.columns) * 100 + "%";
+			item.tranY = Math.floor(cKey / _.columns) * 100 + "%";
+			item.sortKey = cKey;
+			item.realKey = rKey;
+		}
+		return item;
+	});
+}
+
+var triggerCustomEvent = function(list, type, ins,curIndex) {
+	var _list = [], listData = [];
+
+	list.forEach(function (item) {
+		_list[item.sortKey] = item;
+	});
+
+	_list.forEach(function (item) {
+		if (!item.extraNode) {
+			listData.push(item.data);
+		}
+	});
+
+	ins.triggerEvent(type, {listData: listData,curIndex:curIndex});
+}
+
+var longPress = function (event, ownerInstance) {
+	var ins = event.instance;
+	var st = ownerInstance.getState();
+	var _ = st.baseData;
+
+	var sTouch = event.changedTouches[0];
+	if (!sTouch) return;
+
+	st.cur = ins.getDataset().index;
+
+	// 初始项是固定项则返回
+	var item = st.list[st.cur];
+	if (item && item.fixed) return;
+
+	// 如果已经在 drag 中则返回, 防止多指触发 drag 动作, touchstart 事件中有效果
+	if (st.dragging) return;
+	st.dragging = true;
+	ownerInstance.callMethod("drag", {dragging: true});
+
+	// 计算X,Y轴初始位移, 使 item 中心移动到点击处, 单列时候X轴初始不做位移
+	st.tranX = _.columns === 1 ? 0 : sTouch.pageX - (_.itemWidth / 2 + _.wrapLeft);
+	st.tranY = sTouch.pageY - (_.itemHeight / 2 + _.wrapTop);
+	st.sId = sTouch.identifier;
+
+	ins.setStyle({
+		'transform': 'translate3d(' + st.tranX + 'px, ' + st.tranY + 'px, 0)'
+	});
+
+	st.itemsInstance.forEach(function (item, index) {
+		item.removeClass("tran").removeClass("cur");
+		item.addClass(index === st.cur ? "cur" : "tran");
+	})
+
+	ownerInstance.callMethod("vibrate");
+};
+
+var touchMove = function (event, ownerInstance) {
+	var ins = event.instance;
+	var st = ownerInstance.getState();
+	var _ = st.baseData;
+
+	var mTouch = event.changedTouches[0];
+	if (!mTouch) return;
+
+	if (!st.dragging) return;
+
+	// 如果不是同一个触发点则返回
+	if (st.sId !== mTouch.identifier) return;
+
+	// 计算X,Y轴位移, 单列时候X轴初始不做位移
+	var tranX = _.columns === 1 ? 0 : mTouch.pageX - (_.itemWidth / 2 + _.wrapLeft);
+	var tranY = mTouch.pageY - (_.itemHeight / 2 + _.wrapTop);
+
+	// 到顶到底自动滑动
+	if (mTouch.clientY > _.windowHeight - _.itemHeight - _.realBottomSize) {
+		// 当前触摸点pageY + item高度 - (屏幕高度 - 底部固定区域高度)
+		ownerInstance.callMethod("pageScroll", {
+			scrollTop: mTouch.pageY + _.itemHeight - (_.windowHeight - _.realBottomSize)
+		});
+	} else if (mTouch.clientY < _.itemHeight + _.realTopSize) {
+		// 当前触摸点pageY - item高度 - 顶部固定区域高度
+		ownerInstance.callMethod("pageScroll", {
+			scrollTop: mTouch.pageY - _.itemHeight - _.realTopSize
+		});
+	}
+
+	// 设置当前激活元素偏移量
+	ins.setStyle({
+		'transform': 'translate3d(' + tranX + 'px, ' + tranY + 'px, 0)'
+	})
+
+	var startKey = st.list[st.cur].sortKey;
+	var curX = Math.round(tranX / _.itemWidth);
+	var curY = Math.round(tranY / _.itemHeight);
+	var endKey = curX + _.columns * curY;
+
+	// 目标项是固定项则返回
+	var item = st.list[endKey];
+	if (item && item.fixed) return;
+
+	// X轴或Y轴超出范围则返回
+	if (isOutRange(curX, _.columns, curY, _.rows, endKey, st.list.length)) return;
+
+	// 防止拖拽过程中发生乱序问题
+	if (startKey === endKey || startKey === st.preStartKey) return;
+	st.preStartKey = startKey;
+
+	var list = sortCore(startKey, endKey, st);
+	st.itemsInstance.forEach(function (itemIns, index) {
+		var item = list[index];
+		if (index !== st.cur) {
+			itemIns.setStyle({
+				'transform': 'translate3d(' + item.tranX + ',' + item.tranY + ', 0)'
+			});
+		}
+	});
+
+	ownerInstance.callMethod("vibrate");
+	ownerInstance.callMethod("listChange", {list: list});
+	triggerCustomEvent(list, "change", ownerInstance,st.cur);
+}
+
+var touchEnd = function (event, ownerInstance) {
+	var ins = event.instance;
+	var st = ownerInstance.getState();
+
+	if (!st.dragging) return;
+	triggerCustomEvent(st.list, "sortend", ownerInstance,st.cur);
+
+	ins.addClass("tran");
+	ins.setStyle({
+		'transform': 'translate3d(' + st.list[st.cur].tranX + ',' + st.list[st.cur].tranY + ', 0)'
+	});
+
+	st.preStartKey = -1;
+	st.dragging = false;
+	ownerInstance.callMethod("drag", {dragging: false});
+	st.cur = -1;
+	st.tranX = 0;
+	st.tranY = 0;
+}
+
+var baseDataObserver = function (newVal, oldVal, ownerInstance, ins) {
+	var st = ownerInstance.getState();
+	st.baseData = newVal;
+}
+
+var listObserver = function (newVal, oldVal, ownerInstance, ins) {
+	var st = ownerInstance.getState();
+	st.itemsInstance = ownerInstance.selectAllComponents('.item');
+
+	st.list = newVal || [];
+
+	st.list.forEach(function (item, index) {
+		var itemIns = st.itemsInstance[index];
+		if (item && itemIns) {
+			itemIns.setStyle({
+				'transform': 'translate3d(' + item.tranX + ',' + item.tranY + ', 0)'
+			});
+			if (item.fixed) itemIns.addClass("fixed");
+		}
+	})
+}
+
+module.exports = {
+	longPress: longPress,
+	touchMove: touchMove,
+	touchEnd: touchEnd,
+	baseDataObserver: baseDataObserver,
+	listObserver: listObserver
+}

+ 13 - 0
wxcomponents/drag/index.wxss

@@ -0,0 +1,13 @@
+.item-wrap {
+  position: relative; }
+  .item-wrap .item {
+    position: absolute;
+    z-index: 1;
+    top: 0;
+    left: 0; }
+    .item-wrap .item.tran {
+      transition: transform 0.3s !important; }
+    .item-wrap .item.cur {
+      z-index: 2; }
+    .item-wrap .item.fixed {
+      z-index: 0 !important; }

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