Răsfoiți Sursa

Merge branch 'main' into feature/v1.0.89

yuelujie 6 luni în urmă
părinte
comite
d97d2a44cd
39 a modificat fișierele cu 1706 adăugiri și 165 ștergeri
  1. 39 19
      apps/bigmember_pc/src/api/modules/forecast.js
  2. BIN
      apps/bigmember_pc/src/assets/images/icon/checkbox_active.png
  3. BIN
      apps/bigmember_pc/src/assets/images/icon/checkbox_checked.png
  4. BIN
      apps/bigmember_pc/src/assets/images/icon/checkbox_checked_disabled.png
  5. BIN
      apps/bigmember_pc/src/assets/images/icon/checkbox_default.png
  6. BIN
      apps/bigmember_pc/src/assets/images/icon/checkbox_default_disabled.png
  7. BIN
      apps/bigmember_pc/src/assets/images/icon/checkbox_half_checked.png
  8. BIN
      apps/bigmember_pc/src/assets/images/icon/checkbox_half_disabled.png
  9. BIN
      apps/bigmember_pc/src/assets/images/icon/radio_active.png
  10. BIN
      apps/bigmember_pc/src/assets/images/icon/radio_checked.png
  11. BIN
      apps/bigmember_pc/src/assets/images/icon/radio_checked2.png
  12. BIN
      apps/bigmember_pc/src/assets/images/icon/radio_checked_disabled.png
  13. BIN
      apps/bigmember_pc/src/assets/images/icon/radio_checked_disabled2.png
  14. BIN
      apps/bigmember_pc/src/assets/images/icon/radio_default.png
  15. BIN
      apps/bigmember_pc/src/assets/images/icon/radio_default_disabled.png
  16. 11 3
      apps/bigmember_pc/src/components/collect-info/CollectInfo.vue
  17. 32 0
      apps/bigmember_pc/src/components/common/MonitorPopover.vue
  18. 16 1
      apps/bigmember_pc/src/components/dialog/Dialog.vue
  19. 100 4
      apps/bigmember_pc/src/components/forecast/ForFilter.vue
  20. 114 39
      apps/bigmember_pc/src/components/forecast/ForeCast.vue
  21. 449 0
      apps/bigmember_pc/src/composables/quick-monitor/component/MonitorGroup.vue
  22. 80 5
      apps/bigmember_pc/src/composables/quick-monitor/component/QuickMonitor.vue
  23. 8 6
      apps/bigmember_pc/src/composables/quick-monitor/use/base.js
  24. 143 32
      apps/bigmember_pc/src/composables/quick-monitor/use/ent.js
  25. 7 7
      apps/bigmember_pc/src/views/article-content/composables/useArticleUtil.js
  26. 70 5
      apps/bigmember_pc/src/views/ent-intel/EntIntel.vue
  27. 71 9
      apps/bigmember_pc/src/views/portrayal/components/EntFollowStar.vue
  28. 12 2
      apps/mobile/src/api/modules/bigmember.js
  29. 11 0
      apps/mobile/src/api/modules/entgroup.js
  30. 1 0
      apps/mobile/src/api/modules/index.js
  31. 21 0
      apps/mobile/src/router/modules/entgroup.js
  32. 5 3
      apps/mobile/src/store/index.js
  33. 28 0
      apps/mobile/src/store/modules/group.js
  34. 163 0
      apps/mobile/src/views/entgroup/detail.vue
  35. 219 0
      apps/mobile/src/views/entgroup/index.vue
  36. 2 2
      apps/mobile/vite.config.js
  37. 12 1
      data/data-models/modules/quick-monitor/api/follow-ent.js
  38. 31 3
      data/data-models/modules/quick-monitor/plugins/base.js
  39. 61 24
      data/data-models/modules/quick-monitor/plugins/ent-api.js

+ 39 - 19
apps/bigmember_pc/src/api/modules/forecast.js

@@ -1,5 +1,5 @@
-import request from '@/api'
 import qs from 'qs'
