浏览代码

feat:常用功能抽屉布局及拖拽实现

yangfeng 2 年之前
父节点
当前提交
d202d049ef
共有 5 个文件被更改,包括 423 次插入20 次删除
  1. 5 0
      config/proxy.js
  2. 7 7
      package.json
  3. 6 2
      src/components/push-list/PushList.vue
  4. 35 4
      src/store/workspace/common-use.js
  5. 370 7
      src/views/workspace/components/CommonUse.vue

+ 5 - 0
config/proxy.js

@@ -88,6 +88,11 @@ exports.getProxyOfDomain = function (domain) {
       target: domain,
       changeOrigin: true,
       logLevel: 'debug'
+    },
+    '^/commonFunctions': {
+      target: domain,
+      changeOrigin: true,
+      logLevel: 'debug'
     }
   }
 }

+ 7 - 7
package.json

@@ -3,13 +3,13 @@
   "version": "0.1.0",
   "private": true,
   "scripts": {
-    "serve": "vue-cli-service serve --port 8081",
-    "build": "vue-cli-service build --mode production",
-    "build:linux": "rm -rf node_module/.cache && vue-cli-service build",
-    "build:test": "vue-cli-service build --mode test",
-    "lint": "vue-cli-service lint --fix",
+    "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --port 8081",
+    "build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build --mode production",
+    "build:linux": "SET NODE_OPTIONS=--openssl-legacy-provider && rm -rf node_module/.cache && vue-cli-service build",
+    "build:test": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build --mode test",
+    "lint": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service lint --fix",
     "analyzer": "use_analyzer=true vue-cli-service build --mode production",
-    "analyze": "cross-env use_analyzer=true vue-cli-service build --mode production"
+    "analyze": "SET NODE_OPTIONS=--openssl-legacy-provider && cross-env use_analyzer=true vue-cli-service build --mode production"
   },
   "dependencies": {
     "@jianyu/easy-fix-sub-app": "^0.0.1",
@@ -55,4 +55,4 @@
     "vue-template-compiler": "^2.6.10",
     "webpack-bundle-analyzer": "^4.4.1"
   }
-}
+}

+ 6 - 2
src/components/push-list/PushList.vue

@@ -219,7 +219,7 @@ export default {
       default () {
         return {
           area: '',
-          time: ''
+          selectTime: ''
         }
       }
     },