+import request from '@/api'
 
 // 潜在项目预测list接口
 export function getPotenList(data) {
@@ -7,7 +7,7 @@ export function getPotenList(data) {
   return request({
     url: '/bigmember/forecast/forPList',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -17,7 +17,7 @@ export function getProjectList(data) {
   return request({
     url: '/bigmember/analysis/projectName',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -27,7 +27,7 @@ export function getResultDetail(data) {
   return request({
     url: '/bigmember/forecast/forWResult',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -37,7 +37,7 @@ export function getEntFollowList(data) {
   return request({
     url: '/bigmember/follow/ent/list',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -47,7 +47,7 @@ export function getBidAssociation(data) {
   return request({
     url: '/bigmember/search/ent/association',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -67,7 +67,7 @@ export function getBidProjectInfo(data) {
   return request({
     url: '/bigmember/analysis/projectInfo',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -77,7 +77,7 @@ export function getForWData(data) {
   return request({
     url: '/bigmember/forecast/forWData',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -87,7 +87,7 @@ export function getForWStatus(data) {
   return request({
     url: '/bigmember/forecast/forWStatus',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -97,7 +97,7 @@ export function getForWResult(data) {
   return request({
     url: '/bigmember/forecast/forWResult',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -107,7 +107,7 @@ export function getPotenDetail(data) {
   return request({
     url: '/bigmember/forecast/forPContent',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -117,7 +117,7 @@ export function getfollowCheck(data) {
   return request({
     url: '/bigmember/follow/ent/followCheck',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -127,7 +127,7 @@ export function getBidAiused(data) {
   return request({
     url: '/bigmember/use/aiused_history',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -137,7 +137,7 @@ export function getBdInfoStatus(data) {
   return request({
     url: '/bigmember/forecast/bdInfoStatus',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -147,7 +147,7 @@ export function getSearchByNames(data) {
   return request({
     url: '/bigmember/entinfo/bynames',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -157,7 +157,7 @@ export function getNewMsgSelects(data) {
   return request({
     url: '/bigmember/portrait/winner/getNewMsgSelects',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -167,7 +167,7 @@ export function getSvipMsgSelects(data) {
   return request({
     url: '/bigmember/portrait/subVipPortrait/getNewMsgSelects',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -177,7 +177,7 @@ export function changeEntGroup(data) {
   return request({
     url: '/bigmember/follow/ent/changeGroup',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -195,6 +195,26 @@ export function forecastDataExport(data) {
   return request({
     url: '/bigmember/forecast/forDerive',
     method: 'post',
-    data: data
+    data
+  })
+}
+
+// 企业监控分组(仅有获取),不含达到分组上限
+export function ajaxFollowEntGroup(data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/bigmember/follow/ent/labelGroupList',
+    method: 'post',
+    data
+  })
+}
+
+// 企业监控分组(获取、新增、编辑、删除),(获取)包含达到监控上限相关字段
+export function ajaxFollowUpdateGroup(data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/bigmember/follow/ent/labelGroup',
+    method: 'post',
+    data
   })
 }

BIN
apps/bigmember_pc/src/assets/images/icon/checkbox_active.png


BIN
apps/bigmember_pc/src/assets/images/icon/checkbox_checked.png


BIN
apps/bigmember_pc/src/assets/images/icon/checkbox_checked_disabled.png


BIN
apps/bigmember_pc/src/assets/images/icon/checkbox_default.png


BIN
apps/bigmember_pc/src/assets/images/icon/checkbox_default_disabled.png


BIN
apps/bigmember_pc/src/assets/images/icon/checkbox_half_checked.png


BIN
apps/bigmember_pc/src/assets/images/icon/checkbox_half_disabled.png


BIN
apps/bigmember_pc/src/assets/images/icon/radio_active.png


BIN
apps/bigmember_pc/src/assets/images/icon/radio_checked.png


BIN
apps/bigmember_pc/src/assets/images/icon/radio_checked2.png


BIN
apps/bigmember_pc/src/assets/images/icon/radio_checked_disabled.png


BIN
apps/bigmember_pc/src/assets/images/icon/radio_checked_disabled2.png


BIN
apps/bigmember_pc/src/assets/images/icon/radio_default.png


BIN
apps/bigmember_pc/src/assets/images/icon/radio_default_disabled.png


+ 11 - 3
apps/bigmember_pc/src/components/collect-info/CollectInfo.vue

@@ -613,7 +613,11 @@ export default {
         pc_dzbgxzb_customizedquantity:
           '请留下联系方式,我们会安排客户经理与您对接,下载更多市场分析定制报告!',
         pc_sunlightlist_viewdetails: '每天前10名开通权限的用户可免费开通',
-        pc_sunlight_viewdetails: '每天前10名开通权限的用户可免费开通'
+        pc_sunlight_viewdetails: '每天前10名开通权限的用户可免费开通',
+        pc_ent_more:
+          '请升级大会员,可实时监控最多500个企业中标动态,帮助你了解竞争对手和合作伙伴的动向。',
+        pc_ent_limit:
+          '您当前权限不足,请升级大会员,可实时监控最多500个企业中标动态,帮助你了解竞争对手和合作伙伴的动向。'
       },
       sourceTitleTopMap: {
         pc_Interfacedirectory_apply: '立即申请,获取剑鱼数据接口试用体验!',
@@ -642,7 +646,9 @@ export default {
         pc_dzbg_fullreport: '体验市场分析定制报告',
         pc_dzbgxzb_customizedquantity: '市场分析定制报告下载',
         pc_sunlightlist_viewdetails: '采购人联系方式等关键信息无权限查看',
-        pc_sunlight_viewdetails: '采购人联系方式等关键信息无权限查看'
+        pc_sunlight_viewdetails: '采购人联系方式等关键信息无权限查看',
+        pc_ent_more: '申请监控更多企业',
+        pc_ent_limit: '申请监控更多企业'
       },
       sourceDescMap: {
         pc_Interfacedirectory_apply: '接口目录页-申请接口-pc',
@@ -668,7 +674,9 @@ export default {
         pc_dzbg_fullreport: '申请免费体验-完整查看市场分析定制报告',
         pc_sunlightlist_viewdetails: '阳光直采采购信息列表-查看采购信息详情',
         pc_sunlight_viewdetails: '阳光直采-查看采购信息详情',
-        pc_dzbgxzb_customizedquantity: '市场分析定制报告下载包-定制报告份数'
+        pc_dzbgxzb_customizedquantity: '市场分析定制报告下载包-定制报告份数',
+        pc_ent_more: '企业画像页-申请监控更多企业',
+        pc_ent_limit: '企业画像页-申请监控更多企业(已达上限)'
       },
       isRefresh: false
     }

+ 32 - 0
apps/bigmember_pc/src/components/common/MonitorPopover.vue

@@ -10,6 +10,27 @@
       >
         {{ textConfig.more }}
       </li>
+      <!-- <li
+        class="monitor-more-actions"
+        :class="{ 'b-style-none': !followedGroup }"
+        v-if="followedGroup"
+      >
+        <div style="color: #686868;">{{ textConfig.group }}</div>
+        <div
+          class="flex flex-(items-center justify-between)"
+          >
+          <span class="list-center ellipsis list-group-name">
+            {{ followedGroup }}
+          </span>
+          <span
+            class="flex-shrink-0 font-size-14px list-group-action"
+            @click="$emit('click', 'group')"
+          >
+            更改
+            <i class="el-icon-arrow-right"></i>
+          </span>
+        </div>
+      </li> -->
       <li v-if="showList">
         <div class="list-top" @click="$emit('click', 'list')">
           <span>{{ textConfig.list }}</span>
@@ -76,6 +97,10 @@ export default {
     textType: {
       type: String,
       default: 'unit'
+    },
+    followedGroup: {
+      type: String,
+      default: ''
     }
   },
   computed: {
@@ -99,6 +124,7 @@ export default {
         },
         ent: {
           tip: '关注企业一旦中标,会推送企业的中标项目、时间等公告信息。',
+          group: '当前分组',
           more: '查看监控动态',
           list: '查看监控列表',
           listNumTip: '已监控',
@@ -158,6 +184,12 @@ export default {
         background-color: #eaf8fa;
         border-radius: 4px;
       }
+      .list-group-name {
+        margin-right: 8px;
+      }
+      .list-group-action {
+        color: #2abed1;
+      }
     }
   }
 }

+ 16 - 1
apps/bigmember_pc/src/components/dialog/Dialog.vue

@@ -5,6 +5,9 @@
     v-bind="$props"
     :show-close="showClose"
     :visible="visible"
+    :close-on-click-modal="closeClickModal"
+    :close-on-press-escape="closeOnPressEscape"
+    :destroy-on-close="destroyOnClose"
     :before-close="beforeClose"
     @update:visible="update"
     v-component-change-mount="{ selector: comMount }"
@@ -75,7 +78,19 @@ export default {
       type: String,
       default: ''
     },
-    disabled: Boolean
+    disabled: Boolean,
+    closeClickModal: {
+      type: Boolean,
+      default: false
+    },
+    closeOnPressEscape: {
+      type: Boolean,
+      default: false
+    },
+    destroyOnClose: {
+      type: Boolean,
+      default: false
+    }
   },
   methods: {
     update(e) {

+ 100 - 4
apps/bigmember_pc/src/components/forecast/ForFilter.vue

@@ -1,10 +1,21 @@
 <template>
   <div class="ent-filter">
     <div class="filter-title">筛选条件</div>
-    <div class="filter-item">
+    <div class="filter-item items-baseline">
       <span class="item-label">分组:</span>
-      <div>
+      <!-- <div>
         <GroupTag selectorType="line" @onChange="getGroupTag"></GroupTag>
+      </div> -->
+      <div class="selected-list">
+        <el-tag
+          class="tag-item"
+          :class="tag.checked ? 'active' : ''"
+          type="plain"
+          v-for="tag in formatGroupList"
+          :key="tag.name"
+          @click="changeTag(tag)"
+          >{{ tag.name }}</el-tag
+        >
       </div>
     </div>
     <div class="filter-item">
@@ -28,19 +39,26 @@
 </template>
 
 <script>
-import { Input, Button } from 'element-ui'
+import { Input, Button, Tag } from 'element-ui'
 import GroupTag from '@/components/selector/GroupSelectorContent.vue'
 export default {
   name: 'ent-filter',
-  props: {},
+  props: {
+    groupList: {
+      type: Array,
+      default: () => []
+    }
+  },
   computed: {},
   components: {
     [Input.name]: Input,
     [Button.name]: Button,
+    [Tag.name]: Tag,
     GroupTag
   },
   data() {
     return {
+      formatGroupList: [],
       entVal: '',
       curGroup: ''
     }
@@ -48,7 +66,24 @@ export default {
   destroyed() {
     this.$store.commit('forcast/clearEntFilter')
   },
+  watch: {
+    groupList(newVal) {
+      this.initGroupList()
+    }
+  },
+  mounted() {
+    this.initGroupList()
+  },
   methods: {
+    initGroupList() {
+      const selectedGroup = this.curGroup.split(',')
+      this.formatGroupList = this.groupList.map((item, index) => {
+        return {
+          ...item,
+          checked: selectedGroup.includes(item.id)
+        }
+      })
+    },
     getGroupTag(data) {
       this.curGroup = data
       this.$emit('onPageChange', 0, this.entVal, data)
@@ -81,6 +116,48 @@ export default {
       if (!val) {
         this.clearHandle()
       }
+    },
+    changeTag(item) {
+      const data = this.formatGroupList
+      const list = data.slice(1, data.length)
+      if (item.id === '') {
+        list.forEach((v) => {
+          v.checked = false
+        })
+        item.checked = true
+      } else {
+        item.checked = !item.checked
+        const someChecked = list.some((s) => {
+          return s.checked
+        })
+        const everyChecked = list.every((s) => {
+          return s.checked
+        })
+        if (someChecked) {
+          data[0].checked = false
+        } else {
+          data[0].checked = true
+        }
+        if (everyChecked) {
+          list.forEach((v) => {
+            v.checked = false
+          })
+          data[0].checked = true
+        }
+      }
+      const selected = this.getSelectedTag()
+      // this.$emit('onChange', selected)
+      this.getGroupTag(selected)
+    },
+    getSelectedTag() {
+      const data = []
+      const lists = this.formatGroupList
+      lists.forEach((v) => {
+        if (v.checked) {
+          data.push(v.id)
+        }
+      })
+      return data.toString()
     }
   }
 }
@@ -102,6 +179,10 @@ export default {
     display: flex;
     align-items: center;
     padding: 12px 0;
+    &.items-baseline {
+      padding-bottom: 6px;
+      align-items: baseline;
+    }
     .item-label {
       margin-right: 12px;
       font-size: 14px;
@@ -132,5 +213,20 @@ export default {
       border: 0;
     }
   }
+  .tag-item {
+    margin-bottom: 6px;
+    height: 26px;
+    line-height: 26px;
+    margin-right: 12px;
+    background: #fff;
+    border: 0;
+    color: #1d1d1d;
+    font-size: 14px;
+    cursor: pointer;
+    &.active {
+      color: #fff;
+      background-color: $color-text--highlight;
+    }
+  }
 }
 </style>

+ 114 - 39
apps/bigmember_pc/src/components/forecast/ForeCast.vue

@@ -226,7 +226,7 @@
           </div>
           <div class="list_li_item item-flex-column">
             <span
-              v-for="(s, j) in formatGroup(item.s_group)"
+              v-for="(s, j) in formatGroup(item.s_grousp)"
               :key="'00' + j"
               style="line-height: 20px"
               >{{ s }}</span
@@ -491,24 +491,50 @@
       </el-pagination>
     </div>
     <!-- 分组dialog -->
-    <el-dialog
-      custom-class="sub-dialog"
+    <common-dialog
+      custom-class="monitor-class"
       :visible.sync="dialog.group"
       :close-on-click-modal="false"
+      :close-on-press-escape="false"
       :show-close="false"
       :destroy-on-close="true"
-      :lock-scroll="false"
       center
-      width="460px"
+      title="编辑分组"
+      width="464px"
     >
-      <GroupCard
+      <!-- <GroupCard
         :initGroupInfo="this.cur.group"
         @onCancel="dialog.group = false"
         @onConfirm="saveGroupData"
       >
         <div slot="header">编辑分组</div>
-      </GroupCard>
-    </el-dialog>
+      </GroupCard> -->
+      <MonitorGroup
+        :key="cur.group"
+        :list="groupList"
+        :echo="cur.group"
+        @emitDisabled="onGroupDisabled"
+        @onChange="onChangeGroup"
+        @add="onAddGroup"
+        @edit="onEditGroup"
+      ></MonitorGroup>
+      <template #footer>
+        <button
+          class="action-button confirm"
+          :disabled="groupActionDisabled"
+          @click="confirmChangeGroup"
+        >
+          确定
+        </button>
+        <button
+          class="action-button cancel"
+          :disabled="groupActionDisabled"
+          @click="(dialog.group = false), (cur.group = '')"
+        >
+          取消
+        </button>
+      </template>
+    </common-dialog>
   </div>
 </template>
 
@@ -540,6 +566,9 @@ import {
 } from '@/api/modules'
 import MoneySelection from '@/components/selector-cascader/MoneySelection.vue'
 import AreaCityCountryCascader from '@/components/selector-cascader/AreaCityCountryCascader.vue'
+import MonitorGroup from '@/composables/quick-monitor/component/MonitorGroup.vue'
+import commonDialog from '@/components/dialog/Dialog.vue'
+
 export default {
   props: [
     'type',
@@ -554,7 +583,8 @@ export default {
     'entSearch',
     'entSearchRes',
     'showFiltrate',
-    'tab_list'
+    'tab_list',
+    'groupList'
   ],
   name: 'listData',
   mixins: [mixinVisited],
@@ -572,7 +602,9 @@ export default {
     Tips,
     TimeSelector,
     tabs,
-    AreaCityCountryCascader
+    AreaCityCountryCascader,
+    MonitorGroup,
+    commonDialog
   },
   data() {
     return {
@@ -596,7 +628,8 @@ export default {
       },
       cur: {
         group: '', // 当前点击编辑的分组信息
-        fid: ''
+        fid: '',
+        entId: ''
       },
       filterPrice: {
         min: '',
@@ -606,7 +639,9 @@ export default {
       regionMapData: '',
       sourceAreaMap: {},
       allChecked: false,
-      selectedId: []
+      selectedId: [],
+      groupActionDisabled: false,
+      checkedGroupId: ''
     }
   },
   created() {
@@ -989,32 +1024,25 @@ export default {
     // 处理显示分组
     formatGroup(data) {
       if (!data) return
-      let val
-      switch (data) {
-        case 'A':
-          val = '默认分组'
-          break
-        case 'B':
-          val = '竞争对手'
-          break
-        case 'C':
-          val = '合作伙伴'
-          break
-        case 'B,C' || 'C,B':
-          val = '竞争对手,合作伙伴'
-          break
-        default:
-          val = '默认分组'
-          break
-      }
-      const arr = val.split(',')
-      return arr
+      const groupList = this.groupList
+      const groupIdList = data.split(',')
+      const val = []
+      groupList.forEach((item) => {
+        groupIdList.forEach((v) => {
+          if (v === item.id) {
+            val.push(item.name)
+          }
+        })
+      })
+      return val
     },
     // 企业情报监控 编辑分组
     editGroup(item) {
       this.dialog.group = true
-      this.cur.group = item.s_group ? item.s_group : 'A'
+      this.cur.group = item.s_grousp
       this.cur.fid = item.fid
+      this.cur.entId = item.s_entId
+      this.checkedGroupId = item.s_grousp
     },
     // 企业情报监控 取消关注
     cancelFollow(item) {
@@ -1040,17 +1068,15 @@ export default {
     },
     saveGroupData(data) {
       const params = {
-        fid: this.cur.fid,
-        group: data
-      }
-      const defaultParams = {
-        fid: this.cur.fid
+        entId: this.cur.entId,
+        groups: data ? data : undefined
       }
       const filter = this.filter
       const num = this.listState.list.length <= 1 ? 0 : this.listState.pageNum
       // console.log(num, this.listState.list, '1111')
-      changeEntGroup(data === 'A' ? defaultParams : params).then((res) => {
+      changeEntGroup(params).then((res) => {
         if (res.data === 'success') {
+          this.$toast('修改分组成功')
           this.dialog.group = false
           this.$emit(
             'onPageChange',
@@ -1199,6 +1225,33 @@ export default {
       this.filterPrice = { min: '', max: '' }
       this.$emit('changePrice', this.filterPrice)
       this.$emit('onChange', { start: 0, end: 0 })
+    },
+    // 编辑分组弹框确定/取消按钮是否禁用
+    onGroupDisabled(val) {
+      this.groupActionDisabled = val
+      this.$forceUpdate()
+    },
+    // 新增分组
+    onAddGroup(data) {
+      this.$emit('updateGroup', {
+        type: 'add',
+        name: data?.name
+      })
+    },
+    // 编辑分组
+    onEditGroup(data) {
+      this.$emit('updateGroup', {
+        type: 'put',
+        groupId: data?.groupId,
+        name: data?.name
+      })
+    },
+    // 选择分组组件change回调事件
+    onChangeGroup(id) {
+      this.checkedGroupId = id
+    },
+    confirmChangeGroup() {
+      this.saveGroupData(this.checkedGroupId)
     }
   }
 }
@@ -1773,6 +1826,28 @@ export default {
         height: 8px;
       }
     }
+    .monitor-class {
+      padding: 32px;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      margin: 0px !important;
+      border-radius: 8px;
+      .el-dialog__header {
+        padding: 0;
+      }
+      .el-dialog__body {
+        padding: 20px 0 32px;
+        text-align: center;
+      }
+      .el-dialog__footer {
+        padding: 0;
+      }
+    }
+    .action-button.cancel[disabled] {
+      background-color: #e3e4e5;
+      color: #999999;
+    }
   }
   .empty-container {
     padding-bottom: 60px;

+ 449 - 0
apps/bigmember_pc/src/composables/quick-monitor/component/MonitorGroup.vue

@@ -0,0 +1,449 @@
+<script setup>
+import { ref, onMounted, watch, getCurrentInstance } from 'vue'
+const that = getCurrentInstance().proxy
+
+const props = defineProps({
+  // 要回显的分组id
+  echo: {
+    type: String,
+    default: ''
+  },
+  // 分组列表
+  list: {
+    type: Array,
+    default: () => []
+  }
+})
+
+// 属性
+const groupList = ref([])
+const newGroupName = ref('')
+const showAdd = ref(false)
+const isDisabled = ref(false)
+const currentSelect = ref([])
+
+// 冒泡事件名
+const emits = defineEmits(['emitDisabled', 'onChange', 'onAdd', 'onEdit'])
+
+onMounted(() => {
+  initData()
+})
+
+// 监听isDisabled属性
+watch(
+  () => isDisabled.value,
+  (newVal) => {
+    emits('emitDisabled', newVal)
+  }
+)
+// 监听list属性
+watch(
+  () => props.list,
+  (newVal) => {
+    initData()
+  }
+)
+
+function initData() {
+  if (!props.list || !props.list.length) return
+  const formatList = props.list.map((v) => {
+    return {
+      id: v.id,
+      name: v.s_name || v.name,
+      input: '',
+      showEdit: false,
+      canEdit: v.isPut,
+      checked: currentSelect.value.length
+        ? currentSelect.value.includes(v.id)
+        : v.isSelect
+    }
+  })
+  groupList.value = formatList
+  if (props.echo) {
+    setGroupSelected(props.echo)
+  }
+}
+
+// 新增分组改变交互方法
+function onAddAction() {
+  showAdd.value = true
+  newGroupName.value = ''
+  isDisabled.value = true
+}
+// 新增分组确认方法
+function onAddConfirm() {
+  if (!newGroupName.value) {
+    return that.$toast('请输入分组名称')
+  }
+  if (groupList.value.some((v) => v.name === newGroupName.value)) {
+    return that.$toast('分组名称已存在')
+  }
+  const newItem = {
+    // id: Math.random(),
+    name: newGroupName.value,
+    showEdit: false,
+    checked: false,
+    input: ''
+  }
+  groupList.value.splice(1, 0, newItem)
+  showAdd.value = false
+  isDisabled.value = false
+  emits('add', { name: newGroupName.value })
+}
+// 新增分组取消方法
+function onAddCancel() {
+  newGroupName.value = ''
+  showAdd.value = false
+  isDisabled.value = false
+}
+// 单项编辑分组
+function onEditGroup(item) {
+  item.input = item.name
+  item.showEdit = true
+  isDisabled.value = true
+}
+// 单项编辑输入方法
+function onItemInput(event, item) {
+  // item.input = event
+}
+// 单项编辑确认方法
+function onItemConfirm(item) {
+  const list = groupList.value
+  if (!item.input) {
+    return that.$toast('请输入分组名称')
+  }
+  if (list.some((v) => v.name === item.input)) {
+    return that.$toast('分组名称已存在')
+  }
+  item.name = item.input
+  item.showEdit = false
+  isDisabled.value = false
+  emits('edit', { groupId: item.id, name: item.name })
+}
+// 单项编辑取消方法
+function onItemCancel(item) {
+  item.showEdit = false
+  isDisabled.value = false
+}
+// 分组名称选中事件
+function changeGroupState(item) {
+  const list = groupList.value
+  if (item.name === '默认分组') {
+    list.forEach((v) => {
+      v.checked = false
+    })
+    item.checked = true
+  } else {
+    item.checked = !item.checked
+    const allChecked = list.some((s) => {
+      return s.checked
+    })
+    if (allChecked) {
+      list[0].checked = false
+    } else {
+      list[0].checked = true
+    }
+  }
+  const selected = getSelected()
+  emits('onChange', selected)
+}
+// 获取选中的分组
+function getSelected() {
+  const data = []
+  const lists = groupList.value
+  lists.forEach((v) => {
+    if (v.checked) {
+      data.push(v.id)
+    }
+  })
+  currentSelect.value = data
+  return data.toString()
+}
+// 设置选中的分组
+function setGroupSelected(data) {
+  if (!data) return
+  const list = groupList.value
+  const newData = data.split(',')
+  list.forEach((v) => {
+    v.checked = false
+    newData.forEach((s) => {
+      if (s === v.id) {
+        v.checked = true
+      }
+    })
+  })
+}
+
+defineExpose({
+  getSelected
+})
+</script>
+<template>
+  <div class="monitor-group" :class="{ disabled: isDisabled }">
+    <div class="flex flex-(items-center justify-end) group-add-container">
+      <div
+        class="flex flex-(items-center justify-between) add-container"
+        v-if="showAdd"
+      >
+        <el-input
+          v-model="newGroupName"
+          placeholder="请输入分组名称"
+          class="add-input"
+          maxlength="15"
+          autofocus
+          @keyup.enter.native="onAddConfirm"
+        >
+          <template #suffix>
+            <span class="el-input__count">
+              <span class="el-input__count-inner">
+                <em :class="{ 'already-count': newGroupName.length > 0 }">{{
+                  newGroupName.length
+                }}</em
+                >/15
+              </span>
+            </span>
+          </template>
+        </el-input>
+        <div class="flex flex-items-center add-action">
+          <span class="add-confirm" @click="onAddConfirm">确定</span>
+          <span class="add-cancel" @click="onAddCancel">取消</span>
+        </div>
+      </div>
+      <el-button
+        v-else
+        type="text"
+        icon="el-icon-plus"
+        @click="onAddAction"
+        class="add-btn"
+      >
+        新增分组
+      </el-button>
+    </div>
+    <div class="group-list-container">
+      <div v-for="(item, key) in groupList" :key="key" class="group-item">
+        <div
+          v-if="!item.showEdit"
+          class="flex flex-(items-center justify-between) item-name-container"
+          :class="{ 'item-name-checked': item.checked }"
+          @click="changeGroupState(item)"
+        >
+          <div class="flex flex-items-center flex-1">
+            <div
+              v-if="item.name === '默认分组'"
+              class="j-radio"
+              :class="item.checked ? 'r-checked' : ''"
+            ></div>
+            <div
+              v-else
+              class="j-checkbox"
+              :class="item.checked ? 'checked' : ''"
+            ></div>
+            <span class="item-name" :data-id="item.id">{{ item.name }}</span>
+          </div>
+          <span
+            class="item-edit"
+            v-if="item.canEdit"
+            @click.stop="onEditGroup(item)"
+            >编辑</span
+          >
+        </div>
+        <div
+          v-else
+          class="flex flex-(item-center space-between) item-edit-container"
+        >
+          <el-input
+            v-model="item.input"
+            placeholder="请输入分组名称"
+            class="add-input input-bg"
+            maxlength="15"
+            autofocus
+            @input="onItemInput($event, item)"
+            @keyup.enter.native="onItemConfirm(item)"
+          >
+            <template #suffix>
+              <span class="el-input__count">
+                <span class="el-input__count-inner">
+                  <em :class="{ 'already-count': item.input.length > 0 }">{{
+                    item.input.length
+                  }}</em
+                  >/15
+                </span>
+              </span>
+            </template>
+          </el-input>
+          <div class="flex items-center add-action">
+            <span class="add-confirm" @click="onItemConfirm(item)">确定</span>
+            <span class="add-cancel" @click="onItemCancel(item)">取消</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<style lang="scss" scoped>
+.monitor-group {
+  .j-radio {
+    width: 20px;
+    height: 20px;
+    margin-right: 4px;
+    background: url('~@/assets/images/icon/radio_default.png') no-repeat center
+      center;
+    cursor: pointer;
+    background-size: 20px;
+    &.r-checked {
+      background: url('~@/assets/images/icon/radio_checked2.png') no-repeat
+        center center;
+      background-size: 20px;
+    }
+  }
+  .j-checkbox {
+    width: 20px;
+    height: 20px;
+    margin-right: 4px;
+    border: 0;
+    background: url('~@/assets/images/icon/checkbox_default.png') no-repeat
+      center center;
+    cursor: pointer;
+    background-size: 20px;
+    &.checked {
+      border: 0;
+      background: url('~@/assets/images/icon/checkbox_checked.png') no-repeat
+        center center;
+      background-size: 20px;
+      &[disabled] {
+        background: url('~@/assets/images/icon/checkbox_checked_disabled.png')
+          no-repeat;
+        background-size: 20px;
+      }
+    }
+  }
+  .group-add-container {
+    .add-container {
+      width: 100%;
+    }
+  }
+  .group-list-container {
+    margin-top: 20px;
+    max-height: 380px;
+    overflow-y: auto;
+    border: 1px solid rgba(236, 236, 236, 1);
+    border-radius: 4px;
+    &::-webkit-scrollbar {
+      width: 3px;
+    }
+    .item-name {
+      margin-left: 4px;
+      line-height: 22px;
+    }
+    .item-edit {
+      display: none;
+      color: $color_main;
+      cursor: pointer;
+      line-height: 22px;
+    }
+    .group-item {
+      cursor: pointer;
+      &:hover {
+        background: #eaf8fa;
+        .item-edit {
+          display: block;
+        }
+      }
+      &:not(:last-child) {
+        border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+      }
+    }
+    .item-name-container {
+      padding: 8px 12px 8px 16px;
+      &.item-name-checked {
+        background: #eaf8fa;
+        color: $color_main;
+      }
+    }
+    .item-edit-container {
+      padding: 4px 12px 4px 16px;
+    }
+  }
+  .add-confirm {
+    margin-left: 12px;
+    color: $color_main;
+    font-size: 14px;
+    line-height: 22px;
+    cursor: pointer;
+  }
+  .add-cancel {
+    margin-left: 12px;
+    color: #1d1d1d;
+    font-size: 14px;
+    line-height: 22px;
+    cursor: pointer;
+  }
+  ::v-deep {
+    .add-input {
+      flex: 1;
+      .el-input__inner {
+        padding: 0 12px;
+        height: 30px;
+        line-height: 30px;
+        color: #1d1d1d;
+      }
+      &.input-bg {
+        background: #fff;
+        .el-input__inner {
+          background: #eaf8fa;
+        }
+      }
+    }
+    .add-btn {
+      padding: 2px 0;
+      span {
+        margin-left: 0px;
+      }
+    }
+    .el-input__count-inner {
+      background: transparent !important;
+    }
+    .already-count {
+      color: $color_main;
+    }
+  }
+  &.disabled {
+    .group-list-container {
+      background: #f5f6f7;
+    }
+    .add-btn {
+      color: #999999;
+    }
+    .group-item {
+      &:hover {
+        .item-edit {
+          display: none;
+        }
+      }
+    }
+    .item-edit-container {
+      background: #fff;
+    }
+    .item-name-container,
+    .add-btn {
+      pointer-events: none;
+      cursor: not-allowed;
+    }
+    .item-name {
+      color: #999999;
+    }
+    .j-radio {
+      background-image: url('~@/assets/images/icon/radio_default_disabled.png');
+      &.r-checked {
+        background-image: url('~@/assets/images/icon/radio_checked_disabled2.png');
+      }
+    }
+    .j-checkbox {
+      background-image: url('~@/assets/images/icon/checkbox_default_disabled.png');
+      &.checked {
+        background-image: url('~@/assets/images/icon/checkbox_checked_disabled.png');
+      }
+    }
+  }
+}
+</style>

+ 80 - 5
apps/bigmember_pc/src/composables/quick-monitor/component/QuickMonitor.vue

@@ -2,7 +2,9 @@
 import commonDialog from '@/components/dialog/Dialog.vue'
 import CollectInfo from '@/components/collect-info/CollectInfo.vue'
 import MonitorPopover from '@/components/common/MonitorPopover.vue'
+import MonitorGroup from '@/composables/quick-monitor/component/MonitorGroup.vue'
 import { useQuickMonitorModel } from '@/composables/quick-monitor'
+import { getCurrentInstance, ref } from 'vue'
 
 const props = defineProps({
   type: String,
@@ -16,9 +18,17 @@ const props = defineProps({
   auto: {
     type: Boolean,
     default: true
+  },
+  // 留资来源
+  source: {
+    type: Object,
+    default: () => ({})
   }
 })
 
+const that = getCurrentInstance().proxy
+const groupRef = ref('')
+
 const {
   // 基础展示信息
   model,
@@ -31,11 +41,17 @@ const {
   // dialog 提示弹窗
   dialogConfig,
   // 留资
-  collectElement
+  collectElement,
+  // 分组
+  groupList,
+  doChangeGroup,
+  doUpdateGroup,
+  eleLoading
 } = useQuickMonitorModel({
   type: props.type,
   id: props.params,
-  cache: props.cache
+  cache: props.cache,
+  source: props.source
 })
 
 if (props.auto) {
@@ -46,6 +62,34 @@ function getParams() {
   return props.params
 }
 
+function onGroupDisabled(val) {
+  dialogConfig.value.footerActions.forEach((v) => (v.disabled = val))
+  that.$forceUpdate()
+}
+
+// 新增分组
+function onAddGroup(data) {
+  doUpdateGroup({ type: 'add', name: data?.name })
+}
+// 编辑分组
+function onEditGroup(data) {
+  doUpdateGroup({ type: 'put', groupId: data?.groupId, name: data?.name })
+}
+
+// 选择分组组件change回调事件
+function onChangeGroup(id) {
+  doChangeGroup(id)
+}
+
+function doClickMonitorActionsFn(item) {
+  //  未触发子组件change事件时,手动获取选中的分组
+  if (groupRef.value) {
+    const selected = groupRef.value.getSelected()
+    doChangeGroup(selected)
+  }
+  doClickMonitorActions(item.action || 'doCloseDialog')
+}
+
 defineExpose({
   model,
   getParams
@@ -63,6 +107,7 @@ defineExpose({
         trigger="hover"
       >
         <div
+          v-loading.lock="eleLoading"
           slot="reference"
           class="flex-r-c center action-icon"
           @click.stop="doAddFollow"
@@ -88,7 +133,8 @@ defineExpose({
     <common-dialog
       center
       custom-class="monitor-class"
-      width="380px"
+      :width="dialogConfig.width || '380px'"
+      :destroy-on-close="true"
       :visible="dialogConfig.show"
       :title="dialogConfig.title"
     >
@@ -98,12 +144,24 @@ defineExpose({
           :key="index"
           class="action-button"
           :class="item.class"
-          @click="doClickMonitorActions(item.action || 'doCloseDialog')"
+          :disabled="item.disabled"
+          @click="doClickMonitorActionsFn(item)"
         >
           {{ item.label }}
         </button>
       </template>
-      {{ dialogConfig.content }}
+      <!-- 选择监控分组 -->
+      <MonitorGroup
+        v-if="dialogConfig.template === 'group'"
+        ref="groupRef"
+        :list="groupList"
+        @emitDisabled="onGroupDisabled"
+        @onChange="onChangeGroup"
+        @add="onAddGroup"
+        @edit="onEditGroup"
+      >
+      </MonitorGroup>
+      <div v-else>{{ dialogConfig.content }}</div>
     </common-dialog>
     <!-- 留资 -->
     <collect-info ref="collectElement"></collect-info>
@@ -126,10 +184,23 @@ defineExpose({
       margin-left: 2px;
     }
   }
+  ::v-deep {
+    .el-loading-spinner {
+      margin-top: -12px;
+      .circular {
+        width: 24px;
+        height: 24px;
+      }
+    }
+  }
 }
 ::v-deep {
   .monitor-class {
     padding: 32px;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    margin: 0px !important;
     .el-dialog__header {
       padding: 0;
     }
@@ -141,5 +212,9 @@ defineExpose({
       padding: 0;
     }
   }
+  .action-button.cancel[disabled] {
+    background-color: #e3e4e5;
+    color: #999999;
+  }
 }
 </style>

+ 8 - 6
apps/bigmember_pc/src/composables/quick-monitor/use/base.js

@@ -63,32 +63,34 @@ export function useMonitorModel(
   { cache = false, stepTime = 500 } = {}
 ) {
   const useMonitor = useQuickMonitor({
-    type: type,
+    type,
     params: {
-      id: id
+      id
     }
   })
-  const { doFetch: doRunFetch, doChange } = useMonitor
+  const { doFetch: doRunFetch, doChange, doFetchGroup } = useMonitor
 
   function getId() {
     return useMonitor.id
   }
   const { model } = toRefs(reactive(useMonitor))
+  const { groupList } = toRefs(reactive(useMonitor))
 
   // 扩展支持缓存请求结果一定时间
   let lastTime = 0
   const doFetch = async () => {
     if (cache) {
-      let nowTime = Date.now()
+      const nowTime = Date.now()
       if (nowTime - lastTime > stepTime) {
         lastTime = nowTime
         return await doRunFetch()
-      } else {
+      }
+      else {
         return model.value
       }
     }
     return await doRunFetch()
   }
 
-  return { model, doChange, doFetch, getId }
+  return { model, doChange, doFetch, getId, doFetchGroup, groupList }
 }

+ 143 - 32
apps/bigmember_pc/src/composables/quick-monitor/use/ent.js

@@ -1,6 +1,6 @@
-import { computed, ref, getCurrentInstance } from 'vue'
-import { useStore } from '@/store'
+import { computed, getCurrentInstance, ref } from 'vue'
 import { useMonitorModel, useMonitorTipDialog } from './base'
+import { useStore } from '@/store'
 import {
   doOpenPushSettingPage,
   doOpenWinnerListPage,
@@ -79,23 +79,48 @@ const DialogDataMap = {
       }
     ]
   },
-  'success-toast': '监控成功,您可前往“工作台-商机-企业情报监控”查看'
+  'success-toast': '监控成功,您可前往“工作台-商机-企业情报监控”查看',
+  'monitor-group': {
+    title: '选择分组',
+    width: '464px',
+    template: 'group',
+    footerActions: [
+      {
+        label: '确定',
+        class: 'confirm',
+        action: 'confirmChangeGroup'
+      },
+      {
+        label: '取消',
+        class: 'cancel',
+        action: ''
+      }
+    ]
+  }
 }
 
-function useEntQuickMonitorModel({ type, id }, options) {
+function useEntQuickMonitorModel({ type, id, source }, options) {
   const that = getCurrentInstance().proxy
   const collectElement = ref(null)
-  const { dialogConfig, doOpenDialog, doCloseDialog } =
-    useMonitorTipDialog(DialogDataMap)
+  // 当前选择的分组名称id
+  const checkedGroupId = ref('')
+  const { dialogConfig, doOpenDialog, doCloseDialog }
+    = useMonitorTipDialog(DialogDataMap)
   const IsFreeUser = computed(() => {
     const store = useStore()
     return store.getters['user/isFree']
   })
+  const IsSVipUser = computed(() => {
+    const store = useStore()
+    return store.getters['user/isSuper']
+  })
   const loading = ref(false)
-  const { model, doChange, doFetch, getId } = useMonitorModel(
-    { type, id },
-    options
-  )
+  const { model, doChange, doFetch, getId, doFetchGroup, groupList }
+    = useMonitorModel({ type, id }, options)
+  const { limit: limitSource, more: moreSource } = source
+
+  const eleLoading = ref(false)
+
   const monitorPopoverConfig = computed(() => {
     return {
       showTip: !model.value.follow,
@@ -104,7 +129,8 @@ function useEntQuickMonitorModel({ type, id }, options) {
       showCancel: model.value.follow,
       alreadyNum: model.value.expands.used,
       remainNum: model.value.expands.surplus,
-      textType: type
+      textType: type,
+      followedGroup: model.value.followedGroup
     }
   })
 
@@ -118,6 +144,47 @@ function useEntQuickMonitorModel({ type, id }, options) {
     that?.contactCustomer(that)
   }
 
+  // 打开监控分组弹框(更改)
+  async function doOpenSelectGroup() {
+    // doOpenDialog('monitor-group')
+    await doFetchGroup({ type: 'get' }).then((res) => {
+      if (res.success) {
+        doOpenDialog('monitor-group', { groupList: res.data })
+      }
+      else {
+        if (res.data?.limit_count) {
+          if (IsFreeUser.value || IsSVipUser.value) {
+            return doOpenCollectDialog(limitSource || 'pc_article_ent_limit')
+          }
+          else {
+            return doOpenDialog('max-monitor', {
+              count: res.data?.limit_count
+            })
+          }
+        }
+      }
+    })
+  }
+
+  // 选择分组名称change事件
+  function doChangeGroup(groupId) {
+    checkedGroupId.value = groupId
+  }
+
+  // 新增、编辑分组
+  function doUpdateGroup(params) {
+    const { type, name, groupId } = params
+    const toastFn = () => {
+      if (type === 'add') {
+        that.$toast('新增分组成功')
+      }
+      else {
+        that.$toast('分组名称修改成功')
+      }
+    }
+    doFetchGroup({ type, name, groupId, callback: toastFn })
+  }
+
   /**
    * 监控操作业务流程
    * 0. 前置权益判断
@@ -136,44 +203,41 @@ function useEntQuickMonitorModel({ type, id }, options) {
    * @return {Promise<void>}
    */
   async function doAddFollow() {
-    if (loading.value) return
-    loading.value = true
     // 业务流程
     if (!model.value.follow) {
-      await doChange()
+      eleLoading.value = true
+      // 先弹出选择分组弹框
+      await doFetchGroup({ type: 'get' })
         .then((res) => {
           if (res.success) {
-            // 判断是否开启推送提醒
-            if (!res.data?.msg_open) {
-              doOpenDialog('success-monitor')
-            } else {
-              that.$toast(DialogDataMap['success-toast'])
-            }
-          } else {
-            // 判断是否超出可监控项目个数
+            doOpenDialog('monitor-group', { groupList: res.data })
+          }
+          else {
             if (res.data?.limit_count) {
-              if (IsFreeUser.value) {
-                return doOpenCollectDialog('pc_article_ent_limit')
-              } else {
+              if (IsFreeUser.value || IsSVipUser.value) {
+                return doOpenCollectDialog(
+                  limitSource || 'pc_article_ent_limit'
+                )
+              }
+              else {
                 return doOpenDialog('max-monitor', {
                   count: res.data?.limit_count
                 })
               }
             }
-            that.$toast(res.msg)
           }
         })
         .finally(() => {
-          loading.value = false
+          eleLoading.value = false
         })
     }
-    loading.value = false
   }
 
   /**
    * 统一处理 dialog, popover emit 事件
    * @param {string} type
    *  // popover 事务
+   *  - group 当前分组
    *  - more 查看监控动态
    *  - list 查看监控列表
    *  - apply 申请监控更多
@@ -209,9 +273,10 @@ function useEntQuickMonitorModel({ type, id }, options) {
         doOpenDialog('cancel-monitor')
         break
       case 'apply':
-        if (IsFreeUser.value) {
-          doOpenCollectDialog('pc_article_ent_more')
-        } else {
+        if (IsFreeUser.value || IsSVipUser.value) {
+          doOpenCollectDialog(moreSource || 'pc_article_ent_more')
+        }
+        else {
           doOpenDialog('apply-monitor')
         }
         break
@@ -226,6 +291,46 @@ function useEntQuickMonitorModel({ type, id }, options) {
       case 'list':
         doOpenWinnerListPage()
         break
+      case 'group':
+        doOpenSelectGroup()
+        break
+      case 'confirmChangeGroup':
+        if (loading.value)
+          return
+        loading.value = true
+        doChange(false, { groupId: checkedGroupId.value })
+          .then((res) => {
+            if (res.success) {
+              doCloseDialog()
+              // 关注成功,重新获取监控数据
+              doFetch()
+              // 判断是否开启推送提醒
+              if (!res.data?.msg_open) {
+                doOpenDialog('success-monitor')
+              }
+              else {
+                that.$toast(DialogDataMap['success-toast'])
+              }
+            }
+            else {
+              // 判断是否超出可监控项目个数
+              if (res.data?.limit_count) {
+                if (IsFreeUser.value || IsSVipUser.value) {
+                  return doOpenCollectDialog('pc_article_ent_limit')
+                }
+                else {
+                  return doOpenDialog('max-monitor', {
+                    count: res.data?.limit_count
+                  })
+                }
+              }
+              that.$toast(res.msg)
+            }
+          })
+          .finally(() => {
+            loading.value = false
+          })
+        break
       default:
         break
     }
@@ -243,7 +348,13 @@ function useEntQuickMonitorModel({ type, id }, options) {
     // dialog 提示弹窗
     dialogConfig,
     // 留资
-    collectElement
+    collectElement,
+    // 获取(新增、编辑、删除)分组
+    doFetchGroup,
+    groupList,
+    doChangeGroup,
+    doUpdateGroup,
+    eleLoading
   }
 }
 

+ 7 - 7
apps/bigmember_pc/src/views/article-content/composables/useArticleUtil.js

@@ -5,12 +5,12 @@ export function doOpenArticlePage({ id }) {
   const link = router.resolve({
     name: 'article_detail',
     params: {
-      id: id,
+      id,
       content: 'content'
     }
   })
   window.open(
-    link.href.replace('/swordfish/page_big_pc', '') + '.html',
+    `${link.href.replace('/swordfish/page_big_pc', '')}.html`,
     '_blank'
   )
 }
@@ -18,8 +18,8 @@ export function doOpenArticlePage({ id }) {
 // 打开采购单位详情页
 export function doOpenBuyerPage({ name, query = {} }) {
   const link = router.resolve({
-    path: '/unit_portrayal/' + name,
-    query: query
+    path: `/unit_portrayal/${name}`,
+    query
   })
   window.open(link.href, '_blank')
 }
@@ -40,8 +40,8 @@ export function doOpenBuyerListPage() {
 // 打开中标企业详情页
 export function doOpenWinnerPage({ id, query = {} }) {
   const link = router.resolve({
-    path: '/ent_portrait/' + id,
-    query: query
+    path: `/ent_portrait/${id}`,
+    query
   })
   window.open(link.href, '_blank')
 }
@@ -77,7 +77,7 @@ export function doOpenProjectProgressListPage() {
 
 // 打开推送提醒设置
 export function doOpenPushSettingPage() {
-  window.open('/swordfish/page_big_pc/push_setting')
+  window.open('/page_workDesktop/work-bench/app/big/push_setting')
 }
 
 /**

+ 70 - 5
apps/bigmember_pc/src/views/ent-intel/EntIntel.vue

@@ -11,7 +11,11 @@
       <TabHeader slot="bidtab" actived="ent"></TabHeader>
       <img class="bidfor_img" src="@/assets/images/item_1.png" slot="bidImg" />
       <template v-slot:main>
-        <ForFilter v-if="type == 0" @onPageChange="getMyFollowList"></ForFilter>
+        <ForFilter
+          v-if="type == 0"
+          :group-list="groupList"
+          @onPageChange="getMyFollowList"
+        ></ForFilter>
         <ForeCast
           style="border-radius: 4px"
           v-loading="loading"
@@ -22,6 +26,8 @@
           v-if="type == 0"
           slot="main"
           :myDataObj="myDataObj"
+          :groupList="formatGroupList"
+          @updateGroup="onUpdateGroup"
         ></ForeCast>
         <ForeCast
           key="entintelRes"
@@ -42,8 +48,13 @@ import ForeCast from '@/components/forecast/ForeCast.vue'
 import TabHeader from '@/components/common/TabHeader.vue'
 import ForFilter from '@/components/forecast/ForFilter.vue'
 import { mapState } from 'vuex'
-import { getEntFollowList } from '@/api/modules'
+import {
+  getEntFollowList,
+  ajaxFollowEntGroup,
+  ajaxFollowUpdateGroup
+} from '@/api/modules'
 import { scrollTargetView } from '@/utils/'
+
 export default {
   name: 'ent-intel',
   components: {
@@ -61,7 +72,9 @@ export default {
         initTotal: 0 // 已关注总数
       },
       tiptext: '',
-      loading: true
+      loading: true,
+      groupList: [],
+      formatGroupList: []
     }
   },
   computed: {
@@ -71,8 +84,9 @@ export default {
       type: (state) => state.forcast.type
     })
   },
-  created() {
+  async created() {
     // listData
+    this.ajaxEntGroupList({ type: 'get' })
     this.ajaxGetEntFollowList()
   },
   mounted() {
@@ -96,7 +110,58 @@ export default {
         scrollTargetView('.listData_ul')
       })
     },
-    ajaxGetEntFollowList (p = 0, match = '', group = '', pageSize = 10) {
+    onUpdateGroup(params) {
+      this.ajaxEntGroupList(params)
+    },
+    async onUpdateGroup(params) {
+      const { type, groupId, name } = params
+      const { error_code: code } = await ajaxFollowUpdateGroup({
+        type,
+        groupId,
+        name
+      })
+      if (code === 0) {
+        this.$toast(type === 'add' ? '新增分组成功' : '修改分组成功')
+        this.ajaxEntGroupList()
+      }
+    },
+    // 获取分组(新增、编辑)
+    async ajaxEntGroupList() {
+      const {
+        error_code: code,
+        data,
+        error_msg: msg
+      } = await ajaxFollowEntGroup({ type: 'get' })
+      if (code === 0) {
+        // 分组
+        if (data?.groupUserArr) {
+          const newItem = {
+            id: '',
+            name: '全部',
+            count: 0
+          }
+          data?.groupUserArr.unshift(newItem)
+          // 筛选项分组(增加全部选项)
+          this.groupList = data?.groupUserArr.map((group) => {
+            return {
+              id: group.id,
+              name: group.name,
+              isPut: group.isPut,
+              count: group.count
+            }
+          })
+          // 编辑分组(排除全部)
+          this.formatGroupList = this.groupList.filter((v) => {
+            return v.id
+          })
+        } else {
+          this.groupList = []
+        }
+      } else {
+        this.$toast(msg || '操作失败')
+      }
+    },
+    ajaxGetEntFollowList(p = 0, match = '', group = '', pageSize = 10) {
       getEntFollowList({
         pageNum: p,
         pageSize: pageSize,

+ 71 - 9
apps/bigmember_pc/src/views/portrayal/components/EntFollowStar.vue

@@ -1,9 +1,16 @@
 <template>
   <div class="ent-follow-container" v-show="followCheck.isShow">
-    <div class="ent-follow" @click="setFollow()">
+    <quick-monitor
+      class="action-item"
+      :cache="true"
+      type="ent"
+      :params="id"
+      :source="sourceMap"
+    ></quick-monitor>
+    <!-- <div class="ent-follow" @click="setFollow()">
       <span class="iconfont" :class="follow.classActive"></span>
       <span class="follow_text">{{ follow.text }}</span>
-    </div>
+    </div> -->
     <!-- 监控分组选择dialog -->
     <el-dialog
       custom-class="sub-dialog"
@@ -26,12 +33,14 @@
 <script>
 import { getfollowCheck, setCancelEnt, setFollowEnt } from '@/api/modules'
 import GroupSelector from '@/components/selector/GroupSelector.vue'
+import QuickMonitor from '@/composables/quick-monitor/component/QuickMonitor.vue'
 import { Dialog } from 'element-ui'
 export default {
   name: 'EntFollowStar',
   components: {
     [Dialog.name]: Dialog,
-    GroupSelector
+    GroupSelector,
+    QuickMonitor
   },
   props: {
     id: {
@@ -57,6 +66,10 @@ export default {
         groupInitData: ['A'],
         dialog: false,
         loading: false
+      },
+      sourceMap: {
+        limit: 'pc_ent_limit',
+        more: 'pc_ent_more'
       }
     }
   },
@@ -140,13 +153,62 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-::v-deep .sub-dialog {
-  background-color: transparent;
-  box-shadow: none;
+::v-deep {
+  .sub-dialog {
+    background-color: transparent;
+    box-shadow: none;
 
-  .el-dialog__header,
-  .el-dialog__body {
-    padding: 0;
+    .el-dialog__header,
+    .el-dialog__body {
+      padding: 0;
+    }
+  }
+  .group-dialog {
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    margin: 0px !important;
+    border-radius: 8px;
+    .el-dialog__header {
+      padding: 32px 32px 20px;
+    }
+    .el-dialog__body {
+      padding: 0 32px;
+    }
+    .el-dialog__footer {
+      padding: 32px 0;
+      .el-button {
+        width: 132px;
+        height: 36px;
+        padding: 0;
+        border-radius: 6px;
+        font-size: 16px;
+        &.el-button--primary {
+          margin-right: 52px;
+          &.is-disabled {
+            background-color: #87dfea;
+            color: rgba(255, 255, 255, 0.6);
+          }
+        }
+        &.el-button--default {
+          &:hover {
+            background: transparent;
+            border-color: #e0e0e0;
+          }
+          color: #1d1d1d;
+          &.is-disabled {
+            background-color: #e3e4e5;
+            color: #999999;
+          }
+        }
+      }
+    }
+  }
+  .quick-monitor-popover {
+    .action-icon > span {
+      font-size: 13px;
+      color: #686868;
+    }
   }
 }
 .ent-follow {

+ 12 - 2
apps/mobile/src/api/modules/bigmember.js

@@ -1,5 +1,5 @@
-import request from '@/api'
 import qs from 'qs'
+import request from '@/api'
 
 /**
  * 获取用户信息
@@ -149,7 +149,7 @@ export function getProjectReport(data) {
     data
   })
 }
-//定制化分析数据
+// 定制化分析数据
 export function getAnalysisResult(data) {
   data = qs.stringify(data)
   return request({
@@ -158,3 +158,13 @@ export function getAnalysisResult(data) {
     data
   })
 }
+
+// 我关注的企业列表
+export function getEntFollowList(data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/bigmember/follow/ent/labelGroupList',
+    method: 'post',
+    data
+  })
+}

+ 11 - 0
apps/mobile/src/api/modules/entgroup.js

@@ -0,0 +1,11 @@
+import qs from 'qs'
+import request from '@/api'
+// 获取数据超市列表信息
+export function getGroupList(data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/bigmember/follow/ent/labelGroup',
+    method: 'post',
+    data
+  })
+}

+ 1 - 0
apps/mobile/src/api/modules/index.js

@@ -27,3 +27,4 @@ export * from './invoice'
 export * from './business'
 export * from './docs'
 export * from './reportanalysis'
+export * from './entgroup'

+ 21 - 0
apps/mobile/src/router/modules/entgroup.js

@@ -0,0 +1,21 @@
+// 数据超市路由
+export default [
+  {
+    path: '/index',
+    name: 'EntGroupIndex',
+    component: () => import('@/views/entgroup/index.vue'),
+    meta: {
+      header: true,
+      title: '企业分组管理'
+    }
+  },
+  {
+    path: '/detail',
+    name: 'EntGroupDetail',
+    component: () => import('@/views/entgroup/detail.vue'),
+    meta: {
+      header: true,
+      title: '编辑分组'
+    }
+  }
+]

+ 5 - 3
apps/mobile/src/store/index.js

@@ -9,6 +9,7 @@ import ent from './modules/ent'
 import points from './modules/points'
 import article from './modules/article'
 import docs from './modules/docs'
+import group from './modules/group'
 
 if (import.meta.env.DEV) {
   Vue.use(Vuex)
@@ -30,8 +31,8 @@ export default new Vuex.Store({
     },
     // 对指定组件进行动态缓存 -- 组件调用该方法时,判断该组件是否存在于该缓存数组,无则添加
     keepAlive(state, component) {
-      !state.keepAliveList.includes(component) &&
-        state.keepAliveList.push(component)
+      !state.keepAliveList.includes(component)
+      && state.keepAliveList.push(component)
     },
     // 对指定组件进行动态取消缓存-- 组件调用该方法时,从缓存数组中删除对应的组件元素
     unKeepAlive(state, component) {
@@ -49,6 +50,7 @@ export default new Vuex.Store({
     ent,
     article,
     points,
-    docs
+    docs,
+    group
   }
 })

+ 28 - 0
apps/mobile/src/store/modules/group.js

@@ -0,0 +1,28 @@
+import { getEntFollowList } from '@/api/modules/'
+
+export default {
+  namespaced: true,
+  state: () => ({
+    // 企业分组数据
+    entGroupList: []
+  }),
+  mutations: {
+    setEntGroupList(state, entGroupList) {
+      state.entGroupList = entGroupList
+    }
+  },
+  actions: {
+    async setEntGroupList({ commit }) {
+      const res = await getEntFollowList()
+      if (res.error_code === 0) {
+        commit('setEntGroupList', res.data?.groupUserArr || [])
+        return res || []
+      }
+    }
+  },
+  getters: {
+    getEntGroupList: (state) => {
+      return state.entGroupList
+    }
+  }
+}

+ 163 - 0
apps/mobile/src/views/entgroup/detail.vue

@@ -0,0 +1,163 @@
+<template>
+  <div class="ent-group-detail">
+    <div class="j-main">
+      <van-field
+        v-model="groupNameValue"
+        label="分组名称"
+        placeholder="请输入分组名称"
+        maxlength="15"
+        show-word-limit
+        :error-message="errorMessageText"
+        @input="setGroupingName"
+      />
+    </div>
+    <div class="j-footer">
+      <div class="j-button-group">
+        <button class="j-button-cancel" @click="isShowDialog = true">
+          删除
+        </button>
+        <button
+          :disabled="isConfirmButtonDisabled"
+          class="j-button-confirm"
+          @click="setGroupNameEvent('put')"
+        >
+          保存
+        </button>
+      </div>
+    </div>
+    <DelDialog
+      title="删除分组"
+      :show-dialog="isShowDialog"
+      @cancel="isShowDialog = false"
+      @confirm="handleConfirm"
+    >
+      <div slot="content" class="del-group-content">
+        删除后,该分组内的企业将自动移至“默认分组”,是否确认删除?
+      </div>
+    </DelDialog>
+  </div>
+</template>
+
+<script>
+import { Field } from 'vant'
+import { mapState } from 'vuex'
+import { getGroupList } from '@/api/modules/'
+import DelDialog from '@/components/common/Dialog.vue'
+
+export default {
+  name: 'EntGroupDetail',
+  components: {
+    [Field.name]: Field,
+    DelDialog
+  },
+  data() {
+    return {
+      groupNameValue: '',
+      params: {
+        name: '',
+        id: ''
+      },
+      errorMessageText: '',
+      isConfirmButtonDisabled: true,
+      isShowDialog: false
+    }
+  },
+  computed: {
+    ...mapState('group', ['entGroupList'])
+  },
+  created() {
+    this.params = {
+      ...this.$route.query
+    }
+    this.groupNameValue = this.params.name || ''
+  },
+  methods: {
+    async setGroupNameEvent(type) {
+      const { error_code: code, error_msg: msg } = await getGroupList({
+        type,
+        name: this.groupNameValue,
+        groupId: this.params.id
+      })
+      if (code === 0) {
+        if (type === 'del') {
+          this.$toast.success('删除成功')
+        } else {
+          this.$toast.success('保存成功')
+        }
+        setTimeout(() => {
+          this.$router.back()
+        }, 1000)
+      } else {
+        this.$toast.fail(msg)
+      }
+    },
+    handleConfirm() {
+      this.setGroupNameEvent('del')
+      this.isShowDialog = false
+    },
+    setGroupingName(val) {
+      console.log(this.entGroupList)
+      this.groupNameValue = val.trim().replace(/\s+/g, '')
+      this.errorMessageText = ''
+      this.isConfirmButtonDisabled = false
+
+      if (this.groupNameValue === '') {
+        this.errorMessageText = '分组名称不能为空'
+        this.isConfirmButtonDisabled = true
+      } else {
+        const exists = this.entGroupList.some(
+          (item) => item.name === this.groupNameValue
+        )
+        if (exists) {
+          this.errorMessageText = '分组名称已存在'
+          this.isConfirmButtonDisabled = true
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.ent-group-detail {
+  .van-cell {
+    margin-top: 8px;
+    flex-direction: column;
+    ::v-deep {
+      .van-cell__title {
+        font-size: 12px;
+        color: #5f5e64;
+        line-height: 16px;
+      }
+      .van-field__body {
+        margin-top: 4px;
+        .van-field__control {
+          height: 24px;
+          line-height: 24px;
+          &::placeholder {
+            color: #9b9ca3;
+            font-size: 14px;
+            line-height: 16px;
+          }
+        }
+      }
+      .van-field__word-limit {
+        position: absolute;
+        right: 0;
+        top: 8px;
+        font-size: 14px;
+      }
+      .van-field__word-num {
+        color: #2abed1;
+      }
+    }
+  }
+}
+::v-deep {
+  .del-group-content {
+    font-size: 14px;
+    color: #171826;
+    line-height: 20px;
+  }
+}
+</style>

+ 219 - 0
apps/mobile/src/views/entgroup/index.vue

@@ -0,0 +1,219 @@
+<template>
+  <div class="ent-group-index">
+    <div class="j-main">
+      <van-cell-group>
+        <van-cell
+          v-for="item in groupList"
+          :key="item.id"
+          :title="`${item.name}(${item.count})`"
+          :is-link="item.isPut !== 0"
+          @click="setGroupEvent(item)"
+        />
+      </van-cell-group>
+      <Empty v-if="groupList.length === 0" />
+    </div>
+    <div class="j-footer">
+      <div class="j-button-group">
+        <div class="j-button-confirm" @click="showAddGroupingDialog = true">
+          <van-icon name="plus" />
+          <span style="margin-left: 8px">新增分组</span>
+        </div>
+      </div>
+    </div>
+    <van-dialog
+      v-model="showAddGroupingDialog"
+      width="303px"
+      class="add-grouping-dialog"
+      title="新增分组"
+      :show-confirm-button="false"
+      @confirm="addGrouping"
+    >
+      <van-field
+        v-model="groupingName"
+        show-word-limit
+        maxlength="15"
+        :error-message="errorMessageText"
+        placeholder="请输入分组名称"
+        @input="setGroupingName"
+      />
+      <div class="dialog-footer">
+        <van-button type="default" @click="cancelAddGrouping">
+          取消
+        </van-button>
+        <van-button
+          type="default"
+          class="confirm-button"
+          :disabled="isConfirmButtonDisabled"
+          @click="addGrouping"
+        >
+          确定
+        </van-button>
+      </div>
+    </van-dialog>
+  </div>
+</template>
+
+<script>
+import { Button, Cell, CellGroup, Dialog, Field, Icon } from 'vant'
+import { mapActions } from 'vuex'
+import { getGroupList } from '@/api/modules/'
+import Empty from '@/components/common/empty.vue'
+
+export default {
+  name: 'EntGroupIndex',
+  components: {
+    [Cell.name]: Cell,
+    [CellGroup.name]: CellGroup,
+    [Dialog.name]: Dialog,
+    [Button.name]: Button,
+    [Icon.name]: Icon,
+    [Field.name]: Field,
+    Empty
+  },
+  data() {
+    return {
+      groupList: [],
+      groupingName: '',
+      showAddGroupingDialog: false,
+      errorMessageText: '',
+      isConfirmButtonDisabled: true
+    }
+  },
+  created() {
+    this.getMyFollowList()
+  },
+  methods: {
+    ...mapActions('group', ['setEntGroupList']),
+    async getMyFollowList() {
+      const loading = this.$toast.loading()
+      const { error_code: code, data } = await this.setEntGroupList()
+      console.log(data)
+      if (code === 0) {
+        loading.clear()
+        this.groupList = data?.groupUserArr || []
+      } else {
+        loading.clear()
+        this.$toast('获取分组列表失败')
+      }
+    },
+    setGroupEvent(data) {
+      if (data.name === '默认分组') return
+      this.$router.push({
+        path: '/entgroup/detail',
+        query: {
+          id: data.id,
+          name: data.name
+        }
+      })
+    },
+    setGroupingName(val) {
+      this.groupingName = val.trim().replace(/\s+/g, '')
+      this.errorMessageText = ''
+      this.isConfirmButtonDisabled = false
+
+      if (this.groupingName === '') {
+        this.errorMessageText = '分组名称不能为空'
+        this.isConfirmButtonDisabled = true
+      } else {
+        const exists = this.groupList.some(
+          (item) => item.name === this.groupingName
+        )
+        if (exists) {
+          this.errorMessageText = '分组名称已存在'
+          this.isConfirmButtonDisabled = true
+        }
+      }
+    },
+    async addGrouping() {
+      const { error_code: code } = await getGroupList({
+        name: this.groupingName,
+        type: 'add'
+      })
+      if (code === 0) {
+        this.$toast('新增分组成功')
+        this.groupingName = ''
+        this.showAddGroupingDialog = false
+        this.getMyFollowList()
+      } else {
+        this.$toast('新增分组失败')
+      }
+    },
+    cancelAddGrouping() {
+      this.groupingName = ''
+      this.showAddGroupingDialog = false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.ent-group-index {
+  .van-cell-group {
+    margin-top: 8px;
+  }
+  .van-cell {
+    padding: 15px 16px;
+    &::after {
+      left: 0;
+      right: 0;
+    }
+    .van-cell__title {
+      font-size: 16px;
+      color: #171826;
+      line-height: 24px;
+    }
+  }
+  .add-grouping-dialog {
+    ::v-deep {
+      .van-dialog__header {
+        font-size: 18px;
+      }
+      .van-field__control {
+        padding-right: 3.32rem;
+      }
+      .dialog-footer {
+        display: flex;
+        justify-content: space-between;
+        .van-button {
+          flex: 1;
+          font-size: 18px;
+          border: none;
+          border-top: 1px solid rgba(0, 0, 0, 0.1);
+          &.confirm-button {
+            color: #2abed1;
+            border-left: 1px solid rgba(0, 0, 0, 0.1);
+          }
+        }
+      }
+      .van-cell {
+        padding: 0 16px;
+        margin: 16px 0 22px 0;
+        &::after {
+          border: 0;
+        }
+        .van-field__body {
+          padding-bottom: 11px;
+          border-bottom: 0.5px solid rgba(0, 0, 0, 0.05);
+        }
+      }
+      .van-field__control::placeholder {
+        color: #9b9ca3;
+      }
+      .van-field__word-limit {
+        position: absolute;
+        right: 0;
+        top: 0;
+        .van-field__word-num {
+          color: #2abed1;
+        }
+      }
+      .van-field__error-message {
+        margin-top: 8px;
+        font-size: 14px;
+        color: #fb483d;
+        line-height: 20px;
+      }
+    }
+  }
+}
+</style>

+ 2 - 2
apps/mobile/vite.config.js

@@ -131,12 +131,12 @@ export default defineConfig(({ mode, command }) => {
           changeOrigin: true
         },
         '/jyapi': {
-          target: 'https://app3-jytest.jydev.jianyu360.com',
+          target: 'https://app-jytest.jydev.jianyu360.com',
           changeOrigin: true,
           rewrite: path => path.replace(/^\/jyapi/, '')
         },
         '/api': {
-          target: 'https://app3-jytest.jydev.jianyu360.com',
+          target: 'https://app-jytest.jydev.jianyu360.com',
           changeOrigin: true,
           rewrite: path => path.replace(/^\/api/, '')
         },

+ 12 - 1
data/data-models/modules/quick-monitor/api/follow-ent.js

@@ -1,12 +1,13 @@
 import qs from 'qs'
 import { useRequest } from '../../../api'
+
 export function ajaxFollowEntInfo(data) {
   data = qs.stringify(data)
   return useRequest({
     url: '/bigmember/follow/ent/followCheck',
     method: 'post',
     noToast: true,
-    data: data
+    data
   })
 }
 
@@ -27,3 +28,13 @@ export function ajaxFollowEntCancel(data) {
     data
   })
 }
+
+// 企业监控分组(获取、新增、编辑、删除),包含达到监控上限相关
+export function ajaxMonitorGroup(data) {
+  data = qs.stringify(data)
+  return useRequest({
+    url: '/bigmember/follow/ent/labelGroup',
+    method: 'post',
+    data
+  })
+}

+ 31 - 3
data/data-models/modules/quick-monitor/plugins/base.js

@@ -6,9 +6,14 @@
 class MonitorApiBase {
   constructor() {
     this.model = this.createModel()
+    // 分组列表
+    this.groupList = []
+    // 分组id
+    this.groupId = ''
     // 通用函数Hooks
     this.doFetch = this.runFetch.bind(this)
     this.doChange = this.runChange.bind(this)
+    this.doFetchGroup = this.runFetchGroup.bind(this)
   }
 
   /**
@@ -25,7 +30,9 @@ class MonitorApiBase {
    * @param follow
    * @return {Promise<{}>}
    */
-  async runChange(follow = this.model.follow) {
+  async runChange(follow = this.model.follow, params = {}) {
+    const { groupId } = params
+    this.groupId = groupId
     let result = {
       // 操作是否成功
       success: false,
@@ -33,7 +40,9 @@ class MonitorApiBase {
       data: {}
     }
 
-    const doAction = follow ? this.ajaxRemove.bind(this) : this.ajaxAdd.bind(this)
+    const doAction = follow
+      ? this.ajaxRemove.bind(this)
+      : this.ajaxAdd.bind(this)
     result = await doAction()
     if (result.success) {
       this.model.follow = !follow
@@ -41,6 +50,17 @@ class MonitorApiBase {
     return result
   }
 
+  /**
+   * 获取监控分组信息并赋值
+   */
+  async runFetchGroup(params = {}) {
+    const result = await this.ajaxGroup(params)
+    if (result.success) {
+      this.groupList = result.data
+    }
+    return result
+  }
+
   createModel() {
     return {
       // 是否可以展示监控按钮
@@ -53,7 +73,9 @@ class MonitorApiBase {
         surplus: 0,
         // 已用数量
         used: 0
-      }
+      },
+      // 分组名
+      followedGroup: ''
     }
   }
 
@@ -64,12 +86,18 @@ class MonitorApiBase {
   async ajaxGetState() {
     return {}
   }
+
   async ajaxAdd() {
     return {}
   }
+
   async ajaxRemove() {
     return {}
   }
+
+  async ajaxGroup() {
+    return {}
+  }
 }
 
 export default MonitorApiBase

+ 61 - 24
data/data-models/modules/quick-monitor/plugins/ent-api.js

@@ -1,9 +1,10 @@
-import MonitorApiBase from './base'
 import {
-  ajaxFollowEntInfo,
   ajaxFollowEntAdd,
-  ajaxFollowEntCancel
+  ajaxFollowEntCancel,
+  ajaxFollowEntInfo,
+  ajaxMonitorGroup
 } from '../api/follow-ent'
+import MonitorApiBase from './base'
 
 export default class MonitorEntApi extends MonitorApiBase {
   constructor({ id }) {
@@ -20,6 +21,7 @@ export default class MonitorEntApi extends MonitorApiBase {
       const result = this.createModel()
       result.canFollow = res?.data?.isShow || false
       result.follow = res?.data?.followed || false
+      result.followedGroup = res?.data?.followedGroup || ''
 
       if (res?.data?.info) {
         result.expands = res.data.info
@@ -27,42 +29,77 @@ export default class MonitorEntApi extends MonitorApiBase {
       return result
     })
   }
+
   async ajaxAdd() {
-    return ajaxFollowEntAdd({ entId: this.id }).then((res) => {
+    return ajaxFollowEntAdd({ entId: this.id, groups: this.groupId }).then(
+      (res) => {
+        const result = {
+          success: false,
+          msg: '',
+          data: {}
+        }
+        result.success = res?.error_code === 0 && res?.data?.status
+        result.data = res?.data
+        if (result.success) {
+          this.model.expands.used += 1
+          this.model.expands.surplus = Math.max(
+            this.model.expands.surplus - 1,
+            0
+          )
+        }
+        else {
+          result.msg = res?.error_msg || '抱歉,操作失败'
+        }
+        return result
+      }
+    )
+  }
+
+  async ajaxRemove() {
+    return ajaxFollowEntCancel({ entId: this.id }).then((res) => {
       const result = {
         success: false,
         msg: '',
         data: {}
       }
-      result.success = res?.error_code === 0 && res?.data?.status
+      result.success = res?.error_code === 0 && res?.data === 'success'
       result.data = res?.data
       if (result.success) {
-        this.model.expands.used += 1
-        this.model.expands.surplus = Math.max(this.model.expands.surplus - 1, 0)
-      } else {
+        this.model.expands.surplus += 1
+        this.model.expands.used = Math.max(this.model.expands.used - 1, 0)
+        this.model.followedGroup = ''
+      }
+      else {
         result.msg = res?.error_msg || '抱歉,操作失败'
       }
       return result
     })
   }
-  async ajaxRemove() {
-    return ajaxFollowEntCancel({ entId: this.id }).then(
-      (res) => {
-        const result = {
-          success: false,
-          msg: '',
-          data: {}
+
+  async ajaxGroup(params = {}) {
+    const { type, name, groupId, callback } = params
+    return ajaxMonitorGroup({ type, name, groupId }).then((res) => {
+      const result = {
+        success: false,
+        msg: '',
+        data: {}
+      }
+      result.success = res?.error_code === 0
+      result.data = res?.data
+      if (result.success) {
+        // 非获取(新增、修改、删除)成功后再次获取分组列表
+        if (type !== 'get') {
+          this.ajaxGroup({ type: 'get' })
         }
-        result.success = res?.error_code === 0 && res?.data === 'success'
-        result.data = res?.data
-        if (result.success) {
-          this.model.expands.surplus += 1
-          this.model.expands.used = Math.max(this.model.expands.used - 1, 0)
-        } else {
-          result.msg = res?.error_msg || '抱歉,操作失败'
+        else {
+          this.groupList = res?.data
         }
-        return result
+        callback && callback()
       }
-    )
+      else {
+        result.msg = res?.error_msg || '抱歉,操作失败'
+      }
+      return result
+    })
   }
 }