@@ -625,7 +625,11 @@ export default {
       this.listState.loaded = false
       // 判断是否无筛选条件
       this.isAllFirst = false
-      if (query.pageNum === 1 && query.area === '' && query.time === '') {
+      /**
+      * P330开发时发现遗留缺陷-原来代码为query.time === '',字段key取错,且永远不会等于空(父组件已设置value为'all'),下边判断永远不会生效
+      * 现修改为:key取query.selectTime  value判断不等于'all' || ''
+      */
+      if (query.pageNum === 1 && query.area === '' && (query.selectTime === '' || query.selectTime === 'all')) {
         this.isAllFirst = true
       }
       const res = await getPushList(this.vt, query)

+ 35 - 4
src/store/workspace/common-use.js

@@ -16,6 +16,21 @@ function flatMenuList (list = [], index) {
   return children
 }
 
+function flatTabsMenu (list = []) {
+  let children = []
+  if (Array.isArray(list) && list.length !== 0) {
+    list.forEach(menu => {
+      if (menu.child?.length > 0) {
+        const arr = flatTabsMenu(menu.child)
+        children = children.concat(arr)
+      } else {
+        children.push(menu)
+      }
+    })
+  }
+  return children
+}
+
 export default {
   namespaced: true,
   state: () => ({
@@ -27,7 +42,8 @@ export default {
     allFunctions: [], // 所有可用功能
     commonList: [], // 常用功能(显示在外)
     transferCommonList: [], // 常用功能,去除过期的(显示在穿梭框右侧)
-    saveData: null // 要提交的数据
+    saveData: null, // 要提交的数据
+    mainFunctions: [] // 可用主要功能(商机、分析、数据、服务)不包含二级菜单
   }),
   mutations: {
     changeDialogState (state, show) {
@@ -50,6 +66,16 @@ export default {
       this.commit('workspace/commonUse/setMenuInfoList', menuInfoList)
       this.commit('workspace/commonUse/setAllFunctionsContainsUsable', menuList)
       this.commit('workspace/commonUse/setAllFunctions', list)
+      // 以下为筛选包含'商机', '分析', '数据', '服务'1级分类及以下的三级分类
+      const filterName = ['商机', '分析', '数据', '服务']
+      const filterNameList = menuList.filter(menu => filterName.includes(menu.name))
+      const filterMainList = []
+      filterNameList.forEach(item => {
+        const obj = { ...item, children: flatTabsMenu(item.child) }
+        filterMainList.push(obj)
+      })
+      console.log(filterMainList)
+      this.commit('workspace/commonUse/setMainFunList', filterMainList)
     },
     setAllFunctionsContainsUsable: function (state, list = []) {
       if (Array.isArray(list)) {
@@ -74,6 +100,11 @@ export default {
     },
     transferSave (state, data) {
       state.saveData = data
+    },
+    setMainFunList (state, list = []) {
+      if (Array.isArray(list)) {
+        state.mainFunctions = list
+      }
     }
   },
   actions: {
@@ -140,9 +171,9 @@ export default {
     },
     // dialog 卡片组件保存按钮提交事件
     async confirmSave ({ dispatch, commit, state }) {
-      if (!state.saveData) {
-        return commit('changeDialogState', false)
-      }
+      // if (!state.saveData) {
+      //   return commit('changeDialogState', false)
+      // }
       try {
         const { error_code: code, error_msg: msg } = await workspaceCommonUse('save', {
           platform: state.platform,

+ 370 - 7
src/views/workspace/components/CommonUse.vue

@@ -1,6 +1,6 @@
 <template>
   <WorkspaceCard class="work-common" title="常用功能">
-    <span slot="header-right" class="header-right-set" @click="changeDialogState(true)"><i class="icon-set-img"></i> 设置</span>
+    <span slot="header-right" class="header-right-set" @click="setCommonFun"><i class="icon-set-img"></i> 设置</span>
     <div class="common-lists">
       <div class="list-item" v-for="item in commonList" :key="item.id" @click="openLink(item)">
         <div class="icon-box-container" v-if="item.icon && item.icon.indexOf('icon-') === 0">
@@ -13,7 +13,7 @@
         </div>
         <span v-html="item.name" class="item-name"></span>
       </div>
-      <div v-if="commonList && commonList.length < maxCount" class="list-add" @click="changeDialogState(true)">
+      <div v-if="commonList && commonList.length < maxCount" class="list-add" @click="setCommonFun">
         <span class="icon-add-img"></span>
         <span class="add-text">添加常用功能</span>
       </div>
@@ -32,9 +32,54 @@
         <div class="transfer-content">
           <Transfer :maxCount="maxCount" submitKey="id" :left="allFunctions" :right="transferCommonList" @onSave="onTransferSave"></Transfer>
         </div>
-        <!-- <p class="more-tips">最多可选择 <em style="color:#2CB7CA;">{{ maxCount }}</em> 个常用功能</p> -->
       </SelectorCard>
     </el-dialog>
+    <!-- 改为抽屉式 -->
+    <DrawerCard customClass="drawer-class" title="常用功能设置" percent="600px" :with-header="false" v-model="showDrawer" @close="onCloseDrawer" @saveData="onSaveDrawer">
+      <div class="function-drawer-content" @scroll="handleScroll($event)">
+        <div class="added-function">
+          <h3 class="added-title">已添加({{addedList.length}})</h3>
+          <transition-group class="added-container" name="drag" tag="ul" v-if="addedList && addedList.length > 0">
+            <li class="added-item" draggable v-for="(item, i) in addedList" :key="item.id" @dragstart="onDragstart(i)" @dragenter="onDragenter($event, i)" @dragover="onDragover($event, i)">
+              <div class="icon-box-container">
+                <el-image :src="item.icon" :alt="item.name">
+                  <img slot="error" src="https://www.jianyu360.cn/common-module/public/image/auto.png" />
+                </el-image>
+              </div>
+              <span v-html="item.name" class="item-name"></span>
+              <span class="remove-tag" @click.stop="onAddedRemove(item)">-</span>
+            </li>
+          </transition-group>
+          <div v-else class="no-added">暂未设置常用功能</div>
+        </div>
+        <div class="classify-function" ref="refContent">
+          <div class="classify-tab" :class="{'tab-fixed': tabFixed}" ref="tabBox">
+            <span class="tab-item" :class="{'active': tabActive === index}" v-for="(item, index) in tabNames" :key="item.id" @click="goAnchor(item, index)">{{item.name}}</span>
+          </div>
+          <div class="classify-content">
+            <ul class="outer-container">
+              <li class="outer-item" v-for="level in mainFunList" :key="level.id">
+                <h3 class="outer-item-name" :id="level.name">{{ level.name }}</h3>
+                <ul class="insert-container">
+                  <li class="insert-item" v-for="next in level.children" :key="next.id">
+                    <div class="insert-item-left">
+                      <el-image :src="next.icon" :alt="next.name">
+                        <img slot="error" src="/common-module/public/image/auto.png" />
+                      </el-image>
+                      <span v-html="next.name"></span>
+                    </div>
+                    <transition name="el-zoom-in-center">
+                      <span v-if="next.status" class="handle-btn remove-btn" @click.stop="onRemoveFun(next)">移除</span>
+                      <span v-else class="handle-btn add-btn" @click.stop="onAddFun(next)">添加</span>
+                    </transition>
+                  </li>
+                </ul>
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+    </DrawerCard>
   </WorkspaceCard>
 </template>
 
@@ -47,6 +92,8 @@ import SelectorCard from '@/components/selector/SelectorCard'
 import Transfer from '@/components/work-desktop/Transfer'
 import { mixinNoPowerMessageTip } from '@/utils/mixins/no-power-message-box'
 import { tryCallHooks } from '@jianyu/easy-inject-qiankun'
+import { debounce } from '@/utils/globalFunctions'
+import DrawerCard from '@/components/drawer/Drawer.vue'
 
 export default {
   name: 'CommonUse',
@@ -57,7 +104,19 @@ export default {
     WorkspaceCard,
     SelectorCard,
     JyIcon,
-    Transfer
+    Transfer,
+    DrawerCard
+  },
+  data () {
+    return {
+      addedList: [],
+      mainFunList: [],
+      dragIndex: '',
+      enterIndex: '',
+      showDrawer: false,
+      tabActive: 0,
+      tabFixed: false
+    }
   },
   computed: {
     ...mapState({
@@ -65,8 +124,26 @@ export default {
       dialogShow: state => state.workspace.commonUse.dialogShow,
       allFunctions: state => state.workspace.commonUse.allFunctions, // 所有功能
       transferCommonList: state => state.workspace.commonUse.transferCommonList,
-      commonList: state => state.workspace.commonUse.commonList // 常用功能
-    })
+      commonList: state => state.workspace.commonUse.commonList, // 常用功能
+      mainFunctions: state => state.workspace.commonUse.mainFunctions
+    }),
+    tabNames () {
+      return this.mainFunctions.filter(item => item.level === 1)
+    }
+  },
+  watch: {
+    commonList (val) {
+      if (val) {
+        this.addedList = JSON.parse(JSON.stringify(val))
+        this.formatMainFunList(this.mainFunList, this.addedList)
+      }
+    },
+    mainFunctions (val) {
+      if (val) {
+        this.mainFunList = JSON.parse(JSON.stringify(val))
+        this.formatMainFunList(this.mainFunList, this.addedList)
+      }
+    }
   },
   async created () {
     await this.getCanUseFunctions()
@@ -159,6 +236,116 @@ export default {
       } catch (error) {
         this.$toast(error)
       }
+    },
+    onDragstart (index) {
+      this.dragIndex = index
+    },
+    onDragenter: debounce(function (e, index) {
+      e.preventDefault()
+      if (this.dragIndex !== index) {
+        const source = this.addedList[this.dragIndex]
+        this.addedList.splice(this.dragIndex, 1)
+        this.addedList.splice(index, 0, source)
+        this.dragIndex = index
+        const ids = this.addedList.map(v => {
+          return v.id
+        })
+        this.transferSave(ids)
+        console.log(ids)
+      }
+    }, 200),
+    onDragover (e, index) {
+      e.preventDefault()
+    },
+    formatMainFunList (allList = [], addedList = []) {
+      allList.forEach(v => {
+        v.children.forEach(t => {
+          addedList.forEach(s => {
+            if (s.id === t.id) {
+              t.status = true
+            }
+          })
+        })
+      })
+    },
+    onCloseDrawer () {
+      this.showDrawer = false
+    },
+    onSaveDrawer () {
+      this.confirmSaveFn()
+      this.showDrawer = false
+    },
+    setCommonFun () {
+      this.showDrawer = true
+    },
+    onRemoveFun (item) {
+      this.addedList.splice(this.addedList.findIndex(add => add.id === item.id), 1)
+      item.status = false
+      const ids = this.addedList.map(v => {
+        return v.id
+      })
+      this.transferSave(ids)
+      this.$forceUpdate()
+    },
+    onAddFun (item) {
+      this.addedList.unshift(item)
+      item.status = true
+      const ids = this.addedList.map(v => {
+        return v.id
+      })
+      this.transferSave(ids)
+      this.$forceUpdate()
+    },
+    onAddedRemove (item) {
+      this.addedList.splice(this.addedList.findIndex(add => add.id === item.id), 1)
+      this.mainFunList.forEach(v => {
+        if (v.children) {
+          v.children.forEach(s => {
+            if (item.id === s.id) {
+              s.status = false
+              this.$forceUpdate()
+            }
+          })
+        }
+      })
+    },
+    goAnchor (item, index) {
+      this.tabActive = index
+      const dom = this.$root.$el.querySelector('#' + item.name)
+      const offsetTop = this.getOffsetTop(dom)
+      const scrollDom = this.$root.$el.querySelector('.function-drawer-content')
+      console.log(offsetTop, index)
+      scrollDom.scrollTo({
+        /**
+         * 抽屉header高度70px, tabs导航栏高度48px, 滚动子元素h3标题高度60px
+         */
+        top: index === 0 ? (this.tabFixed ? offsetTop - 70 : offsetTop - 70 - 48) : (this.tabFixed ? offsetTop - 70 - 60 : offsetTop - 70 - 60 - 48),
+        behavior: 'smooth'
+      })
+    },
+    handleScroll (e) {
+      const tabOffsetTop = this.$refs.refContent.getBoundingClientRect().top
+      this.tabFixed = tabOffsetTop < 70
+      const scrollItems = this.$root.$el.querySelectorAll('.outer-item')
+      for (let i = scrollItems.length - 1; i >= 0; i--) {
+        // 判断滚动条滚动距离是否大于当前滚动项可滚动距离
+        const judge =
+          e.target.scrollTop >=
+          scrollItems[i].offsetTop - scrollItems[0].offsetTop
+        if (judge) {
+          this.tabActive = i
+          break
+        }
+      }
+    },
+    // 获取当前元素的offsetTop
+    getOffsetTop (obj) {
+      let offsetTop = 0
+      while (obj !== window.document.body && obj != null) {
+        offsetTop += obj.offsetTop
+        obj = obj.offsetParent
+      }
+      return offsetTop
     }
   }
 }
@@ -166,7 +353,9 @@ export default {
 
 <style lang="scss" scoped>
 $main: #2cb7ca;
-
+.drag-move {
+  transition: transform .3s;
+}
 ::v-deep{
   .fn-dialog{
     .el-dialog__header,.el-dialog__body{
@@ -195,6 +384,180 @@ $main: #2cb7ca;
     text-align: center;
     color: #686868;
   }
+  .drawer-class{
+    background: #F2F2F4;
+    .el-header{
+      padding: 26px 30px;
+      font-size: 18px;
+      background: #fff;
+      box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.05) inset;
+    }
+    .el-main{
+      overflow-y: hidden!important;
+    }
+    .function-drawer-content{
+      height: 100%;
+      overflow-y: scroll;
+    }
+    .added-function{
+      padding: 16px 30px 24px;
+      background: #fff;
+      .added-title{
+        line-height: 24px;
+        font-size: 16px;
+      }
+      .no-added{
+        padding: 40px;
+        text-align: center;
+        font-size: 16px;
+        color: #686868;
+      }
+      .added-container{
+        display: flex;
+        flex-wrap: wrap;
+      }
+      .added-item{
+        position: relative;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        width: 104px;
+        min-height: 70px;
+        margin: 16px 0 0 0;
+        text-align: center;
+        flex-shrink: 0;
+        border-radius: 8px;
+        cursor: move;
+        .item-name{
+          margin-top: 8px;
+          line-height: 22px;
+          font-size: 14px;
+          color: #1D1D1D;
+        }
+        .remove-tag{
+          position: absolute;
+          top: 0;
+          right: 8px;
+          display: inline-block;
+          width: 14px;
+          height: 14px;
+          line-height: 12px;
+          border-radius: 50%;
+          background: #FF3A20;
+          color: #fff;
+          font-weight: bold;
+          cursor: pointer;
+        }
+      }
+    }
+    .classify-function{
+      margin-top: 12px;
+      .classify-tab{
+        padding: 0 12px;
+        height: 48px;
+        line-height: 48px;
+        background: #fff;
+        border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+      }
+      .tab-fixed{
+        position: fixed;
+        top: 70px;
+        width: 100%;
+        height: 48px;
+        background: #fff;
+        z-index: 10;
+      }
+      .tab-item{
+        display: inline-block;
+        height: 100%;
+        padding: 0 20px;
+        cursor: pointer;
+      }
+      .active{
+        position: relative;
+        color: $main;
+        &::after{
+          position: absolute;
+          content: '';
+          width: 32px;
+          height: 2px;
+          bottom: 0;
+          left: 50%;
+          margin-left: -16px;
+          background: $main;
+        }
+      }
+      .classify-content{
+        .outer-container,
+        .insert-container{
+          display: flex;
+          flex-direction: column;
+        }
+        .outer-item{
+          padding-left: 30px;
+          margin-bottom: 8px;
+          background: #fff;
+          &:last-child{
+            min-height: 100%;
+          }
+        }
+        .outer-item-name{
+          padding: 24px 0 8px;
+          font-size: 18px;
+          line-height: 28px;
+          font-weight: 700;
+          color: #1D1D1D;
+        }
+        .insert-item{
+          position: relative;
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          padding: 18px 30px 18px 0;
+          &::after{
+            position: absolute;
+            content: '';
+            right: 0;
+            left: 60px;
+            bottom: 0;
+            border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+          }
+          .el-image{
+            width: 44px;
+            height: 44px;
+            flex-shrink: 0;
+          }
+        }
+        .insert-item-left{
+          display: flex;
+          align-items: center;
+          span{
+            margin-left: 16px;
+            font-size: 16px;
+            line-height: 24px;
+          }
+        }
+        .handle-btn{
+          width: 72px;
+          height: 30px;
+          line-height: 28px;
+          text-align: center;
+          background: transparent;
+          cursor: pointer;
+        }
+        .add-btn{
+          border: 1px solid #E0E0E0;
+          color: #1D1D1D;
+          border-radius: 4px;
+        }
+        .remove-btn{
+          border: 0;
+          font-size: 14px;
+          color: #FF3A20;
+        }
+      }
+    }
+  }
 }
 .icon-box-container {
   display: flex;