Sfoglia il codice sorgente

Merge branch 'master' into hotfix/v1.5.8.1

lianbingjie 2 anni fa
parent
commit
6f6ec841c4
85 ha cambiato i file con 10731 aggiunte e 210 eliminazioni
  1. 2 0
      src/api/modules/index.js
  2. 5 8
      src/api/modules/marketing.js
  3. 71 0
      src/api/modules/medical.js
  4. 92 0
      src/api/modules/medicalField.js
  5. 20 0
      src/api/modules/subscribe.js
  6. 1 1
      src/api/modules/svip.js
  7. BIN
      src/assets/fonts/iconfont.ttf
  8. BIN
      src/assets/fonts/iconfont.woff
  9. BIN
      src/assets/fonts/iconfont.woff2
  10. BIN
      src/assets/images/hospital@3x.png
  11. BIN
      src/assets/images/icon/icon-menu.png
  12. BIN
      src/assets/images/icon/renling.png
  13. 28 0
      src/assets/js/selector.js
  14. 403 71
      src/assets/style/iconfont.css
  15. 6 0
      src/components/article-item/ArticleItem.vue
  16. 4 2
      src/components/collect-info/CollectInfo.vue
  17. 165 0
      src/components/common/Dialog.vue
  18. 3 0
      src/components/common/SpecCard.vue
  19. 87 0
      src/components/common/informationSuccess.vue
  20. 152 0
      src/components/drawer/Drawer.vue
  21. 3 0
      src/components/forecast/ForeCast.vue
  22. 6 0
      src/components/home/FloatSide.vue
  23. 74 0
      src/components/input/BaseInput.vue
  24. 62 0
      src/components/list-item/listItem.vue
  25. 73 0
      src/components/medical/CommonTab.vue
  26. 139 0
      src/components/medical/FollowFilter.vue
  27. 541 0
      src/components/medical/FollowList.vue
  28. 492 0
      src/components/push-list/DistributorSearch.vue
  29. 452 0
      src/components/push-list/MedicalList.vue
  30. 1 1
      src/components/push-list/PotentialList.vue
  31. 1 1
      src/components/push-list/ProjectList.vue
  32. 1 1
      src/components/push-list/ProjectProgressList.vue
  33. 28 4
      src/components/push-list/PushList.vue
  34. 551 0
      src/components/scope/components/Edit.vue
  35. 504 0
      src/components/scope/components/List.vue
  36. 202 0
      src/components/scope/index.vue
  37. 29 0
      src/components/selector/AreaSelector.vue
  38. 67 13
      src/components/selector/AreaSelectorContent.vue
  39. 1 1
      src/components/selector/BusinessScopeSelectorContent.vue
  40. 5 0
      src/components/selector/BuyerclassSelector.vue
  41. 9 2
      src/components/selector/BuyerclassSelectorContent.vue
  42. 4 4
      src/components/selector/InfoTypeSelectorContent.vue
  43. 76 0
      src/components/selector/MedicalSelector.vue
  44. 319 0
      src/components/selector/MedicalSelectorContent.vue
  45. 1 0
      src/components/selector/PopSelector.vue
  46. 108 0
      src/components/selector/PushTimeSelector.vue
  47. 8 1
      src/components/selector/SelectorCard.vue
  48. 201 0
      src/components/subscribe-manager/KeyConfig.vue
  49. 254 0
      src/components/subscribe-manager/PushConfig.vue
  50. 409 0
      src/components/subscribe-manager/SubConfig.vue
  51. 625 0
      src/components/subscribe-manager/components/Edit.vue
  52. 736 0
      src/components/subscribe-manager/components/List.vue
  53. 351 0
      src/components/subscribe-manager/index.vue
  54. 4 4
      src/components/work-desktop/CommonUse.vue
  55. 32 0
      src/router/medical-field.js
  56. 14 6
      src/router/router-interceptors.js
  57. 4 0
      src/router/router.js
  58. 13 1
      src/router/routers.js
  59. 4 2
      src/store/index.js
  60. 31 0
      src/store/medical.js
  61. 45 9
      src/store/user.js
  62. 15 1
      src/utils/format.js
  63. 26 0
      src/utils/globalFunctions.js
  64. 62 20
      src/views/SubPush.vue
  65. 3 0
      src/views/analysisReport/components/MarketOverview.vue
  66. 15 0
      src/views/medical-field/Buy.vue
  67. 827 0
      src/views/medical-field/Credentials.vue
  68. 365 0
      src/views/medical-field/MedicalPortrait.vue
  69. 548 0
      src/views/medical/DistributorSearch.vue
  70. 471 0
      src/views/medical/FindMedical.vue
  71. 166 0
      src/views/medical/distributorFollowed.vue
  72. 362 0
      src/views/medical/institutionFollowed.vue
  73. 111 3
      src/views/portrayal/EntPortrayal.vue
  74. 112 3
      src/views/portrayal/EntSearchPortrayal.vue
  75. 21 19
      src/views/portrayal/components/AnalysisDetailList.vue
  76. 75 23
      src/views/portrayal/components/DynamicList.vue
  77. 6 0
      src/views/portrayal/components/DynamicListItem.vue
  78. 1 1
      src/views/portrayal/components/EntForm.vue
  79. 3 0
      src/views/subscribe/components/key/Edit.vue
  80. 3 0
      src/views/subscribe/components/key/KeyConfig.vue
  81. 3 0
      src/views/subscribe/components/key/List.vue
  82. 3 0
      src/views/subscribe/components/scope/Edit.vue
  83. 3 0
      src/views/subscribe/components/scope/List.vue
  84. 1 1
      src/views/vipsubscribe/Buy.vue
  85. 10 7
      src/views/workspace/components/CommonUse.vue

+ 2 - 0
src/api/modules/index.js

@@ -13,6 +13,8 @@ export * from './file'
 export * from './customer'
 export * from './dataExport'
 export * from './marketing'
+export * from './medicalField'
+export * from './medical'
 export * from './workspace'
 export * from './entbase'
 export * from './public'

+ 5 - 8
src/api/modules/marketing.js

@@ -1,8 +1,5 @@
 import request from '@/api'
-let jyApi = ''
-if (process.env.NODE_ENV === 'production') {
-  jyApi = '/jyapi'
-}
+
 /**
  * 轻量化营销服务
  */
@@ -10,7 +7,7 @@ if (process.env.NODE_ENV === 'production') {
 // 获取服务端当前时间戳
 export function getServerInitTime () {
   return request({
-    baseURL: jyApi + '/marketing',
+    baseURL: '/jyapi/marketing',
     url: '/time/now?t=' + Date.now(),
     method: 'post'
   })
@@ -19,7 +16,7 @@ export function getServerInitTime () {
 // 预约
 export function appointmentAdd (data) {
   return request({
-    baseURL: jyApi + '/marketing',
+    baseURL: '/jyapi/marketing',
     url: '/appointment/add',
     method: 'post',
     data
@@ -29,7 +26,7 @@ export function appointmentAdd (data) {
 // 预热信息
 export function getAppointmentInfo (data) {
   return request({
-    baseURL: jyApi + '/marketing',
+    baseURL: '/jyapi/marketing',
     url: '/appointment/info',
     method: 'post',
     data
@@ -39,7 +36,7 @@ export function getAppointmentInfo (data) {
 // 是否已预约
 export function getIsAppointment (data) {
   return request({
-    baseURL: jyApi + '/marketing',
+    baseURL: '/jyapi/marketing',
     url: '/appointment/isAppointment',
     method: 'post',
     data

+ 71 - 0
src/api/modules/medical.js

@@ -0,0 +1,71 @@
+import request from '@/api'
+
+// 资源查询
+export function GetResourceSurplus (headers) {
+  return request({
+    url: '/resourceCenter/surplus',
+    baseURL: '/jyapi',
+    headers: headers,
+    method: 'post'
+  })
+}
+
+// 查询我关注的医疗机构
+export function followInstitutionList (data) {
+  return request({
+    url: '/domain/claim/institution',
+    baseURL: '/jyapi',
+    method: 'POST',
+    data
+  })
+}
+
+// 查询我关注的经销商
+export function followDistributorList (data) {
+  return request({
+    url: '/domain/claim/distributor',
+    baseURL: '/jyapi',
+    method: 'POST',
+    data
+  })
+}
+
+// 医疗机构画像-基本信息
+export function getInstitution (data) {
+  return request({
+    url: '/domain/portrait/institution',
+    baseURL: '/jyapi',
+    method: 'post',
+    data: data
+  })
+}
+
+// 医疗机构-最新标讯信息
+export function getMeNewMsgList (data) {
+  return request({
+    url: '/domain/portrait/getNewMsgList',
+    baseURL: '/jyapi',
+    method: 'post',
+    data: data
+  })
+}
+
+// 医疗机构画像-是否认领
+export function isClaimed (data) {
+  return request({
+    url: '/domain/isClaimed',
+    baseURL: '/jyapi',
+    method: 'post',
+    data: data
+  })
+}
+
+// 医疗机构标讯信息导出
+export function getPortrayalMedicalExportId (data) {
+  return request({
+    url: '/domain/portrait/newMsgListExport',
+    baseURL: '/jyapi',
+    method: 'post',
+    data: data
+  })
+}

+ 92 - 0
src/api/modules/medicalField.js

@@ -0,0 +1,92 @@
+import request from '@/api'
+import qs from 'qs'
+
+// 用户认证信息保存
+export function domainUsersave (data) {
+  return request({
+    url: '/domain/userAuthInfoSave',
+    baseURL: '/jyapi',
+    method: 'post',
+    data
+  })
+}
+
+// 获取用户认证信息
+export function domainUserauthinfo (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/domain/userAuthInfo',
+    baseURL: '/jyapi',
+    method: 'post',
+    data
+  })
+}
+
+// 经销商列表
+export function getDistributorList (data) {
+  return request({
+    baseURL: '/jyapi/domain',
+    url: '/search/distributor',
+    method: 'post',
+    data
+  })
+}
+
+// 获取医疗机构筛选条件
+export function getMedicalFilter () {
+  return request({
+    baseURL: '/jyapi/domain',
+    url: '/getFilterItem',
+    method: 'post'
+  })
+}
+
+// 筛选医疗机构列表
+export function getSearchMedicalList (data) {
+  return request({
+    baseURL: '/jyapi',
+    url: '/domain/search/institution',
+    method: 'post',
+    data
+  })
+}
+
+// 经销商认领
+export function distributorClaim (data) {
+  return request({
+    baseURL: '/jyapi/domain',
+    url: '/distributor/claim',
+    method: 'post',
+    data
+  })
+}
+
+// 经销商取消认领
+export function distributorUnClaimed (data) {
+  return request({
+    baseURL: '/jyapi/domain',
+    url: '/distributor/unclaimed',
+    method: 'post',
+    data
+  })
+}
+
+// 机构认领
+export function setInstitutionClaim (data) {
+  return request({
+    baseURL: '/jyapi/domain',
+    url: '/institution/claim',
+    method: 'post',
+    data
+  })
+}
+
+// 机构取消认领
+export function institutionUnClaimed (data) {
+  return request({
+    baseURL: '/jyapi/domain',
+    url: '/institution/unclaimed',
+    method: 'post',
+    data
+  })
+}

+ 20 - 0
src/api/modules/subscribe.js

@@ -122,3 +122,23 @@ export function getPushCount (data) {
     data: data
   })
 }
+
+// 超级订阅订阅设置-推送设置
+export function getPushSetDetail (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/pushset/detail',
+    method: 'post',
+    data: data
+  })
+}
+
+// 超级订阅订阅设置-修改订阅设置(全量提交,新增接口)
+export function updateVipSubscribe (data) {
+  return request({
+    baseURL: '/jyapi/jybx',
+    url: '/subscribe/vType/update',
+    method: 'post',
+    data: data
+  })
+}

+ 1 - 1
src/api/modules/svip.js

@@ -15,7 +15,7 @@ export function getSVIPBuyInfo (data) {
   } else {
     return request({
       baseURL: '/subscribepay',
-      url: '/vipsubscribe/getSubBuyMsg',
+      url: '/vipsubscribe/getSubBuyMsg?t=' + Date.now(),
       method: 'POST'
     })
   }

BIN
src/assets/fonts/iconfont.ttf


BIN
src/assets/fonts/iconfont.woff


BIN
src/assets/fonts/iconfont.woff2


BIN
src/assets/images/hospital@3x.png


BIN
src/assets/images/icon/icon-menu.png


BIN
src/assets/images/icon/renling.png


+ 28 - 0
src/assets/js/selector.js

@@ -519,3 +519,31 @@ export const branchJson = [
   '渠道',
   '其他'
 ]
+
+// 医疗机构经营性质
+export const hospitalNatureExp = [
+  {
+    name: '全部',
+    code: '0',
+    level: '0',
+    children: []
+  },
+  {
+    name: '公立',
+    code: '1',
+    level: '1',
+    children: []
+  },
+  {
+    name: '民营',
+    code: '2',
+    level: '1',
+    children: []
+  },
+  {
+    name: '其它',
+    code: '3',
+    level: '1',
+    children: []
+  }
+]

+ 403 - 71
src/assets/style/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 624651 */
-  src: url('../fonts/iconfont.woff2?t=1634261673232') format('woff2'),
-      url('../fonts/iconfont.woff?t=1634261673232') format('woff'),
-      url('../fonts/iconfont.ttf?t=1634261673232') format('truetype');
+  src: url('../fonts/iconfont.woff2?t=1661408654061') format('woff2'),
+       url('../fonts/iconfont.woff?t=1661408654061') format('woff'),
+       url('../fonts/iconfont.ttf?t=1661408654061') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,406 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-a-Property1gongzuozhuomianProperty2wodezichan:before {
+  content: "\e628";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2jigouguanli:before {
+  content: "\e629";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2jiaoyiguanli:before {
+  content: "\e62f";
+}
+
+.icon-a-Property1Default:before {
+  content: "\e621";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2wenku:before {
+  content: "\e622";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2biaoshuzhizuo:before {
+  content: "\e624";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2kechengpeixun:before {
+  content: "\e627";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2huaxiangfenxi:before {
+  content: "\e60a";
+}
+
+.icon-a-yiliaolingyuhuablack:before {
+  content: "\e610";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2juecefenxi:before {
+  content: "\e612";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2shujukanban:before {
+  content: "\e618";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2shichangfenxi:before {
+  content: "\e619";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2fuwushangzhuanxiang:before {
+  content: "\e608";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2shangjiwajue:before {
+  content: "\e609";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2shujushichang:before {
+  content: "\e607";
+}
+
+.icon-a-Property1gongzuozhuomianProperty2xiaoxizhongxinProperty3grey:before {
+  content: "\e606";
+}
+
+.icon-a-Property1fuwuProperty2grey:before {
+  content: "\e605";
+}
+
+.icon-a-lingyuhuagray:before {
+  content: "\e603";
+}
+
+.icon-yiban:before {
+  content: "\e6de";
+}
+
+.icon-bumanyi-cai:before {
+  content: "\e6df";
+}
+
+.icon-yiban-cai:before {
+  content: "\e6e0";
+}
+
+.icon-manyi-cai:before {
+  content: "\e6e1";
+}
+
+.icon-henmanyi-cai:before {
+  content: "\e6e2";
+}
+
+.icon-henbumanyi-cai:before {
+  content: "\e6e3";
+}
+
+.icon-bumanyi:before {
+  content: "\e6da";
+}
+
+.icon-manyi:before {
+  content: "\e6db";
+}
+
+.icon-henmanyi:before {
+  content: "\e6dc";
+}
+
+.icon-henbumanyi:before {
+  content: "\e6dd";
+}
+
+.icon-green:before {
+  content: "\e6d9";
+}
+
+.icon-fangda:before {
+  content: "\e6d6";
+}
+
+.icon-zanting:before {
+  content: "\e6d7";
+}
+
+.icon-bofang:before {
+  content: "\e6d8";
+}
+
+.icon-huo:before {
+  content: "\e6d5";
+}
+
+.icon-wenku_excel:before {
+  content: "\e6d1";
+}
+
+.icon-wenku_word:before {
+  content: "\e6d2";
+}
+
+.icon-wenku_pdf:before {
+  content: "\e6d3";
+}
+
+.icon-wenku_ppt:before {
+  content: "\e6d4";
+}
+
+.icon-houtui:before {
+  content: "\e6d0";
+}
+
+.icon-danxuan_yixuan:before {
+  content: "\e6cf";
+}
+
+.icon-jiaru_hover:before {
+  content: "\e6cd";
+}
+
+.icon-tuihui_hover:before {
+  content: "\e6ce";
+}
+
+.icon-jiaru:before {
+  content: "\e6cb";
+}
+
+.icon-tuihui:before {
+  content: "\e6cc";
+}
+
+.icon-Setting:before {
+  content: "\e6c7";
+}
+
+.icon-xiala:before {
+  content: "\e6c6";
+}
+
+.icon-dizhi:before {
+  content: "\e6c5";
+}
+
+.icon-danweileixing:before {
+  content: "\e6c4";
+}
+
+.icon-chakanhuaxiang:before {
+  content: "\e6c3";
+}
+
+.icon-a-kaiguanon:before {
+  content: "\e6c2";
+}
+
+.icon-a-kaiguanoff:before {
+  content: "\e6c1";
+}
+
+.icon-shujudaochu:before {
+  content: "\e6be";
+}
+
+.icon-biaoge:before {
+  content: "\e6bf";
+}
+
+.icon-liebiao:before {
+  content: "\e6c0";
+}
+
+.icon-telphone_mian:before {
+  content: "\e6bb";
+}
+
+.icon-weixin_miam:before {
+  content: "\e6bc";
+}
+
+.icon-telphone_line:before {
+  content: "\e6bd";
+}
+
+.icon-weixin_line:before {
+  content: "\e6ba";
+}
+
+.icon-close:before {
+  content: "\e6b9";
+}
+
+.icon-search:before {
+  content: "\e6b8";
+}
+
+.icon-shouqikuang:before {
+  content: "\e6b6";
+}
+
+.icon-zhankaikuang:before {
+  content: "\e6b7";
+}
+
+.icon-time:before {
+  content: "\e69f";
+}
+
+.icon-qiehuan:before {
+  content: "\e6b5";
+}
+
+.icon-guanzhu_yiguanzhu:before {
+  content: "\e67d";
+}
+
+.icon-guanzhu_jiaguanzhu:before {
+  content: "\e67e";
+}
+
+.icon-guanzhu_weiguanzhu:before {
+  content: "\e67f";
+}
+
+.icon-renling_yirenling:before {
+  content: "\e672";
+}
+
+.icon-renling_weirenling:before {
+  content: "\e673";
+}
+
+.icon-shoucang:before {
+  content: "\e684";
+}
+
+.icon-shoucang_weishoucang:before {
+  content: "\e685";
+}
+
+.icon-top:before {
+  content: "\e6b4";
+}
+
+.icon-code:before {
+  content: "\e6a4";
+}
+
+.icon-byEmail:before {
+  content: "\e6a5";
+}
+
+.icon-date:before {
+  content: "\e6a6";
+}
+
+.icon-shouqi2:before {
+  content: "\e6a7";
+}
+
+.icon-delete:before {
+  content: "\e6a8";
+}
+
+.icon-jiangxu:before {
+  content: "\e6a9";
+}
+
+.icon-more:before {
+  content: "\e6aa";
+}
+
+.icon-name:before {
+  content: "\e6ab";
+}
+
+.icon-shengxu:before {
+  content: "\e6ac";
+}
+
+.icon-zhankai:before {
+  content: "\e6ad";
+}
+
+.icon-phone:before {
+  content: "\e6ae";
+}
+
+.icon-password:before {
+  content: "\e6af";
+}
+
+.icon-help:before {
+  content: "\e6b0";
+}
+
+.icon-edit:before {
+  content: "\e6b1";
+}
+
+.icon-zhengyan:before {
+  content: "\e6b2";
+}
+
+.icon-company:before {
+  content: "\e6b3";
+}
+
+.icon-kefu_xian:before {
+  content: "\e6a2";
+}
+
+.icon-kefu_mian:before {
+  content: "\e6a3";
+}
+
+.icon-hui10:before {
+  content: "\e65f";
+}
+
+.icon-hui9:before {
+  content: "\e65e";
+}
+
+.icon-hui8:before {
+  content: "\e65b";
+}
+
+.icon-a-Property1xiazaixiangmubaogaoProperty2nor:before {
+  content: "\e65a";
+}
+
+.icon-duihao:before {
+  content: "\e658";
+}
+
+.icon-box:before {
+  content: "\e6c8";
+}
+
+.icon-home:before {
+  content: "\e6c9";
+}
+
+.icon-book:before {
+  content: "\e6ca";
+}
+
+.icon-shouqi1:before {
+  content: "\e657";
+}
+
+.icon-a-zhankai1:before {
+  content: "\e656";
+}
+
+.icon-a-Frame380:before {
+  content: "\e655";
+}
+
 .icon-hui7:before {
   content: "\e654";
 }
@@ -57,10 +457,6 @@
   content: "\e63c";
 }
 
-.icon-xiazai:before {
-  content: "\e63b";
-}
-
 .icon-logo:before {
   content: "\e639";
 }
@@ -69,22 +465,10 @@
   content: "\e63a";
 }
 
-.icon-youxiang:before {
-  content: "\e6fb";
-}
-
-.icon-shouji:before {
-  content: "\e638";
-}
-
 .icon-zhifuwancheng:before {
   content: "\e637";
 }
 
-.icon-shaixuan-moren:before {
-  content: "\e636";
-}
-
 .icon-76:before {
   content: "\e686";
 }
@@ -101,10 +485,6 @@
   content: "\e634";
 }
 
-.icon-rili:before {
-  content: "\e603";
-}
-
 .icon-huangguan:before {
   content: "\e604";
 }
@@ -113,10 +493,6 @@
   content: "\e631";
 }
 
-.icon-shujudaochu:before {
-  content: "\e630";
-}
-
 .icon-yixuan:before {
   content: "\e62d";
 }
@@ -125,10 +501,6 @@
   content: "\e62e";
 }
 
-.icon-shanchu:before {
-  content: "\e62f";
-}
-
 .icon-zishangxianyilai:before {
   content: "\e62a";
 }
@@ -141,10 +513,6 @@
   content: "\e62c";
 }
 
-.icon-guanjianci:before {
-  content: "\e612";
-}
-
 .icon-hangye:before {
   content: "\e613";
 }
@@ -165,14 +533,6 @@
   content: "\e617";
 }
 
-.icon-guanbi:before {
-  content: "\e618";
-}
-
-.icon-zhaobiaosousuo:before {
-  content: "\e619";
-}
-
 .icon-shujuguizeziyoudingyi:before {
   content: "\e61a";
 }
@@ -201,22 +561,10 @@
   content: "\e620";
 }
 
-.icon-biaoge:before {
-  content: "\e621";
-}
-
-.icon-shijian:before {
-  content: "\e622";
-}
-
 .icon-shouqi:before {
   content: "\e623";
 }
 
-.icon-liebiao:before {
-  content: "\e624";
-}
-
 .icon-gengduo:before {
   content: "\e625";
 }
@@ -225,18 +573,6 @@
   content: "\e626";
 }
 
-.icon-bangzhu:before {
-  content: "\e627";
-}
-
-.icon-shengfenchengshi:before {
-  content: "\e628";
-}
-
-.icon-qiyemingcheng:before {
-  content: "\e629";
-}
-
 .icon-zhidingarrow:before {
   content: "\e60b";
 }
@@ -257,10 +593,6 @@
   content: "\e60f";
 }
 
-.icon-sousuo:before {
-  content: "\e610";
-}
-
 .icon-weixin:before {
   content: "\e611";
 }

+ 6 - 0
src/components/article-item/ArticleItem.vue

@@ -160,10 +160,16 @@ export default {
         background-size: contain;
         cursor: pointer;
         vertical-align: sub;
+        ::before{
+          content: ''!important;
+        }
       }
       .icon-collect.checked{
         background: transparent url(https://cdn-ali.jianyu360.com/images/collected.png) center no-repeat;
         background-size: contain;
+        ::before{
+          content: ''!important;
+        }
       }
 
       .tag,

+ 4 - 2
src/components/collect-info/CollectInfo.vue

@@ -196,7 +196,7 @@ export default {
     [Input.name]: Input,
     [Option.name]: Option,
     [Cascader.name]: Cascader
-   
+
   },
   data () {
     var validName = (rule, value, callback) => {
@@ -1022,8 +1022,10 @@ export default {
         height: 15px;
         background: url('../../assets/images/icon/icon-warning1.png') no-repeat;
         background-size: contain;
-
+        ::before{
+          content: ''!important;
         }
+    }
     .warm-text{
       // text-align: center;
       line-height: 30px;

+ 165 - 0
src/components/common/Dialog.vue

@@ -0,0 +1,165 @@
+<template>
+  <div class="common-dialog">
+    <el-dialog
+      :custom-class="setClass"
+      :title="title"
+      :visible.sync="centerDialogVisible"
+      :width="width"
+      :show-close="showClose"
+      :fullscreen="fullscreen"
+      :append-to-body="appendToBody"
+      @before-close="beforeClose"
+      @close="setCancel"
+      :center="center"
+      >
+      <slot></slot>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="setConfirm">{{confirmText}}</el-button>
+        <el-button @click="setCancel">{{cancelText}}</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { Dialog, Button } from 'element-ui'
+export default {
+  name: 'common-dialog',
+  components: {
+    [Dialog.name]: Dialog,
+    [Button.name]: Button
+  },
+  props: {
+    // 是否显示弹窗
+    centerDialogVisible: {
+      type: Boolean,
+      default: true
+    },
+    // 标题:标题为空则不显示头部标签
+    title: {
+      type: String,
+      default: ''
+    },
+    // 弹窗类名
+    customClass: {
+      type: String,
+      default: ''
+    },
+    // 是否占满屏幕
+    fullscreen: {
+      type: Boolean,
+      default: false
+    },
+    // 是否插入Body
+    appendToBody: {
+      type: Boolean,
+      default: false
+    },
+    // 是否对头部和底部采用居中布局
+    center: {
+      type: Boolean,
+      default: true
+    },
+    // 宽度
+    width: {
+      type: String,
+      default: '30%'
+    },
+    // 确认按钮文案
+    confirmText: {
+      type: String,
+      default: '确 定'
+    },
+    // 取消按钮文案
+    cancelText: {
+      type: String,
+      default: '取 消'
+    },
+    // 是否显示关闭按钮
+    showClose: {
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {
+    setClass () {
+      if (this.title === '') {
+        return `${this.customClass} noHeader`
+      } else {
+        return this.customClass
+      }
+    }
+  },
+  data () {
+    return {
+    }
+  },
+  methods: {
+    // 确认
+    setConfirm () {
+      this.$emit('setConfirm')
+    },
+    // 取消,关闭弹窗
+    setCancel () {
+      this.$emit('setCancel')
+    },
+    // 关闭前的回调, 暂停关闭
+    beforeClose () {
+      this.$emit('beforeClose')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.common-dialog{
+  width: 100%;
+  height: 100%;
+  ::v-deep .el-dialog__wrapper{
+    // position: inherit;
+    .el-dialog__header{
+      padding: 0 20px 20px;
+    }
+    .noHeader{
+      .el-dialog__header{
+        display: none;
+      }
+    }
+    .el-dialog{
+      width: 380px!important;
+      padding: 32px;
+      background: #FFFFFF;
+      border-radius: 8px;
+      .el-dialog__body{
+        padding: 0;
+        font-size: 14px;
+        line-height: 22px;
+      }
+      .el-dialog__footer{
+        margin-top: 32px;
+        padding: 0;
+        .dialog-footer{
+          display: flex;
+          justify-content: space-between;
+        }
+        .el-button {
+          padding: 0;
+          width: 132px;
+          height: 36px;
+          background: #2ABED1;
+          border-radius: 6px;
+          font-size: 16px;
+          font-weight: 400;
+          color: #FFFFFF;
+          line-height: 36px;
+          &.el-button--default{
+            border: 1px solid #E0E0E0;
+            color: #686868;
+            background: #FFFFFF;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 3 - 0
src/components/common/SpecCard.vue

@@ -87,6 +87,9 @@ export default {
       margin-right: 4px;
       background: url('~@/assets/images/icon/icon-crown.png') no-repeat center;
       background-size: cover;
+      ::before{
+        content: ''!important;
+      }
     }
   }
 }

+ 87 - 0
src/components/common/informationSuccess.vue

@@ -0,0 +1,87 @@
+<template>
+    <el-dialog
+      title="温馨提示"
+      :visible.sync="passVisible"
+      width="380px"
+      :show-close="false"
+      :lock-scroll="false"
+      custom-class="no-pass-alert"
+      :close-on-click-modal="false"
+      center>
+      <!-- <span>恭喜留资成功,您可拨打客服电话 400-108-6670 或在线<i @click="openCustomer">联系客服</i>开通,谢谢!</span> -->
+      <span>恭喜留资成功,客户经理将在工作日24h内联系您,谢谢!</span>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="know">我知道了</el-button>
+      </span>
+    </el-dialog>
+  </template>
+  
+  <script>
+  import { Dialog, Button } from 'element-ui'
+  export default {
+    components: {
+      [Dialog.name]: Dialog,
+      [Button.name]: Button
+    },
+    data () {
+      return {
+        passVisible: false
+      }
+    },
+    mounted () {
+      window.openCustomer = this.openCustomer
+    },
+    methods: {
+      openCustomer () {
+        this.contactCustomer(this)
+      },
+      know(){
+        this.passVisible=false
+        this.$emit('know') //点击我知道了回调
+      }
+    }
+  }
+  </script>
+  
+  <style lang="scss" scoped>
+  ::v-deep .no-pass-alert {
+    padding: 0;
+    padding: 32px;
+    .el-dialog__header {
+      padding: 0;
+      .el-dialog__title {
+        font-size: 18px;
+        color: $color-input--default;
+        line-height: 28px;
+      }
+    }
+    .el-dialog__body {
+      text-align: center;
+      padding: 0;
+      padding: 20px 0 32px 0;
+      span {
+        font-size: 14px;
+        color: #686868;
+        line-height: 22px;
+        i {
+          color: $color_main;
+          cursor: pointer;
+        }
+      }
+    }
+    .el-dialog__footer {
+      padding: 0;
+      .el-button--primary {
+        background: $color_main;
+        padding: 8px 34px;
+        border: none;
+        span {
+          font-size: 16px;
+          line-height: 20px;
+          color: #fff;
+        }
+      }
+    }
+  }
+  </style>
+  

+ 152 - 0
src/components/drawer/Drawer.vue

@@ -0,0 +1,152 @@
+<template>
+  <div class="drawer-dialog">
+    <el-drawer
+      :custom-class="customClass"
+      :show-close="showClose"
+      :append-to-body="false"
+      :visible.sync="drawerFlag"
+      :direction="direction"
+      :size="percent"
+      :withHeader="withHeader"
+      @before-close="beforeClose"
+      @close="close"
+      >
+      <div class="el-container">
+        <div class="el-header">
+          <div slot="title">{{ title }}</div>
+        </div>
+        <div class="el-main">
+          <slot></slot>
+        </div>
+        <div class="el-footer">
+          <el-button class="el-btn el-save" @click="saveData">{{ confirmText }}</el-button>
+          <el-button class="el-btn el-cancel" @click="close">{{ cancelText }}</el-button>
+        </div>
+      </div>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import { Drawer, Button } from 'element-ui'
+export default {
+  name: 'drawer-dialog',
+  components: {
+    [Drawer.name]: Drawer,
+    [Button.name]: Button
+  },
+  props: {
+    showDrawer: {
+      type: Boolean,
+      default: false
+    },
+    customClass: {
+      type: String,
+      default: ''
+    },
+    confirmText: {
+      type: String,
+      default: '保存设置'
+    },
+    cancelText: {
+      type: String,
+      default: '取消'
+    },
+    percent: {
+      type: String,
+      default: '42%'
+    },
+    direction: {
+      type: String,
+      default () {
+        return 'rtl'
+      }
+    },
+    // 控制是否显示 header 栏, 默认为 true, 当此项为 false 时, title attribute 和 title slot 均不生效
+    withHeader: {
+      type: Boolean,
+      default: true
+    },
+    title: {
+      type: String,
+      default: ''
+    },
+    showClose: {
+      type: Boolean,
+      default: false
+    }
+  },
+  model: {
+    prop: 'showDrawer',
+    event: 'change'
+  },
+  data () {
+    return {
+      drawerFlag: this.showDrawer
+    }
+  },
+  watch: {
+    showDrawer (val) {
+      this.drawerFlag = val
+    }
+  },
+  methods: {
+    saveData () {
+      this.$emit('saveData')
+    },
+    // 关闭回调
+    close () {
+      this.drawerFlag = false
+      this.$emit('close', this.drawerFlag)
+    },
+    // 关闭前的回调,会暂停 Drawer 的关闭
+    beforeClose () {
+      this.$emit('beforeClose')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.drawer-dialog{
+
+}
+::v-deep .el-drawer__body{
+  height: 100%;
+  .el-container{
+    display: flex;
+    justify-content: space-between;
+    flex-direction: column;
+    height: 100%;
+  }
+  .el-main{
+    flex: 1;
+    overflow-y: scroll;
+    padding: 0!important;
+  }
+  .el-footer {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 100%;
+    width: -moz-available;
+    width: -webkit-fill-available;
+    height: 72px;
+    background: #FFFFFF;
+    box-shadow: 0px -8px 8px 0px rgba(0,0,0,0.05);
+    .el-btn{
+      padding: 7px 34px;
+      min-width: 132px;
+      height: 36px;
+      border-radius: 6px;
+      border: 1px solid #2CB7CA;
+      color: #2CB7CA;
+      &.el-cancel {
+        margin-left: 40px;
+        color: #686868;
+        border: 1px solid #E0E0E0;
+      }
+    }
+  }
+}
+</style>

+ 3 - 0
src/components/forecast/ForeCast.vue

@@ -798,6 +798,9 @@ export default {
       background: url('~@/assets/images/icon/help.png') no-repeat center center;
       background-size: contain;
       cursor: pointer;
+      ::before{
+        content: ''!important;
+      }
     }
   }
   .list_li_item{

+ 6 - 0
src/components/home/FloatSide.vue

@@ -142,6 +142,9 @@ export default {
       background: #b4b4bf;
       border-radius: 50%;
       margin-left: 8px;
+      ::before{
+        content: ''!important;
+      }
     }
 
     .top-group {
@@ -180,6 +183,9 @@ export default {
       border: 1px solid #ffffff;
       border-radius: 50%;
       margin-left: 4px;
+      ::before{
+        content: ''!important;
+      }
     }
     .color {
       &--gray {

+ 74 - 0
src/components/input/BaseInput.vue

@@ -0,0 +1,74 @@
+<template>
+  <div class="search-input">
+    <el-input
+      :placeholder="placeholder"
+      :value="value"
+      @input="onInput($event)"
+      @blur="$emit('onBlur')"
+      @focus="$emit('onFocus')"
+      @clear="$emit('onClear', value)"
+      clearable>
+    </el-input>
+  </div>
+</template>
+
+<script>
+import { Input } from 'element-ui'
+
+export default {
+  name: 'Search-Input',
+  props: {
+    placeholder: String,
+    value: String
+  },
+  components: {
+    [Input.name]: Input
+  },
+  model: {
+    prop: 'value',
+    event: 'onInput'
+  },
+  data () {
+    return {}
+  },
+  methods: {
+    onInput (e) {
+      this.$emit('onInput', e)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+  .search-input {
+    position: relative;
+    width: 640px;
+    @include diy-icon('search-white', 24);
+
+    ::v-deep .el-input__inner {
+      border-radius: 4px;
+      background: #FFFFFF;
+      border: 1px solid #E0E0E0;
+      padding: 0 16px;
+      color: #1D1D1D;
+      font-family: Microsoft YaHei;
+      font-size: 14px;
+      line-height: 24px;
+      height: 36px;
+    }
+
+    ::v-deep .el-input-group__append {
+      height: 42px;
+      box-sizing: border-box;
+      text-align: center;
+      border: none;
+      border-radius: 0px 22px 22px 0px;
+      background: #2CB7CA;
+
+      .el-button {
+        padding: 8px 19px;
+        margin-top: -4px;
+      }
+    }
+  }
+</style>

+ 62 - 0
src/components/list-item/listItem.vue

@@ -0,0 +1,62 @@
+<template>
+  <div class="list-item">
+    <div v-show="!item.remove" class="flex-r-c center sb card-list-item">
+      <div class="flex-c-c left">
+        <div class="flex-r-c center sb">
+          <div class="flex-r-c center">
+            <i class="el-icon-jy-icon-company"></i>
+            <span class="text--title">{{item.Buyer}}</span>
+          </div>
+        </div>
+        <div class="flex-r-c card-bottom-info">
+          <span class="text--sm-name">项目数量:</span>
+          <span class="text--sm-value">{{ item.PNCount }}</span>
+          <span class="text--sm-name">项目总金额:</span>
+          <span class="text--sm-value">{{ item.Budget }}</span>
+          <span class="text--sm-name">所在地:</span>
+          <span class="text--sm-value" v-if="item.WProvince || item.WCity">{{item.WProvince}}&nbsp;&nbsp;{{item.WProvince.slice(0,2) !== item.WCity.slice(0,2) ? item.WCity : ''}}</span>
+          <span class="text--sm-value" v-else>-</span>
+        </div>
+      </div>
+      <!-- <div class="pcor-right-group flex-c-c right" v-if="filters.pcor === 'R'">
+        <div class="flex-r-c center" @click.stop="changeFollow(item)">
+          <i :class="'el-icon-jy-heart_' + (item.follow ? 'solid' : 'stroke')"></i>
+          <span>{{item.follow ? '取消' : ''}}关注</span>
+        </div>
+      </div>
+      <div class="pcor-right-group flex-c-c right" v-else-if="filters.pcor === 'C'">
+        <div class="flex-r-c center" @click.stop="changeFollow(item)">
+          <i :class="'el-icon-jy-heart_' + (item.follow ? 'solid' : 'stroke')"></i>
+          <span>{{item.follow ? '已' : ''}}关注</span>
+        </div>
+      </div> -->
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'list-item',
+  props: {
+    item: {
+      type: Object,
+      default () {
+        return {}
+      }
+    }
+  },
+  data () {
+    return {
+
+    }
+  },
+  computed: {
+
+  },
+  methods: {}
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 73 - 0
src/components/medical/CommonTab.vue

@@ -0,0 +1,73 @@
+<template>
+  <div class="medical-title-tab">
+    <div class="medical-title-left">
+      <div class="medical-title-main medical-title-item">
+        {{ title }}
+      </div>
+      <div class="medical-title-describe medical-title-item" v-if="$slots.default">
+        <slot></slot>
+      </div>
+    </div>
+    <div class="medical-title-right">
+      <slot name="right"></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'medical-commonTab',
+  props: {
+    title: { // 主标题
+      type: String,
+      default: '标题'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.medical-title-tab {
+  height: 50px;
+  background: #FFFFFF;
+  display: flex;
+  flex-direction: row;
+  padding: 0 14px;
+  align-items: center;
+  justify-content: space-between;
+  border-bottom: 1px solid #ECECEC;
+
+  border-top-left-radius: 8px;
+  border-top-right-radius: 8px;
+
+  .medical-title-item,
+  .medical-title-left,
+  .medical-title-right
+   {
+    display: flex;
+    align-items: center;
+  }
+
+  .medical-title-main {
+    height: 50px;
+    font-size: 16px;
+    font-weight: 400;
+    color: #2CB7CA;
+    line-height: 50px;
+    margin: 0 16px;
+    border-bottom: 2px solid #2CB7CA;
+  }
+
+  .medical-title-describe{
+    background: #F5F6F7;
+    border-radius: 4px 4px 4px 4px;
+    padding: 4px 10px;
+
+    height: 24px;
+    font-size: 12px;
+    font-weight: 400;
+    color: #686868;
+    line-height: 18px;
+  }
+}
+</style>

+ 139 - 0
src/components/medical/FollowFilter.vue

@@ -0,0 +1,139 @@
+<template>
+  <div class="ent-filter">
+    <div class="filter-content">
+      <div class="filter-item">
+        <span class="item-label">{{ this.searchName }}:</span>
+        <div style="display:flex;align-items: center;">
+          <el-input @keydown.enter.native="enterKeydown" class="ent-input" v-model="entVal"
+                    :placeholder='searchPlaceholder'
+                    @clear="clearHandle" clearable @input="onInputHandle"></el-input>
+          <el-button v-show="entVal" @click="searchHandle" class="search-btn">搜索</el-button>
+        </div>
+        <div class="dot-line"></div>
+      </div>
+      <div class="filter-item filter-group-top-dotted" v-if="showGroup">
+        <span class="item-label">分组:</span>
+        <div>
+          <GroupTag selectorType="line" @onChange="getGroupTag"></GroupTag>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Input, Button } from 'element-ui'
+import GroupTag from '@/components/selector/GroupSelectorContent.vue'
+
+export default {
+  name: 'medical-filter',
+  props: {
+    searchName: { // 搜索项名称
+      type: String,
+      default: '搜索'
+    },
+    searchPlaceholder: { // 输入框提示内容
+      type: String,
+      default: '请输入查询内容'
+    },
+    showGroup: { // 是否展示分组标签
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {},
+  components: {
+    [Input.name]: Input,
+    [Button.name]: Button,
+    GroupTag
+  },
+  data () {
+    return {
+      entVal: '',
+      curGroup: ''
+    }
+  },
+  methods: {
+    getGroupTag (data) {
+      this.curGroup = data
+      this.$emit('onPageChange', 0, this.entVal, data)
+    },
+    searchHandle () {
+      this.$emit('onPageChange', 0, this.entVal, this.curGroup)
+    },
+    // 输入框点击清空
+    clearHandle () {
+      this.$emit('onPageChange', 0, '', this.curGroup)
+    },
+    // 回车搜索
+    enterKeydown () {
+      this.searchHandle()
+    },
+    // 输入框手动清空
+    onInputHandle (val) {
+      if (!val) {
+        this.clearHandle()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.ent-filter {
+  margin-bottom: 16px;
+  background: #fff;
+
+  .filter-content {
+    padding: 0px 16px;
+
+    .filter-item {
+      position: relative;
+      display: flex;
+      align-items: center;
+      padding: 18px 0;
+
+      .item-label {
+        text-align: right;
+        width: 98px;
+        height: 18px;
+        font-size: 14px;
+        font-weight: 400;
+        color: #686868;
+        line-height: 16px;
+      }
+
+      .ent-input {
+        ::v-deep {
+          .el-input__inner {
+            width: 432px;
+            height: 36px;
+            line-height: 36px;
+            font-size: 14px;
+          }
+
+          .el-input__icon {
+            line-height: 36px;
+          }
+        }
+      }
+
+      .search-btn {
+        margin-left: 12px;
+        padding: 0 20px;
+        height: 36px;
+        line-height: 36px;
+        font-size: 14px;
+        color: #fff;
+        background-color: $color-text--highlight;
+        border: 0;
+      }
+    }
+
+    .filter-group-top-dotted {
+      border-top: 1px dashed #e4e7ed;
+    }
+
+  }
+}
+</style>

+ 541 - 0
src/components/medical/FollowList.vue

@@ -0,0 +1,541 @@
+<template>
+  <div class="listData">
+    <div>
+      <div class="entintel-thead">
+        <span class="w-900">经销商名称</span>
+        <span class="thead-flex w-100">
+          <em>认领时间</em>
+        </span>
+        <span class="w-100" v-if="false">分组</span>
+        <span class="w-100">操作</span>
+      </div>
+      <ul class="listData_ul">
+        <li class="list_li"
+            :class="{ visited: item.visited }"
+            v-for="(item, index) in getMyData.listState.list"
+            :key="index">
+          <div class="w-900" style="cursor: pointer" @click="goViewEnt(item)">
+            <div class="list_name ent_li_name visited-hd">
+              <i class="company-icon"></i>
+              {{ item.ent_name }}
+              <span class="red_point" v-if="item.i_apppushunread === 1"></span>
+            </div>
+            <div class="list_unit">
+              <div class="pur_unit">
+                <span class="unit_label">成立日期:</span>
+                <span class="unit_name entname visited-ft">
+                  {{ item.establish_date ? item.establish_date : '--' }}
+                </span>
+              </div>
+              <div class="pur_unit">
+                <span class="unit_label">注册资本:</span>
+                <span class="unit_name entname visited-ft">
+                  {{ item.capital ? formatMoneyRange(item.capital) : '--' }}
+                </span>
+              </div>
+              <div class="pur_unit">
+                <span class="unit_label">企业地址:</span>
+                <span class="unit_name entname visited-ft">
+                  {{ item.address ? item.address : '--' }}
+                </span>
+              </div>
+              <div class="pur_unit">
+                <span class="unit_label">企业联系方式:</span>
+                <span class="unit_name entname visited-ft">
+                  {{ item.company_phone ? item.company_phone : '--' }}
+                </span>
+              </div>
+            </div>
+          </div>
+          <div class="list_li_item list_li_gray w-100">
+            {{ item.create_time ? item.create_time : '--' }}
+          </div>
+          <div class="list_li_item item-flex-column list_li_gray w-100" v-if="false">
+            <span v-for="(s,j) in formatGroup(item.s_group)" :key="'00' + j" style="line-height:20px;">{{ s }}</span>
+          </div>
+          <div class="list_li_item entintel-handle w-100">
+            <span @click="editGroup(item)" v-if="false">编辑分组</span>
+            <span @click="cancelFollow(item)">取消认领</span>
+          </div>
+        </li>
+      </ul>
+    </div>
+    <Empty v-show="showEmpty" :images="require('@/assets/images/empty/jy-back.png')">
+      <div name="default">{{ getTipText }}</div>
+    </Empty>
+    <div class="el-pagination-container" v-show="!showEmpty">
+      <el-pagination
+        background
+        layout="prev, pager, next, ->"
+        :hide-on-single-page="true"
+        :current-page="filter.pageNum"
+        :page-size="filter.pageSize"
+        :total="listState.total"
+        @current-change="onPageChange"
+      >
+      </el-pagination>
+    </div>
+    <!-- 分组dialog -->
+    <el-dialog
+      custom-class="sub-dialog"
+      :visible.sync="dialog.group"
+      :close-on-click-modal="false"
+      :show-close="false"
+      :destroy-on-close="true"
+      :lock-scroll="false"
+      center
+      width="460px"
+    >
+      <GroupCard
+        :initGroupInfo="this.cur.group"
+        @onCancel="dialog.group = false"
+        @onConfirm="saveGroupData"
+      >
+        <div slot="header">编辑分组</div>
+      </GroupCard>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { Pagination, Progress, Message, Tooltip, Dialog, Button } from 'element-ui'
+import { mixinVisited } from '@/utils/mixins/visited'
+import Empty from '@/components/common/Empty.vue'
+import GroupCard from '@/components/selector/GroupSelector.vue'
+import { dateFormatter, openSelfLink } from '@/utils'
+import { setFollowEnt, setCancelEnt, changeEntGroup, distributorUnClaimed } from '@/api/modules'
+import { mapState } from 'vuex'
+
+export default {
+  name: 'listData',
+  mixins: [mixinVisited],
+  components: {
+    [Pagination.name]: Pagination,
+    [Progress.name]: Progress,
+    [Message.name]: Message,
+    [Tooltip.name]: Tooltip,
+    [Dialog.name]: Dialog,
+    [Button.name]: Button,
+    Empty,
+    GroupCard
+  },
+  props: {
+    loading: { // 搜索项名称
+      type: Boolean,
+      default: true
+    }
+  },
+  data () {
+    return {
+      filter: {
+        pageNum: 1,
+        match: '',
+        group: '',
+        pageSize: 10
+      },
+      listState: {
+        total: 0, // 一共多少条数据
+        list: [] // 查询请求返回的数据
+      },
+      isFollow: '1',
+      tips: '', // 空状态提示
+      tipimages: require('@/assets/images/empty/jy-cry.png'),
+      dialog: {
+        group: false
+      },
+      cur: {
+        group: '', // 当前点击编辑的分组信息
+        fid: ''
+      }
+    }
+  },
+  created () {
+  },
+  watch: {
+    'myDataObj.list' (newVal) {
+      this.getEntListTips()
+    }
+  },
+  computed: {
+    ...mapState({
+      info: state => state.user.info
+    }),
+    showEmpty () {
+      console.log(this.loading)
+      return !this.listState.list.length && !this.loading
+    },
+    getShowPagination () {
+      let show = true
+      if (this.filter.pageNum === 1 && this.listState.list && this.listState.list.length < this.filter.pageSize) {
+        show = false
+      }
+      return show
+    },
+    getMyData () {
+      // eslint-disable-next-line vue/no-side-effects-in-computed-properties
+      return this
+    },
+    getTipText () {
+      return this.tips
+    }
+  },
+  mounted () {
+    this.getEntListTips()
+  },
+  methods: {
+    dateFormatter,
+    entInitData (res, filter) {
+      this.filter = filter
+      if (res && res.list && res.list.length !== 0) {
+        this.listState.list = res.list
+        this.listState.total = res.total
+      } else {
+        this.listState.list = []
+        this.listState.total = 0
+        if (!this.filter.match && !this.filter.group && res.initTotal === 0) {
+          this.tips = '暂未认领任何经销商'
+        } else {
+          this.tips = '暂无匹配数据'
+        }
+      }
+    },
+    // 处理金额区间转换
+    formatMoneyRange (key) {
+      const num = parseFloat(key) // 单位万元
+      if (!num) return
+      if (num < 1e4) {
+        return num.toFixed(2) + '万'
+      } else if (num > 1e4) {
+        return (num / 1e4).toFixed(2) + '亿'
+      } else if (num > 1e8) {
+        return (num / 1e8).toFixed(2) + '万亿'
+      }
+    },
+    // 关注
+    setFollow (id, num) {
+      if (num === '') {
+        setFollowEnt({ entId: id }).then(res => {
+          if (res.error_code === 0) {
+            if (res.data === 'success') {
+              this.entSearch.forEach(function (item, i) {
+                if (id === item.entId) {
+                  item.isFollow = '1'
+                }
+              })
+            }
+          }
+        })
+      } else {
+        setCancelEnt({ entId: id }).then(res => {
+          if (res.error_code === 0) {
+            if (res.data === 'success') {
+              this.entSearch.forEach(function (item, i) {
+                if (id === item.entId) {
+                  item.isFollow = ''
+                }
+              })
+            }
+          }
+        })
+      }
+    },
+    goViewEnt (item) {
+      if (this.info.power.indexOf(5) > -1) {
+        openSelfLink(this.$router.resolve({
+          path: `/ent_portrait/${item.ent_id}?ismedical=1`
+        }))
+      } else {
+        openSelfLink(this.$router.resolve({
+          path: `/svip/ent_ser_portrait/${item.ent_id}?ismedical=1`
+        }))
+      }
+    },
+    onPageChange (p) {
+      this.filter.pageNum = p
+      this.$emit('onPageChange', p)
+    },
+    // 处理显示分组
+    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
+    },
+    // 企业情报监控 编辑分组
+    editGroup (item) {
+      this.dialog.group = true
+      this.cur.group = item.s_group ? item.s_group : 'A'
+      this.cur.fid = item.fid
+    },
+    // 企业情报监控 取消关注
+    cancelFollow (item) {
+      if (this.listState.list.length <= 1 && this.filter.pageNum > 0) {
+        this.filter.pageNum--
+      }
+
+      distributorUnClaimed({
+        ent_id: item.ent_id
+      }).then(res => {
+        if (res.error_code === 0) {
+          this.$toast('取消认领成功', 1000)
+          this.$emit('onPageChange', this.filter.pageNum)
+          this.$emit('flushResource')
+        } else {
+          this.$toast(res.error_msg, 1500)
+        }
+      })
+    },
+    saveGroupData (data) {
+      const params = {
+        fid: this.cur.fid,
+        group: data
+      }
+      const defaultParams = {
+        fid: this.cur.fid
+      }
+      const num = this.listState.list.length <= 1 ? 0 : this.filter.pageNum
+      // console.log(num, this.listState.list, '1111')
+      changeEntGroup(data === 'A' ? defaultParams : params).then(res => {
+        if (res.data === 'success') {
+          this.dialog.group = false
+          this.$emit('onPageChange', num)
+        } else {
+          this.$toast(res.error_msg)
+        }
+      })
+    },
+    goSearchEnt () {
+      location.href = '/jylab/entSearch/index.html'
+    },
+    getEntListTips () {
+      if (this.myDataObj && !this.filter.match && !this.filter.group && this.myDataObj.initTotal === 0) {
+        this.tips = '暂未认领任何经销商'
+        this.tipimages = require('@/assets/images/empty/jy-back.png')
+      } else if (this.myDataObj && this.myDataObj.list.length === 0 && this.myDataObj.initTotal !== 0) {
+        this.tips = '暂无匹配数据'
+        this.tipimages = require('@/assets/images/empty/jy-back.png')
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.listData {
+  width: 100%;
+  background-color: #fff;
+
+  .w-900 {
+    flex: 9;
+    display: inline-block;
+  }
+
+  .w-100 {
+    flex: 1;
+    display: inline-block;
+  }
+
+  .listData_title {
+    padding-left: 40px;
+    width: 100%;
+    height: 28px;
+    font-size: 18px;
+    font-weight: 400;
+    text-align: LEFT;
+    color: #1d1d1d;
+    line-height: 28px;
+  }
+
+  .entintel-thead {
+    display: flex;
+    align-items: center;
+    height: 48px;
+    background: #f7f9fc;
+    color: #1D1D1D;
+    text-align: center;
+    font-size: 14px;
+    padding: 0 20px;
+
+    .thead-flex {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .icon-quesion {
+      display: inline-block;
+      width: 18px;
+      height: 18px;
+      margin-left: 2px;
+      background: url('~@/assets/images/icon/help.png') no-repeat center center;
+      background-size: contain;
+      cursor: pointer;
+
+      ::before {
+        content: ''!important;
+      }
+    }
+  }
+
+  .list_li_item {
+    width: 100px;
+    text-align: center;
+    font-size: 14px;
+
+    &.item-flex-column {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+    }
+  }
+
+  .list_li_gray {
+    color: #999999;
+  }
+
+  .add-btn {
+    margin: 16px 0;
+    background: #2cb7ca;
+    border-radius: 6px;
+    border: 0;
+  }
+
+  .entintel-handle {
+    width: 100px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    font-size: 14px;
+    color: #2CB7CA;
+    line-height: 22px;
+
+    span {
+      cursor: pointer
+    }
+  }
+
+  .listData_ul {
+    content: '';
+    overflow: hidden;
+    width: 100%;
+
+    .list_li {
+      padding: 24px;
+      width: 100%;
+      box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.05) inset;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+
+      .company-icon {
+        display: inline-block;
+        width: 20px;
+        height: 20px;
+        margin-right: 4px;
+        background: url('~@/assets/images/icon/icon-company.png') no-repeat center center;
+        background-size: contain;
+      }
+
+      .list_name {
+        font-size: 16px;
+        font-weight: 400;
+        text-align: LEFT;
+        color: #1d1d1d;
+        line-height: 24px;
+      }
+
+      .ent_li_name {
+        display: flex;
+        align-items: center;
+
+        .red_point {
+          display: flex;
+          margin-left: 4px;
+          width: 8px;
+          height: 8px;
+          background: #fb483d;
+          border-radius: 50%;
+        }
+      }
+
+      .list_unit {
+        display: flex;
+        margin-top: 12px;
+
+        .pur_unit {
+          margin-right: 28px;
+          font-size: 14px;
+          color: #2CB7CA;
+          font-weight: 400;
+
+          .unit_label {
+            color: #999999;
+          }
+
+          .entname {
+            color: #1d1d1d;
+            text-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.05) inset;
+          }
+        }
+      }
+
+      &:hover {
+        background: #f5f6f7;
+        box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.05) inset;
+
+        .list_name {
+          color: #2CB7CA;
+        }
+
+      }
+    }
+  }
+
+
+  .poten_tip {
+    text-align: center;
+  }
+
+  .setKeyWords {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-top: 40px;
+    width: 100%;
+    height: 46px;
+
+    .setBtn {
+      width: 352px;
+      height: 46px;
+      background: #2cb7ca;
+      border-radius: 6px;
+      font-size: 16px;
+      font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+      font-weight: 400;
+      text-align: CENTER;
+      color: #ffffff;
+    }
+  }
+
+  .el-pagination-container {
+    margin-top: 40px;
+  }
+}
+</style>

+ 492 - 0
src/components/push-list/DistributorSearch.vue

@@ -0,0 +1,492 @@
+<template>
+  <el-card class="potential-list-card">
+    <div class="header-box flex-r-c sb">
+      <span class="card-title">{{title}}<span class="header-hengxian-hover"></span></span>
+      <div class="flex-r-c center" v-show="!showEmpty">
+        <div class="link-text" @click="changeSort(0)" :class="{active: listState.sort === 0}">数量倒序</div>
+        <div class="link-text" @click="changeSort(1)" :class="{active: listState.sort === 1}">金额倒序</div>
+      </div>
+    </div>
+    <div class="header-hengxian"></div>
+    <div class="info-list" v-loading="listState.loading">
+      <div
+        v-for="(item, index) in getListData"
+        :key="index"
+        @click="$emit('goDetail', item)">
+        <div v-show="!item.remove" class="flex-r-c center sb card-list-item">
+          <div class="flex-c-c left">
+            <div class="flex-r-c center sb">
+              <div class="flex-r-c center">
+                <i class="el-icon-jy-icon-company"></i>
+                <span class="text--title">{{item.company_name}}</span>
+              </div>
+            </div>
+            <div class="flex-r-c card-bottom-info">
+              <span class="text--sm-name">项目数量:</span>
+              <span class="text--sm-value">{{ item.project_count == undefined ? 0 : item.project_count }}</span>
+              <span class="text--sm-name">项目总金额:</span>
+              <span class="text--sm-value">{{ item.project_money == undefined ? '-' : item.project_money }}</span>
+              <span class="text--sm-name">所在地:</span>
+              <span class="text--sm-value" v-if="item.area || item.city">{{item.area}}&nbsp;&nbsp;{{item.area && item.city && item.area.slice(0,2) !== item.city.slice(0,2) ? item.city : ''}}</span>
+              <span class="text--sm-value" v-else>-</span>
+            </div>
+          </div>
+          <div class="pcor-right-group flex-c-c right">
+            <div class="flex-r-c center" @click.stop="changeFollow(item)">
+              <!-- <i :class="'el-icon-jy-heart_' + (item.follow ? 'solid' : 'stroke')"></i> -->
+              <span :class="{ icon_claim_yes: item.isClaim, icon_claim_no: !item.isClaim }"></span>
+              <span class="claimClass">{{item.isClaim ? '已' : ''}}认领</span>
+            </div>
+            <!-- <span  @click.stop="changeDelete(item)">不是我的经销商</span> -->
+          </div>
+        </div>
+      </div>
+      <empty v-if="showEmpty" :images="require('@/assets/images/empty/jy-smile.png')">
+        <!-- <div v-if="isAllFirst">
+          <span>选择条件,立即挖掘</span>
+        </div> -->
+        <!-- <div v-else> -->
+        <div>
+          <span>暂无匹配信息</span>
+        </div>
+      </empty>
+    </div>
+    <div class="el-pagination-container" v-if="getShowPagination">
+      <el-pagination
+        background
+        layout="prev, pager, next, ->"
+        :hide-on-single-page="true"
+        :current-page="listState.pageNum"
+        :page-size="listState.pageSize"
+        :total="listState.list.length"
+        @current-change="onPageChange"
+      >
+      </el-pagination>
+    </div>
+  </el-card>
+</template>
+
+<script>
+import { Pagination, Card, Button, Dialog } from 'element-ui'
+import Empty from '@/components/common/Empty.vue'
+import { moneyUnit } from '@/utils/'
+import { getDistributorList, distributorClaim, distributorUnClaimed } from '@/api/modules/'
+
+export default {
+  name: 'potential-list',
+  components: {
+    [Pagination.name]: Pagination,
+    [Card.name]: Card,
+    [Button.name]: Button,
+    [Dialog.name]: Dialog,
+    Empty
+    // GroupCard
+  },
+  filters: {
+    formatMoney (value) {
+      return moneyUnit(value)
+    }
+  },
+  props: {
+    title: {
+      type: String,
+      default: '潜在客户'
+    },
+    filters: {
+      type: Object,
+      default () {
+        return {
+          area: '',
+          time: ''
+        }
+      }
+    }
+  },
+  computed: {
+    showEmpty () {
+      return this.listState.list.length === 0 && this.listState.loaded
+    },
+    getShowPagination () {
+      let show = true
+      if (this.listState.pageNum === 1 && this.listState.list.length < this.listState.pageSize) {
+        show = false
+      }
+      return show
+    },
+    getFilters () {
+      return this.filters
+    },
+    getListData () {
+      return this.listState.list.slice((this.listState.pageNum - 1) * 10, (this.listState.pageNum) * 10).map(v => {
+        return Object.assign(v, {
+          follow: false,
+          remove: false
+        })
+      })
+    }
+  },
+  data () {
+    return {
+      isAllFirst: true,
+      listState: {
+        loaded: true, // 是否已经搜索过
+        loading: false,
+        pageNum: 1, // 当前页
+        pageSize: 10, // 每页多少条数据
+        total: 0, // 一共多少条数据
+        sort: 0,
+        list: [] // 查询请求返回的数据
+      },
+      dialog: {
+        group: false
+      },
+      cur: {
+        group: 'A', // 当前点击编辑的分组信息
+        fid: ''
+      },
+      selectItem: {}
+    }
+  },
+  created () {},
+  methods: {
+    changeFollow (item) {
+      this.selectItem = item
+      this.cur.fid = item.entId
+      if (item.isClaim) {
+        distributorUnClaimed({ ent_id: item.company_id }).then(res => {
+          if (!(res && res.error_code === 0 && res.error_msg === '')) {
+            this.$toast(res.error_msg, 2000)
+          } else {
+            this.$toast('取消认领成功')
+            item.isClaim = !item.isClaim
+            this.$forceUpdate()
+          }
+        })
+      } else {
+        distributorClaim({ ent_id: item.company_id, type: 2, ent_name: item.company_name }).then(res => {
+          if (!(res && res.error_code === 0 && res.error_msg === '')) {
+            if (res.error_code === 1013) {
+              this.$toast('经销商认领已达上限', 2000)
+            } else {
+              this.$toast(res.error_msg, 2000)
+            }
+          } else {
+            this.$toast('认领成功')
+            item.isClaim = !item.isClaim
+            this.$forceUpdate()
+          }
+        })
+      }
+    },
+    changeSort (i) {
+      this.listState.sort = i
+      this.listState.pageNum = 1
+      this.listState.list = []
+      this.listState.total = 0
+      this.getList()
+    },
+    // 恢复数据至第一次请求的状态(页码等)
+    resetListState () {
+      const state = {
+        loaded: false,
+        loading: false,
+        pageNum: 1,
+        total: 0,
+        list: []
+      }
+      Object.assign(this.listState, state)
+    },
+    saveGroupData (data) {
+      this.selectItem.group = data
+      this.$emit(this.selectItem.follow ? 'remove' : 'follow', this.selectItem)
+      this.selectItem.follow = !this.selectItem.follow
+      this.$forceUpdate()
+    },
+    doQuery (filters) {
+      this.resetListState()
+      this.getList(filters)
+    },
+    async getList (filters) {
+      if (this.getFilters.productList.length > 0) {
+        const productArr = []
+        this.getFilters.productList.forEach(v => {
+          productArr.push(v.value)
+        })
+        this.getFilters.product_model = productArr.join()
+      }
+      const query = {
+        sort_no: this.listState.sort,
+        sort: this.listState.sort,
+        business_scope: this.getFilters.business_scope,
+        business: this.getFilters.business,
+        searchbool: this.getFilters.searchbool,
+        product_model: this.getFilters.product_model,
+        company_name: this.getFilters.company_name,
+        brand: this.getFilters.brand
+      }
+
+      if (filters && Object.keys(filters).length > 0) {
+        Object.keys(filters).forEach(v => {
+          if (typeof filters[v] !== 'undefined') {
+            query[v] = filters[v]
+          }
+        })
+      }
+      query.area_code = JSON.stringify(this.getFilters.area_code)
+      this.listState.loading = true
+      this.listState.loaded = false
+      // 判断是否无筛选条件
+      this.isAllFirst = false
+      const tempQuery = query.searchbool === 0 ? {
+        sort_no: query.sort_no,
+        sort: query.sort
+      } : query
+      const res = await getDistributorList(tempQuery)
+      this.listState.loading = false
+      this.listState.loaded = true
+      if (res.error_code === 0) {
+        this.listState.total = res.data === null ? 0 : res.data.length
+        this.listState.list = res.data || []
+      } else {
+        this.listState.total = 0
+        this.listState.list = []
+      }
+    },
+    onPageChange (p) {
+      this.listState.pageNum = p
+      // this.getList()
+    },
+    getMore () {
+      this.$emit('getMore')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  @include diy-icon('edit', 20, 20);
+  @include diy-icon('icon-company', 24, 24);
+  @include diy-icon('heart_stroke', 18, 18);
+  @include diy-icon('heart_solid', 18, 18);
+  ::v-deep .el-icon-jy-icon-company {
+    margin-right: 8px;
+    flex-shrink: 0;
+  }
+  // card样式重置
+  ::v-deep {
+    .el-card__header {
+      margin: 0 40px;
+      padding-left: 0;
+      padding-right: 0;
+    }
+    .el-card__body {
+      padding: 0;
+    }
+    .el-dialog__header {
+      padding: 0;
+    }
+    .el-dialog__body {
+      padding: 0;
+    }
+    .empty-container {
+      margin-top: 60px;
+    }
+    .get-more {
+      display: flex;
+      .el-icon-arrow-right {
+        margin-left: 4px;
+        order: 2;
+      }
+    }
+  }
+  .sub-manager {
+    display: flex;
+    align-items: center;
+    padding: 8px 16px;
+    font-size: 14px;
+    line-height: 24px;
+    color: #1d1d1d;
+    border-color: #E0E0E0;
+    &.el-button:focus,
+    &.el-button:hover {
+      color: inherit;
+      background-color: inherit;
+    }
+  }
+
+  .potential-list-card {
+    border-radius: 8px;
+    box-shadow: none!important;
+    .header-box {
+      padding: 0 40px;
+      background: #FFFFFF;
+      opacity: 1;
+      border-bottom: 1px solid #ECECEC;
+      border-radius: 8px 8px 0 0;
+    }
+    .header-hengxian-hover {
+      height: 2px;
+      background: #2CB7CA;
+      opacity: 1;
+    }
+    .card-title {
+      padding: 14px 0;
+      color: #2cb7ca;
+      line-height: 20px;
+      border-bottom: 2px solid #2CB7CA;
+    }
+
+    .pcor-right-group {
+      > span {
+        font-size: 12px;
+        line-height: 23px;
+        color: #AAAAAA;
+        margin-top: 17px;
+        &:hover {
+          color: #2CB7CA;
+        }
+      }
+      i + span {
+        margin-left: 4px;
+        font-size: 14px;
+        line-height: 22px;
+        color: #686868;
+      }
+    }
+
+    .link-text {
+      font-size: 14px;
+      line-height: 24px;
+      text-decoration-line: underline;
+      color: #1D1D1D;
+      cursor: pointer;
+      & + .link-text {
+        margin-left: 20px;
+      }
+      &:hover,&.active {
+        color: #2CB7CA;
+      }
+    }
+    .text--{
+      &sm-value {
+        color: #1D1D1D;
+      }
+      &title {
+        font-size: 16px;
+        line-height: 24px;
+        color: #1D1D1D;
+      }
+      &sm-time {
+        font-size: 12px;
+        line-height: 20px;
+        color: #999999;
+      }
+    }
+    .card-list-item {
+      padding: 24px 0;
+      padding-left: 40px;
+      padding-right: 40px;
+      box-sizing: border-box;
+      box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.05);
+      cursor: pointer;
+      &:hover {
+        background: #F7F9FC;
+        .text--title,
+        .text--sm-value {
+          color: #2CB7CA;
+        }
+      }
+    }
+    .card-bottom-info {
+      margin-top: 12px;
+      justify-content: flex-start;
+      text-align: left;
+      font-size: 14px;
+      line-height: 22px;
+      color: #999999;
+      .text--sm-value {
+        margin-right: 40px;
+        &:last-child {
+          margin-right: 0;
+        }
+      }
+    }
+    .sub-manager {
+      float: right;
+    }
+    .info-list {
+      min-height: 100px;
+      border-top: 1px solid transparent;
+    }
+    .add-key-button {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-top: 32px;
+      padding: 8px 16px;
+      color: #F7F9FA;
+      border-radius: 6px;
+      background-color: #2ABED1;
+      cursor: pointer;
+      .icon-chahao {
+        margin-right: 4px;
+        transform: rotate(-45deg);
+      }
+      .button-text {
+        margin-left: 4px;
+        white-space: nowrap;
+      }
+    }
+    .icon-chahao {
+      position: relative;
+      display: inline-block;
+      width: 14px;
+      height: 14px;
+      &:before,
+      &:after {
+        position: absolute;
+        content: ''!important;
+        background-color: #fff;
+        top: 50%;
+        left: 50%;
+        width: 14px;
+        height: 2px;
+        border-radius: 2px;
+      }
+      &:before {
+        transform: translate(-50%,-50%) rotate(45deg);
+      }
+      &:after {
+        transform: translate(-50%,-50%) rotate(-45deg);
+      }
+    }
+    .el-pagination-container  {
+      margin-right: 40px;
+    }
+    .icon_claim_no{
+      width: 20px;
+      height: 20px;
+      background: url('~@/assets/images/icon/renling.png') no-repeat;
+      background-size: contain;
+    }
+    .icon_claim_yes{
+      width: 20px;
+      height: 20px;
+      background: url('~@/assets/images/icon/renling-active.png') no-repeat;
+      background-size: contain;
+    }
+    .icon_claim_no,
+    .icon_claim_yes {
+      margin-right: 4px;
+      display: flex;
+      width: 18px;
+      height: 18px;
+    }
+    .claimClass {
+      font-size: 14px;
+      font-weight: 400;
+      color: #686868;
+      white-space: nowrap;
+    }
+  }
+</style>

+ 452 - 0
src/components/push-list/MedicalList.vue

@@ -0,0 +1,452 @@
+<template>
+  <el-card class="potential-list-card">
+    <div class="header-box flex-r-c sb">
+      <span class="card-title">{{title}}</span>
+      <div class="flex-r-c center" v-show="!showEmpty">
+        <div class="link-text" @click="changeSort(0)" :class="{active: listState.sort === 0}">数量倒序</div>
+        <div class="link-text" @click="changeSort(1)" :class="{active: listState.sort === 1}">金额倒序</div>
+      </div>
+    </div>
+    <div class="info-list" v-loading="listState.loading">
+      <div
+        v-for="(item, index) in getListData"
+        :key="index"
+        @click="$emit('goDetail', item)">
+        <div v-show="!item.remove" class="flex-r-c center sb card-list-item">
+          <div class="flex-c-c left">
+            <div class="flex-r-c center sb">
+              <div class="flex-r-c center">
+                <i class="el-icon-jy-icon-company"></i>
+                <span class="text--title">{{item.company_name}}</span>
+              </div>
+            </div>
+            <div class="flex-r-c card-bottom-info">
+              <span class="text--sm-name">项目数量:</span>
+              <span class="text--sm-value">{{ item.project_count || '--'}}</span>
+              <span class="text--sm-name">项目总金额:</span>
+              <span class="text--sm-value">{{ item.project_money || '--'}}</span>
+              <span class="text--sm-name">所在地:</span>
+              <span class="text--sm-value" v-if="item.area || item.city">{{item.area}}&nbsp;&nbsp;
+                {{item.area && item.city && item.area.slice(0,2) !== item.city.slice(0,2) ? item.city : ''}}
+              </span>
+              <span class="text--sm-value" v-else>-</span>
+            </div>
+          </div>
+          <div class="pcor-right-group flex-c-c right">
+            <div class="flex-r-c center" @click.stop="changeClaim(item)">
+              <i :class="'el-icon-jy-renling' + (item.isClaim ? '-active' : '')"></i>
+              <span>{{item.isClaim ? '已' : ''}}认领</span>
+            </div>
+          </div>
+        </div>
+      </div>
+      <empty v-if="showEmpty" :images="require('@/assets/images/empty/jy-smile.png')">
+        <div v-if="isAllFirst">
+          <span></span>
+        </div>
+        <div v-else>
+          <span>暂无匹配信息</span>
+        </div>
+      </empty>
+    </div>
+    <div class="el-pagination-container" v-if="getShowPagination">
+      <el-pagination
+        background
+        layout="prev, pager, next, slot, jumper"
+        :hide-on-single-page="true"
+        :current-page="listState.pageNum"
+        :page-size="listState.pageSize"
+        :total="listState.list.length"
+        @current-change="onPageChange"
+      >
+        <em class="page-size-border">{{ listState.pageSize }}条/页</em>
+      </el-pagination>
+    </div>
+  </el-card>
+</template>
+
+<script>
+import { Pagination, Card, Button, Dialog } from 'element-ui'
+import Empty from '@/components/common/Empty.vue'
+import { moneyUnit } from '@/utils/'
+import { getSearchMedicalList, setInstitutionClaim, institutionUnClaimed } from '@/api/modules/'
+
+export default {
+  name: 'potential-list',
+  components: {
+    [Pagination.name]: Pagination,
+    [Card.name]: Card,
+    [Button.name]: Button,
+    [Dialog.name]: Dialog,
+    Empty
+  },
+  filters: {
+    formatMoney (value) {
+      return moneyUnit(value)
+    }
+  },
+  props: {
+    title: {
+      type: String,
+      default: '医疗机构列表'
+    },
+    filters: {
+      type: Object,
+      default () {
+        return {}
+      }
+    }
+  },
+  computed: {
+    showEmpty () {
+      return this.listState.list.length === 0 && this.listState.loaded
+    },
+    getShowPagination () {
+      let show = true
+      if (this.listState.pageNum === 1 && this.listState.list.length < this.listState.pageSize) {
+        show = false
+      }
+      return show
+    },
+    getFilters () {
+      return this.filters
+    },
+    getListData () {
+      return this.listState.list.slice((this.listState.pageNum - 1) * 10, (this.listState.pageNum) * 10)
+    }
+  },
+  data () {
+    return {
+      isAllFirst: true,
+      listState: {
+        loaded: true, // 是否已经搜索过
+        loading: false,
+        pageNum: 1, // 当前页
+        pageSize: 10, // 每页多少条数据
+        total: 0, // 一共多少条数据
+        sort: 0,
+        list: [] // 查询请求返回的数据
+      }
+    }
+  },
+  created () {},
+  methods: {
+    async changeClaim (item) {
+      if (item.isClaim) {
+        // 取消认领
+        const { error_code: code, error_msg: msg } = await institutionUnClaimed({
+          ent_id: item.company_id
+        })
+        if (code === 0) {
+          this.$toast('已取消认领')
+          item.isClaim = false
+          this.$forceUpdate()
+        } else {
+          this.$toast(msg, 2000)
+        }
+      } else {
+        // 认领
+        const { error_code: code, error_msg: msg } = await setInstitutionClaim({
+          ent_id: item.company_id,
+          ent_name: item.company_name
+        })
+        if (code === 0) {
+          this.$toast('认领成功')
+          item.isClaim = true
+          this.$forceUpdate()
+        } else {
+          if (code === 1013) {
+            this.$toast('医疗机构认领已达上限')
+          } else {
+            this.$toast(msg, 2000)
+          }
+        }
+      }
+    },
+    changeSort (i) {
+      this.listState.sort = i
+      this.listState.pageNum = 1
+      this.listState.list = []
+      this.listState.total = 0
+      this.getList()
+    },
+    // 恢复数据至第一次请求的状态(页码等)
+    resetListState () {
+      const state = {
+        loaded: false,
+        loading: false,
+        pageNum: 1,
+        total: 0,
+        list: []
+      }
+      Object.assign(this.listState, state)
+    },
+    doQuery (filters) {
+      this.resetListState()
+      this.getList(filters)
+    },
+    async getList () {
+      const query = {
+        sort: this.listState.sort,
+        company_name: this.getFilters.name,
+        area_code: JSON.stringify(this.getFilters.area),
+        level_code: this.getFilters.level,
+        mi_type_code: this.getFilters.type,
+        business_type_code: this.getFilters.nature,
+        sdequipment_code: this.getFilters.scope
+      }
+
+      if (query && Object.keys(query).length > 0) {
+        for (const key in query) {
+          if (key !== 'sort' && !query[key]) {
+            delete query[key]
+          }
+        }
+      }
+      this.listState.loading = true
+      this.listState.loaded = false
+      // 判断是否无筛选条件
+      this.isAllFirst = false
+      const res = await getSearchMedicalList(query)
+      this.listState.loading = false
+      this.listState.loaded = true
+      if (res.error_code === 0) {
+        this.listState.list = res.data || []
+      } else {
+        this.listState.list = []
+      }
+    },
+    onPageChange (p) {
+      this.listState.pageNum = p
+      // this.getList()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  @include diy-icon('edit', 20, 20);
+  @include diy-icon('icon-company', 24, 24);
+  @include diy-icon('renling', 18, 18);
+  @include diy-icon('renling-01', 18, 18);
+  @include diy-icon('renling-active', 18, 18);
+  ::v-deep .el-icon-jy-icon-company {
+    margin-right: 8px;
+    flex-shrink: 0;
+  }
+  // card样式重置
+  ::v-deep {
+    .el-card__header {
+      margin: 0 40px;
+      padding-left: 0;
+      padding-right: 0;
+    }
+    .el-card__body {
+      padding: 0 0 20px;
+    }
+    .el-dialog__header {
+      padding: 0;
+    }
+    .el-dialog__body {
+      padding: 0;
+    }
+    .empty-container {
+      margin-top: 60px;
+    }
+    .get-more {
+      display: flex;
+      .el-icon-arrow-right {
+        margin-left: 4px;
+        order: 2;
+      }
+    }
+    .el-pagination__jump{
+      margin-left: 8px;
+    }
+  }
+  .sub-manager {
+    display: flex;
+    align-items: center;
+    padding: 8px 16px;
+    font-size: 14px;
+    line-height: 24px;
+    color: #1d1d1d;
+    border-color: #E0E0E0;
+    &.el-button:focus,
+    &.el-button:hover {
+      color: inherit;
+      background-color: inherit;
+    }
+  }
+
+  .potential-list-card {
+    border-radius: 8px;
+    box-shadow: none!important;
+    overflow: hidden;
+    .header-box {
+      padding: 14px 40px;
+      border-bottom: 1px solid #ECECEC;
+    }
+    .card-title {
+      position: relative;
+      line-height: 28px;
+      color: $color_main;
+      &::after{
+        position: absolute;
+        left: 0;
+        bottom: -14px;
+        content: '';
+        width: 100%;
+        height: 2px;
+        background: $color_main;
+      }
+    }
+
+    .pcor-right-group {
+      > span {
+        font-size: 12px;
+        line-height: 23px;
+        color: #AAAAAA;
+        margin-top: 17px;
+        &:hover {
+          color: #2CB7CA;
+        }
+      }
+      i + span {
+        margin-left: 4px;
+        font-size: 14px;
+        line-height: 22px;
+        color: #686868;
+        white-space: nowrap;
+      }
+      &:hover{
+        .el-icon-jy-renling{
+          background-image: url('~@/assets/images/icon/renling-01.png');
+        }
+        span{
+          color: #2CB7CA;
+        }
+      }
+    }
+
+    .link-text {
+      font-size: 14px;
+      line-height: 24px;
+      text-decoration-line: underline;
+      color: #1D1D1D;
+      cursor: pointer;
+      & + .link-text {
+        margin-left: 20px;
+      }
+      &:hover,&.active {
+        color: #2CB7CA;
+      }
+    }
+    .text--{
+      &sm-value {
+        color: #1D1D1D;
+      }
+      &title {
+        font-size: 16px;
+        line-height: 24px;
+        color: #1D1D1D;
+      }
+      &sm-time {
+        font-size: 12px;
+        line-height: 20px;
+        color: #999999;
+      }
+    }
+    .card-list-item {
+      padding: 24px 0;
+      padding-left: 40px;
+      padding-right: 40px;
+      box-sizing: border-box;
+      box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.05);
+      cursor: pointer;
+      &:hover {
+        background: #F7F9FC;
+        .text--title,
+        .text--sm-value {
+          color: #2CB7CA;
+        }
+      }
+    }
+    .card-bottom-info {
+      margin-top: 12px;
+      justify-content: flex-start;
+      text-align: left;
+      font-size: 14px;
+      line-height: 22px;
+      color: #999999;
+      .text--sm-value {
+        margin-right: 40px;
+        &:last-child {
+          margin-right: 0;
+        }
+      }
+    }
+    .sub-manager {
+      float: right;
+    }
+    .info-list {
+      min-height: 100px;
+      border-top: 1px solid transparent;
+    }
+    .add-key-button {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-top: 32px;
+      padding: 8px 16px;
+      color: #F7F9FA;
+      border-radius: 6px;
+      background-color: #2ABED1;
+      cursor: pointer;
+      .icon-chahao {
+        margin-right: 4px;
+        transform: rotate(-45deg);
+      }
+      .button-text {
+        margin-left: 4px;
+        white-space: nowrap;
+      }
+    }
+    .icon-chahao {
+      position: relative;
+      display: inline-block;
+      width: 14px;
+      height: 14px;
+      &:before,
+      &:after {
+        position: absolute;
+        content: ''!important;
+        background-color: #fff;
+        top: 50%;
+        left: 50%;
+        width: 14px;
+        height: 2px;
+        border-radius: 2px;
+      }
+      &:before {
+        transform: translate(-50%,-50%) rotate(45deg);
+      }
+      &:after {
+        transform: translate(-50%,-50%) rotate(-45deg);
+      }
+    }
+    .el-pagination-container  {
+      margin-right: 40px;
+    }
+    .page-size-border{
+      display: inline-block;
+      padding: 0 15px;
+      height: 28px;
+      line-height: 28px;
+      border: 1px solid #ECECEC;
+      border-radius: 2px;
+      font-size: 14px;
+      color: #1D1D1D;
+    }
+  }
+</style>

+ 1 - 1
src/components/push-list/PotentialList.vue

@@ -453,7 +453,7 @@ export default {
       &:before,
       &:after {
         position: absolute;
-        content: '';
+        content: ''!important;
         background-color: #fff;
         top: 50%;
         left: 50%;

+ 1 - 1
src/components/push-list/ProjectList.vue

@@ -284,7 +284,7 @@ export default {
       &:before,
       &:after {
         position: absolute;
-        content: '';
+        content: ''!important;
         background-color: #fff;
         top: 50%;
         left: 50%;

+ 1 - 1
src/components/push-list/ProjectProgressList.vue

@@ -508,7 +508,7 @@ export default {
       &:before,
       &:after {
         position: absolute;
-        content: '';
+        content: ''!important;
         background-color: #fff;
         top: 50%;
         left: 50%;

File diff suppressed because it is too large
+ 28 - 4
src/components/push-list/PushList.vue


+ 551 - 0
src/components/scope/components/Edit.vue

@@ -0,0 +1,551 @@
+<template>
+  <div class="edit-form">
+    <div class="input-box">
+      <div class="input-box-title">{{title}}关键词组</div>
+      <div class="item">
+        <div class="item-label">关键词:</div>
+        <div class="item-value">
+          <el-input
+            type="textarea"
+            autosize
+            resize="none"
+            :placeholder="keyPlaceholder"
+            debounce="600"
+            maxlength="200"
+            @input="keywordsInput"
+            v-model="cur.key">
+          </el-input>
+        </div>
+      </div>
+      <div class="item" v-if="sameWordsList && sameWordsList.length > 0">
+        <div class="item-label"></div>
+        <div class="item-value">
+          <div class="recommend">
+            <div class="recommend-title">
+              <span>相似推荐</span>
+              <span class="batch-btn" v-if="sameWordsList && sameWordsList.length > 9" @click="getRecommendFn"><i class="el-icon-refresh"></i> 换一批</span>
+            </div>
+            <div class="recommend-main">
+              <div class="r-list"
+              :class="{active: s.selected}"
+              v-for="(s, j) in sameWordsList.slice(0, 9)"
+              :key="'00' + j"
+              @click="addSameItem($event, s)">{{s.word}}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="item no-center" v-if="showMatch">
+        <div class="item-label">匹配模式:</div>
+        <div class="item-value">
+          <el-radio-group v-model="cur.matchway" @change="chooseMatchWay($event)">
+            <el-radio class="radio-item" name="matchway" :label="1">模糊<span class="math-tips">(包含其中1个关键词即可)</span></el-radio>
+            <el-radio name="matchway" :label="0">精准<span class="math-tips">(同时包含所有关键词)</span></el-radio>
+          </el-radio-group>
+        </div>
+      </div>
+      <div class="item" v-if="showNotWay" key="notway">
+        <div class="item-label">排除词:</div>
+        <div class="item-value">
+          <el-input
+            type="textarea"
+            autosize
+            resize="none"
+            :placeholder="notKeyPlaceholder"
+            v-model="cur.notkey">
+          </el-input>
+        </div>
+      </div>
+      <div class="item" v-else key="notway">
+        <div class="item-label"></div>
+        <div class="item-value">
+          <div class="add-words-btn" @click="showNotWay = true">+添加排除词(不希望接收,与关键词互斥)</div>
+        </div>
+      </div>
+      <div class="btn-groups">
+        <button type="button" :disabled="keyDisabled" class="save-btn confirm-btn" @click="submitKeywords">保存</button>
+        <button type="button" class="cancle-btn" @click="cancelEdit">取消</button>
+      </div>
+    </div>
+    <!-- 关键词重复提示 -->
+    <el-dialog
+      :custom-class="isNesting ? 'nesting-dialog small-dialog' : 'small-dialog'"
+      title="新增关键词组"
+      :visible.sync="dialog.repeat"
+      width="380px"
+      center
+      :show-close="false"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+      >
+      <p class="dialog-text">该组关键词已存在,请勿重复添加</p>
+      <span slot="footer" class="dialog-footer">
+        <el-button class="know-btn" type="primary" @click="dialog.repeat = false">我知道了</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { Input, Button, RadioGroup, Radio, Dialog } from 'element-ui'
+import { getRecommend } from '@/api/modules'
+import { debounce } from '@/utils/'
+import $bus from '@/utils/bus'
+/* eslint-disable */
+Array.prototype.indexOf = function (val) {
+  for (var i = 0; i < this.length; i++) {
+    if (this[i] === val) return i
+  }
+  return -1
+}
+Array.prototype.remove = function (val) {
+  var index = this.indexOf(val)
+  if (index > -1) {
+    this.splice(index, 1)
+  }
+}
+/* eslint-disable */
+export default {
+  name: 'keywords-edit',
+  components: {
+    [Input.name]: Input,
+    [Button.name]: Button,
+    [RadioGroup.name]: RadioGroup,
+    [Radio.name]: Radio,
+    [Dialog.name]: Dialog
+  },
+  props: {
+    datas: Array,
+    keywords: Object,
+    keyIndex: Number,
+    title: {
+      type: String,
+      default: ''
+    },
+    showMatch: {
+      type: Boolean,
+      default: true
+    },
+    keyPlaceholder: {
+      type: String,
+      default: '请输入关键词'
+    },
+    notKeyPlaceholder: {
+      type: String,
+      default: '请输入排除词'
+    },
+    isNesting: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      cur: {
+        key: '',
+        appendkey: '',
+        notkey: '',
+        matchway: 1
+      },
+      dialog: {
+        repeat: false
+      },
+      showNotWay: false, // 是否展示添加排除词输入框
+      sameWordsList: [] // 相似推荐数据
+    }
+  },
+  computed: {
+    keyDisabled () {
+      return !this.cur.key
+    }
+  },
+  created () {
+    if (this.keywords) {
+      if (this.keywords.key && this.keywords.key.length > 0) {
+        if (this.keywords.appendkey) {
+          this.cur.key = this.keywords.key.join(' ') + ' ' + this.keywords.appendkey.join(' ')
+        } else {
+          this.cur.key = this.keywords.key.join(' ')
+        }
+      }
+      if (this.keywords.notkey && this.keywords.notkey.length > 0) {
+        this.showNotWay = true
+        this.cur.notkey = this.keywords.notkey.join(' ')
+      }
+      if (this.keywords.matchway) {
+        this.cur.matchway = 1
+      } else {
+        this.cur.matchway = 0
+      }
+    }
+  },
+  methods: {
+    // 关键词推荐数量查询
+    keywordsInput () {
+      if (this.cur.key) {
+        setTimeout(() => {
+          this.getRecommendFn()
+        }, 1000)
+      }
+    },
+    // 获取相似项目推荐
+    getRecommendFn: debounce(function () {
+      getRecommend({
+        count: 20,
+        value: this.cur.key
+      }).then(res => {
+        this.sameWordsList = []
+        if (res.length > 0) {
+          res.forEach(item => {
+            item.selected = false
+          })
+          const m = this.cur.key.split(' ')
+          m.forEach(v => {
+            res.forEach(s => {
+              if (v === s.word) {
+                s.selected = true
+              }
+            })
+          })
+        }
+        this.sameWordsList = res || []
+      })
+    }, 500),
+    chooseMatchWay () {
+      // console.log(state, this.cur.matchway)
+    },
+    // 添加订阅推荐到输入框
+    addSameItem (e, item) {
+      item.selected = !item.selected
+      const m = this.cur.key.split(' ')
+      if (item.selected) {
+        m.push(item.word)
+        this.cur.key = m.join(' ')
+      } else {
+        m.remove(item.word)
+        this.cur.key = m.join(' ')
+      }
+    },
+    // 保存数据
+    saveCurData () {
+      const data = JSON.parse(JSON.stringify(this.datas)) || []
+      const arr = this.cur.key.split(' ')
+      const keyArr = arr[0].split(',')
+      const appendArr = arr.length > 1 ? arr.slice(1) : null
+      if (this.title === '新增') {
+        const obj = {
+          key:  keyArr,
+          notkey:  this.cur.notkey ? this.cur.notkey.split(' ') : [],
+          appendkey: appendArr,
+          matchway: this.cur.matchway,
+          updatetime: Math.round(new Date() / 1000)
+        }
+        data.push(obj)
+      } else {
+        data[this.keyIndex].key = []
+        data[this.keyIndex].notkey = null
+        data[this.keyIndex].appendkey = null
+        data[this.keyIndex].key = keyArr
+        data[this.keyIndex].appendkey = appendArr
+        data[this.keyIndex].notkey = this.cur.notkey ? this.cur.notkey.split(' ') : null,
+        data[this.keyIndex].matchway = this.cur.matchway,
+        data[this.keyIndex].updatetime = Math.round(new Date() / 1000)
+      }
+      return data
+    },
+    // 提交数据
+    submitKeywords () {
+      const isChange = this.getISChange()
+      if (isChange) {
+        const params = this.saveCurData()
+        this.$emit('closeForm', {
+          data: params,
+          index: this.keyIndex
+        })
+        return
+      }
+      const isRepeat = this.getRepeatFn()
+      if (isRepeat) {
+        this.dialog.repeat = true
+        return
+      }
+      const params = this.saveCurData()
+      this.$emit('closeForm', {
+        data: params,
+        index: this.keyIndex
+      })
+      $bus.$emit('updateScope', params)
+    },
+    // 判断有无重复
+    getRepeatFn () {
+      const datas = JSON.parse(JSON.stringify(this.datas)) || []
+      const arr = this.cur.key.split(' ')
+      const keyArr = arr[0].split(',')
+      const appendArr = arr.length > 1 ? arr.slice(1) : null
+      const notKeyArr = this.cur.notkey ? this.cur.notkey.split(',') : null
+      const matchWay = this.cur.matchway
+      let isRepeat
+      for (let i = 0; i < datas.length; i++) {
+        const v = datas[i]
+        v.appendkey = v.appendkey && v.appendkey.length > 0 ? v.appendkey : null
+        v.notkey = v.notkey && v.notkey.length > 0 ? v.notkey : null
+        const rKey = JSON.stringify(v.key) === JSON.stringify(keyArr)
+        const rAppend = JSON.stringify(v.appendkey) === JSON.stringify(appendArr)
+        const rNot = JSON.stringify(v.notkey) === JSON.stringify(notKeyArr)
+        const rWay = v.matchway === this.cur.matchway
+        // console.log(JSON.stringify(v.key), JSON.stringify(v.appendkey), JSON.stringify(v.notkey), JSON.stringify(keyArr), JSON.stringify(appendArr), JSON.stringify(notKeyArr))
+        // console.log(rKey, rAppend, rNot, rWay)
+        if (rKey && rAppend && rNot && rWay) {
+          isRepeat = rKey && rAppend && rNot && rWay
+          break
+        }
+      }
+      return isRepeat
+    },
+    // 判断是否更改
+    getISChange () {
+      if (this.title === '修改') {
+        const obj = this.keywords
+        obj.notkey = obj.notkey && obj.notkey.length > 0 ? obj.notkey : null
+        obj.appendkey = obj.appendkey && obj.appendkey.length > 0 ? obj.appendkey : null
+        const arr = this.cur.key.split(' ')
+        const keyArr = arr[0].split(',')
+        const appendArr = arr.length > 1 ? arr.slice(1) : null
+        const notKeyArr = this.cur.notkey ? this.cur.notkey.split(',') : null
+        const matchWay = this.cur.matchway
+        const cKey = JSON.stringify(obj.key) === JSON.stringify(keyArr)
+        const cAppend = JSON.stringify(obj.appendkey) === JSON.stringify(appendArr)
+        const cNotKey = JSON.stringify(obj.notkey) === JSON.stringify(notKeyArr)
+        const cWay = obj.matchway === matchWay
+        const notChange = cKey && cAppend && cNotKey && cWay
+        // console.log(cKey, cAppend, cNotKey, cWay, JSON.stringify(obj.appendkey), JSON.stringify(appendArr), JSON.stringify(obj.notkey), JSON.stringify(notKeyArr), obj.matchway, matchWay)
+        return notChange
+      }
+    },
+    cancelEdit () {
+      this.$emit('closeForm', {
+        index: this.keyIndex
+      })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.edit-form{
+  margin-top: 24px;
+  .classify-title{
+    padding-bottom: 20px;
+    .icon-edit,.icon-delete{
+      display: inline-block;
+      width: 16px;
+      height: 16px;
+      background-repeat: no-repeat;
+      background-position: center center;
+      cursor: pointer;
+      &::before{
+        content: ''!important;
+      }
+    }
+    .icon-edit{
+      margin: 0 10px;
+      background-image: url('~@/assets/images/icon-edit.png');
+      background-size: contain;
+    }
+    .icon-delete{
+      background-image: url('~@/assets/images/icon-delete.png');
+      background-size: contain;
+    }
+  }
+  .input-box{
+    padding: 20px 30px;
+    background: #f6f7f9;
+    border-radius: 8px;
+  }
+  .input-box-title{
+    margin-bottom: 16px;
+    font-size: 16px;
+    text-align: center;
+    color: #1d1d1d;
+  }
+  .item{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 10px;
+    &.no-center{
+      align-items: unset;
+    }
+  }
+  .item-label{
+    margin-right: 8px;
+    min-width: 78px;
+    height: 40px;
+    color: #1d1d1d;
+    font-size: 14px;
+    line-height: 40px;
+    text-align: right;
+  }
+  .no-center{
+    .item-label{
+      line-height: 20px;
+    }
+  }
+  .radio-item{
+    margin-bottom: 16px;
+  }
+  .item-label-required:before{
+    content: "*";
+    color: #f56c6c;
+    margin-right: 2px;
+  }
+  .recommend{
+    padding: 16px;
+    background: #fff;
+    border-radius: 4px;
+    .recommend-title{
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      color: #686868;
+      font-size: 14px;
+      .batch-btn{
+        color: #2cb7ca;
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+    .recommend-main{
+      display: flex;
+      align-items: center;
+      flex-wrap: wrap;
+      .r-list{
+        padding: 0 8px;
+        margin: 12px 12px 0 0;
+        height: 22px;
+        line-height: 22px;
+        background: #f5f6f7;
+        border-radius: 4px;
+        color: #1d1d1d;;
+        font-size: 14px;
+        cursor: pointer;
+      }
+      .active{
+        background: #2cb7ca;
+        color: #fff;
+      }
+    }
+  }
+  .add-words-btn{
+    width: 352px;
+    height: 40px;
+    line-height: 40px;
+    border: 1px dashed #2cb7ca;
+    border-radius: 6px;
+    background: #fff;
+    color: #2cb7ca;
+    text-align: center;
+    cursor: pointer;
+    font-size: 14px;
+  }
+  .item-value{
+    flex: 1;
+  }
+  .custom-long-input{
+    width: 352px;
+  }
+  .custom-short-input{
+    width: 170px;
+  }
+  .item-other{
+    display: flex;
+    justify-content: center;
+    margin-bottom: 10px;
+  }
+  .item-other-value{
+    margin-top: 8px;
+  }
+  .math-tips{
+    margin-top: 4px;
+    font-size: 12px;
+    color: #999999;
+    line-height: 20px;
+  }
+  .radio-item{
+    margin-bottom: 10px;
+  }
+  .item-keywords-value{
+    display: flex;
+    justify-content: space-between;
+  }
+  .add-tag{
+    width: 170px;
+    height: 40px;
+    line-height: 40px;
+    color: #2cb7ca;
+    border-radius: 6px;
+    text-align: center;
+    cursor: pointer;
+    color: #2cb7ca;
+    border: 1px dashed #2cb7ca;
+  }
+  .add-word-list{
+    margin-bottom: 6px;
+  }
+  .keywords-help{
+    width: 412px;
+    margin-top: 20px;
+    font-size: 12px;
+    color: #999999;
+    line-height: 20px;
+    text-align: justify;
+  }
+  .btn-groups{
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+}
+// element-ui样式修改
+::v-deep {
+  .el-input__inner,
+  .el-textarea__inner{
+    font-size: 14px;
+    color: #1D1D1D;
+    border-color: #ececec;
+  }
+  .el-textarea__inner{
+    padding: 8px 15px;
+  }
+  .el-input__inner:hover,
+  .el-textarea__inner:hover{
+    border-color: #ececec;
+  }
+  .el-input__inner:focus,
+  .el-textarea__inner:focus{
+    border-color: #2cb7ca;
+  }
+  .el-radio{
+    color: #1d1d1d;
+    font-size: 14px;
+  }
+  .el-radio__inner{
+    width: 20px;
+    height: 20px;
+  }
+  .el-radio__input.is-checked .el-radio__inner{
+    border: 0;
+    background: transparent;
+    width: 20px;
+    height: 20px;
+    background: url('~@/assets/images/icon-checked.png') no-repeat center center;
+    background-size: contain;
+  }
+  .el-radio__inner::after{
+    background: transparent;
+  }
+  .el-radio__input.is-checked+.el-radio__label{
+    color: #1d1d1d;
+  }
+  .el-radio__inner:hover{
+    border-color: #ececec;
+  }
+}
+</style>

+ 504 - 0
src/components/scope/components/List.vue

@@ -0,0 +1,504 @@
+<template>
+  <div class="classify">
+    <div class="key-title">
+      <span>{{datas.title}}</span>
+    </div>
+    <div class="classify-list">
+      <div class="classify-content" v-if="newWordsList && newWordsList.length > 0">
+        <div class="add-words-box" @click="addKeyFn()" v-if="!addShowForm" key="add">+新增关键词组</div>
+        <div style="width:100%;" v-else key="add">
+          <Edit
+            :datas="newWordsList"
+            title="新增"
+            :keywords="{key:[], appendkey:[],notkey:[],showForm: true,matchway: 1}"
+            :keyIndex="newWordsList.length"
+            :is-nesting="isNesting"
+            @closeForm="addShowForm = false"
+          >
+          </Edit>
+        </div>
+        <div class="list" v-for="(v, i) in newWordsList" :key="'0' + i" style="width:100%;">
+          <Edit
+            :datas="newWordsList"
+            title="修改"
+            :keywords="v"
+            :keyIndex="i"
+            @closeForm="onCloseForm"
+            :is-nesting="isNesting"
+            v-if="v.showForm" key="edit">
+          </Edit>
+          <div class="list-box" v-else key="edit">
+            <div v-if="showMatch">
+              <div class="list-left yellow-box" v-if="v.matchway">模糊</div>
+              <div class="list-left blue-box" v-else>精准</div>
+            </div>
+            <div class="list-middle">
+              <div class="list-keywords" v-if="v.appendkey">
+                {{v.key.join(' ') + ' ' +  v.appendkey.join(' ')}}
+              </div>
+              <div class="list-keywords" v-else >{{v.key.join(' ')}}</div>
+              <p class="list-notkey" v-if="v.notkey && v.notkey.length > 0">排除词: {{v.notkey.join(' ')}}</p>
+            </div>
+            <div class="list-right">
+              <span class="icon-edit" @click="editKeyFn(v, i)"></span>
+              <span class="icon-delete" @click="deleteKeyFn(i)"></span>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div v-else>
+        <Edit
+            title="新增"
+            :datas="newWordsList"
+            :keywords="{key:[], appendkey:[],notkey:[],showForm: true,matchway:1}"
+            :keyIndex="newWordsList.length"
+            :is-nesting="isNesting"
+            @closeForm="addShowForm = false"
+          >
+          </Edit>
+      </div>
+    </div>
+    <!-- 删除关键词dialog -->
+    <el-dialog
+      :custom-class="isNesting ? 'nesting-dialog sub-dialog small-dialog' : 'sub-dialog small-dialog'"
+      :visible.sync="dialog.delKey"
+      :close-on-click-modal="true"
+      :show-close="false"
+      top="30vh"
+      center
+      width="380px"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+    >
+      <div class="delete-class">
+        <div class="delete-class-header">删除关键词组</div>
+        <div class="delete-class-content">确定删除该关键词组吗?</div>
+        <div class="delete-class-footer">
+          <el-button type="primary" class="confirm" @click="confirmDeleteKeyFn">确定</el-button>
+          <el-button class="cancel" @click="dialog.delKey = false">取消</el-button>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { Tooltip, Dialog, Input, Button } from 'element-ui'
+import Edit from './Edit'
+export default {
+  name: 'scope-list',
+  components: {
+    [Tooltip.name]: Tooltip,
+    [Dialog.name]: Dialog,
+    [Input.name]: Input,
+    [Button.name]: Button,
+    Edit
+  },
+  props: {
+    datas: {
+      title: String,
+      from: String,
+      keyList: Array
+    },
+    showMatch: {
+      type: Boolean,
+      default: true
+    },
+    isNesting: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      dialog: {
+        editKey: false, // 修改关键词弹框
+        delKey: false
+      },
+      // 传给dialog子组件的数据
+      props: {
+        ways: '', // 编辑还是新增
+        keyIndex: null, // 关键词下标
+        key: [], // 关键词
+        notkey: [], // 排除词
+        appendkey: [] // 附加词
+      },
+      newWordsList: [],
+      addShowForm: false,
+      delIndex: null
+    }
+  },
+  computed: {},
+  mounted () {
+    this.formatDatasList()
+  },
+  watch: {
+    'datas.keyList': function (newVal) {
+      if (newVal) {
+        this.formatDatasList()
+      }
+    }
+  },
+  methods: {
+    getCurEdit () {
+      const data = this.newWordsList
+      const t = data.some(s => {
+        return s.showForm
+      })
+      return t || this.addShowForm
+    },
+    // 排序
+    sortData (arr) {
+      return arr.sort((a, b) => {
+        return b.updatetime - a.updatetime
+      })
+    },
+    // 整理数据
+    formatDatasList () {
+      const lists = JSON.parse(JSON.stringify(this.datas.keyList))
+      this.sortData(lists)
+      lists.forEach(v => {
+        v.showForm = false
+      })
+      this.newWordsList = lists
+    },
+    onCloseForm (data) {
+      this.newWordsList[this.props.keyIndex].showForm = false
+    },
+    // 取消编辑
+    cancelEdit () {
+      this.dialog.editKey = false
+    },
+    // 添加关键词
+    addKeyFn () {
+      const t = this.getCurEdit()
+      if (t) {
+        return this.$toast('请先保存或取消正在操作的关键词组')
+      }
+      this.addShowForm = true
+    },
+    // 删除单个关键词(打开确认弹框)
+    deleteKeyFn (i) {
+      this.dialog.delKey = true
+      this.delIndex = i
+    },
+    confirmDeleteKeyFn () {
+      // 需用JSON.parse、JSON.stringify深拷贝原数组数据
+      const data = this.newWordsList
+      if (!data) return
+      data.splice(this.delIndex, 1)
+      this.$emit('updateKey', data)
+      this.dialog.delKey = false
+    },
+    // 编辑单个关键词
+    editKeyFn (v, i) {
+      const t = this.getCurEdit()
+      if (t) {
+        return this.$toast('请先保存或取消正在操作的关键词组')
+      } else {
+        this.props.ways = 'edit'
+        this.props.key = v.key
+        this.props.notkey = v.notkey
+        this.props.appendkey = v.appendkey
+        this.props.keyIndex = i
+        v.showForm = true
+      }
+    },
+    // 清空传值
+    clearPropsData () {
+      this.props.key = []
+      this.props.notkey = []
+      this.props.appendkey = []
+    },
+    // 修改关键词dialog 保存数据
+    submitKeywords () {
+      // 需用JSON.parse、JSON.stringify深拷贝原数组数据
+      const data = JSON.parse(JSON.stringify(this.datas.keyList))
+      const refs = this.$refs.keyEditRef.cur
+      const keyArr = this.getKeyTotalArray()
+      const type = this.props.ways
+      const obj = {
+        key: refs.key.split(' '),
+        notkey: refs.notkey,
+        appendkey: refs.appendkey
+      }
+      if (refs.key === '') {
+        return this.$message({
+          type: 'warning',
+          message: '关键词不能为空'
+        })
+      }
+      // 判断附加词是否重复
+      if (this.getArrIsRepeat(refs.appendkey)) {
+        return this.$message({
+          type: 'warning',
+          message: '设置的附加词重复,请调整后再添加'
+        })
+      }
+      // 判断排除词是否重复
+      if (this.getArrIsRepeat(refs.notkey)) {
+        return this.$message({
+          type: 'warning',
+          message: '设置的排除词重复,请调整后再添加'
+        })
+      }
+      if (type === 'add') {
+        if (keyArr.indexOf(refs.key) > -1) {
+          return this.$message({
+            type: 'warning',
+            message: '关键词不能重复'
+          })
+        }
+        data.push(obj)
+      } else if (type === 'edit') {
+        // 如果当前编辑的关键词和修改框里的关键词不相同 需校验是否重复
+        if (data[this.props.keyIndex].key.toString() !== refs.key) {
+          if (keyArr.indexOf(refs.key) > -1) {
+            return this.$message({
+              type: 'warning',
+              message: '关键词不能重复'
+            })
+          }
+        }
+        data[this.props.keyIndex].key = refs.key.split(' ')
+        data[this.props.keyIndex].notkey = refs.notkey
+        data[this.props.keyIndex].appendkey = refs.appendkey
+      }
+      // 通知父组件发请求修改并更新
+      this.$emit('updateKey', data)
+      this.dialog.editKey = false
+    },
+    // 获取所有关键词的key的属性,并返回一个数组(主要用于判断添加关键词是否重复)
+    getKeyTotalArray () {
+      const data = JSON.parse(JSON.stringify(this.datas.keyList))
+      const keysArr = []
+      data.forEach((s) => {
+        if (s && s.key && Array.isArray(s.key)) {
+          keysArr.push(s.key.toString().replace(',', ' '))
+        }
+      })
+      return keysArr
+    },
+    // 判断附加词、排除词是否重复
+    getArrIsRepeat (arr) {
+      this.removeEmptyInput(arr)
+      return (new Set(arr)).size !== arr.length
+    },
+    // 输入框为空移除
+    removeEmptyInput (arr) {
+      for (let i = 0; i < arr.length; i++) {
+        // eslint-disable-next-line
+        if (arr[i] === '' || arr[i] === null || typeof (arr[i]) === undefined) {
+          arr.splice(i, 1)
+          i = i - 1
+        }
+      }
+      return arr
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+// dialog 自定义class样式
+.sub-dialog{
+  border-radius: 8px;
+  .selector-card{
+    width: 800px;
+    height: auto;
+  }
+  .key-edit-content,.class-edit-content{
+    width: 740px;
+    padding: 30px 0;
+    height: auto;
+    max-height: 340px;
+    margin: 0 auto;
+    border: 1px solid #ececec;
+    overflow-y: scroll;
+    box-sizing: border-box;
+  }
+  .item{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 10px;
+  }
+  .item-label{
+    margin-right: 8px;
+    min-width: 78px;
+    height: 40px;
+    color: #1d1d1d;
+    font-size: 14px;
+    line-height: 40px;
+    text-align: right;
+  }
+  .item-label-required:before{
+    content: "*";
+    color: #f56c6c;
+    margin-right: 2px;
+  }
+  .item-value{
+    width: 352px;
+  }
+  .custom-long-input{
+    width: 352px;
+  }
+  .custom-short-input{
+    width: 170px;
+  }
+  .delete-class{
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 32px 32px 42px;
+    background: #FFFFFF;
+    border-radius: 6px;
+    .delete-class-header{
+      font-size: 18px;
+      line-height: 28px;
+      color: #1D1D1D;
+    }
+    .delete-class-content{
+      padding: 20px 0 42px;
+      color: #686868;
+      font-size: 14px;
+      line-height: 22px;
+    }
+    .delete-class-footer{
+      display: flex;
+      align-items: center;
+      .confirm,
+      .cancel {
+        padding: 10px 50px;
+        margin: 0 20px;
+      }
+      ::v-deep {
+        .el-button--primary {
+          color: #2cb7ca;
+          background: none;
+          border-color: #2cb7ca;
+          &:hover {
+            color: #fff;
+            background-color: #2cb7ca;
+          }
+        }
+        .el-button--default {
+          &:hover,
+          &:active {
+            color: #2cb7ca;
+            border-color: #2cb7ca;
+            background: #eaf8fa;
+          }
+        }
+      }
+    }
+  }
+}
+.classify{
+  .key-title{
+    padding: 26px 0;
+    font-size: 18px;
+    color: #1d1d1d;
+    line-height: 28px;
+    border-bottom: 1px solid #ececec;
+  }
+  .classify-list{
+    margin-bottom: 30px;
+    .icon-edit,.icon-delete{
+      display: inline-block;
+      width: 16px;
+      height: 16px;
+      background-repeat: no-repeat;
+      background-position: center center;
+      cursor: pointer;
+      &::before{
+        content: ''!important;
+      }
+    }
+    .icon-edit{
+      margin: 0 10px;
+      background-image: url('~@/assets/images/icon-edit.png');
+      background-size: contain;
+    }
+    .icon-delete{
+      background-image: url('~@/assets/images/icon-delete.png');
+      background-size: contain;
+    }
+    .classify-content{
+      display: flex;
+      align-items: center;
+      justify-content: flex-start;
+      flex-wrap: wrap;
+      .add-words-box{
+        width: 100%;
+        height: 38px;
+        margin-top: 20px;
+        line-height: 38px;
+        border: 1px dashed #2cb7ca;
+        border-radius: 6px;
+        color: #2cb7ca;
+        font-size: 14px;
+        text-align: center;
+        cursor: pointer;
+      }
+      .list-box{
+        width: 100%;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 12px 16px;
+        margin-top: 24px;
+        border: 1px solid #ececec;
+        border-radius: 8px;
+        box-sizing: border-box;
+        .list-left{
+          width: 40px;
+          height: 22px;
+          line-height: 22px;
+          margin-right: 12px;
+          font-size: 12px;
+          text-align: center;
+          box-sizing: border-box;
+        }
+        .yellow-box{
+          border: 1px solid #ff9f40;
+          color: #ff9f40;
+          border-radius: 4px;
+        }
+        .blue-box{
+          border: 1px solid #2cb7ca;
+          color: #2cb7ca;
+          border-radius: 4px;
+        }
+        .list-middle{
+          flex: 1;
+        }
+        .list-keywords{
+          color: #1d1d1d;
+          line-height: 22px;
+          font-size: 14px;
+        }
+        .list-addkey,
+        .list-notkey{
+          color: #686868;
+          line-height: 20px;
+          font-size: 12px;
+        }
+        .list-right{
+          margin-left:15px;
+        }
+      }
+      .words-add{
+        width: 162px;
+        height: 80px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 24px;
+        border: 1px solid #ececec;
+        margin: 10px 0 0 0;
+        border-radius: 9px;
+        color: #c4c4c4;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 202 - 0
src/components/scope/index.vue

@@ -0,0 +1,202 @@
+<template>
+  <div class="scope-config">
+    <!-- 关键词列表 -->
+    <ScopeList :datas="setData" :is-nesting="true" @updateKey="updateKeyWordsApi"></ScopeList>
+    <div class="reset-tips">新增的关键词设置,将在登出时还原</div>
+  </div>
+</template>
+
+<script>
+import ScopeList from './components/List'
+import { mapActions, mapState } from 'vuex'
+import $bus from '@/utils/bus'
+export default {
+  name: 'scope',
+  components: {
+    ScopeList
+  },
+  data () {
+    return {
+      setData: {
+        title: '业务范围设置',
+        keyList: []
+      }
+    }
+  },
+  computed: {
+    ...mapState({
+      userInfo: state => state.user.info,
+      scope: state => state.user.scope,
+      isDeleteAllScope: state => state.user.isDeleteAllScope,
+      vipScope: state => state.user.vipScope,
+      isDeleteVipScope: state => state.user.isDeleteVipScope
+    })
+  },
+  watch: {
+    'setData.list': function (newVal) {
+      if (newVal) {
+        this.sortData(newVal)
+      }
+    }
+  },
+  created () {
+    this.getScopeInfo()
+  },
+  mounted () {
+    $bus.$on('updateScope', (data) => {
+      this.setData.keyList = data
+    })
+  },
+  methods: {
+    ...mapActions('user', ['getKeywordsList', 'getSvipUserSubscribeList']),
+    // 从接口中取数据 执行业务范围页面逻辑
+    async getScopeInfo () {
+      // 区分大会员、超级订阅关键词
+      let state
+      let isClear
+      if (this.userInfo.memberStatus > 0) {
+        state = this.scope
+        isClear = this.isDeleteAllScope
+      } else if (this.userInfo.vipStatus > 0) {
+        state = this.vipScope
+        isClear = this.isDeleteVipScope
+      }
+      if (state || isClear) {
+        state.forEach((v, i) => {
+          v.showForm = false
+          v.iidex = i
+          if (!v.updatetime) {
+            v.updatetime = 0
+          }
+        })
+        this.setData.keyList = state
+      } else {
+        if (this.userInfo.memberStatus > 0) {
+          this.getKeywordsList().then((res) => {
+            this.setData.keyList = res || []
+          })
+        } else if (this.userInfo.vipStatus > 0) {
+          // this.getSvipUserSubscribeList()
+        }
+      }
+    },
+    sortData (arr) {
+      return arr.sort((a, b) => {
+        return b.updatetime - a.updatetime
+      })
+    },
+    formatKeyList (arr) {
+      if (!arr) return
+      const newArr = []
+      arr.forEach((v) => {
+        if (v.a_key) {
+          v.a_key.forEach((s) => {
+            newArr.push(s)
+          })
+        }
+      })
+      return newArr
+    },
+    // 子组件通知父组件更新关键词接口
+    updateKeyWordsApi (data) {
+      console.log(data, '子组件传来的修改完缓存里的数据')
+      this.setData.keyList = data
+    },
+    // 保存设置
+    saveSetting () {
+      if (this.userInfo.memberStatus > 0) {
+        this.$store.commit('user/clearScope', true)
+        this.$store.commit('user/setScope', this.setData.keyList)
+      } else if (this.userInfo.vipStatus > 0) {
+        this.$store.commit('user/clearVipScope', true)
+        this.$store.commit('user/setVipScope', this.setData.keyList)
+      }
+    },
+    // 数组合并去重用于将新增的关键词组合并到本地缓存中
+    unique (arr) {
+      const res = new Map()
+      const newArr = arr.filter((item) => !res.has(item.key.toString()) && res.set(item.key.toString(), 1))
+      return newArr
+    }
+  }
+
+}
+</script>
+
+<style lang="scss">
+.scope-config{
+  padding: 0 30px;
+  background: #fff;
+  border-radius: 5px;
+  .reset-tips{
+    width: 100%;
+    height: 32px;
+    line-height: 32px;
+    color: #FF9F40;
+    font-size: 13px;
+    background: rgb(255, 159, 64, 0.1);
+    text-align: center;
+  }
+  .save-btn,
+  .cancle-btn{
+    display: block;
+    flex: 1;
+    height: 46px;
+    margin: 20px auto;
+    line-height: 46px;
+    color: #fff;
+    background: #2CB7CA;
+    border-radius: 6px;
+    text-align: center;
+    cursor: pointer;
+    font-size: 16px;
+    border: 0;
+    &:disabled{
+      opacity: 0.5;
+      cursor: not-allowed;
+    }
+  }
+  .confirm-btn{
+    margin: 20px 0;
+  }
+  .cancle-btn{
+    margin-left: 20px;
+    background: #fff;
+    border: 1px solid #2cb7ca;
+    color: #2CB7CA;
+  }
+}
+.small-dialog{
+  border-radius: 8px;
+  &.nesting-dialog{
+    position: absolute;
+    right: 100px;
+  }
+  .dialog-text{
+    margin-top:4px;
+    font-size:14px;
+    color:#686868;
+    line-height:22px;
+    text-align:center;
+  }
+  .know-btn{
+    background: #2cb7ca;
+    border-radius: 6px;
+    border:0;
+  }
+  .dialog-update-black{
+    color: #1D1D1D;
+    line-height: 22px;
+    text-align: justify;
+  }
+  .dialog-update-gray{
+    margin-top: 20px;
+    color: #686868;
+    line-height: 22px;
+    text-align: justify;
+  }
+}
+.sub-dialog .el-dialog__header,.sub-dialog .el-dialog__body{
+  padding: 0;
+}
+</style>

+ 29 - 0
src/components/selector/AreaSelector.vue

@@ -2,6 +2,7 @@
   <selector-card
     class="area-selector"
     :cardType="selectorType"
+    :confirmDisabled="confirmDisabled"
     @onConfirm="onConfirm"
     @onCancel="onCancel">
     <div slot="header" :class="{ 's-header': selectorType === 'line' }">
@@ -17,6 +18,9 @@
       :initCityMap="initCityMap"
       :beforeTabClick="beforeTabClick"
       :comtype="comtype"
+      :areaCount="areaCount"
+      :selectedDetail="selectedDetail"
+      @selectedCount="onSelectedArea"
       @onChange="onChange"
     />
   </selector-card>
@@ -65,11 +69,32 @@ export default {
       default () {
         return {}
       }
+    },
+    // 买了几个省份,-1代表全国
+    areaCount: {
+      type: Number,
+      default: -1
+    },
+    // 是否显示选择的省市详情
+    selectedDetail: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data () {
+    return {
+      selectedAreaCount: 0
     }
   },
   computed: {
     content () {
       return this.$refs.content
+    },
+    confirmDisabled () {
+      const noAll = this.areaCount < this.selectedAreaCount && this.selectedAreaCount !== -1
+      const isAll = this.selectedAreaCount === -1 && this.areaCount !== -1
+      // console.log((noAll || isAll) && this.areaCount === -1)
+      return (noAll || isAll) && this.areaCount !== -1
     }
   },
   created () {},
@@ -92,6 +117,10 @@ export default {
     },
     onChange (selected) {
       this.$emit('onChange', selected)
+    },
+    onSelectedArea (data) {
+      this.selectedAreaCount = data
+      this.$emit('selectedCount', data)
     }
   }
 }

+ 67 - 13
src/components/selector/AreaSelectorContent.vue

@@ -1,15 +1,24 @@
 <template>
   <div class="selector-content" v-if="selectorType === 'card'" key="s-content">
+    <div v-if="showSelectResult">
+      <div class="result-container">
+        <div class="result-label">可选:</div>
+        <div class="result-content">
+          <div class="content-count">{{ canSelectedCount }}</div>
+        </div>
+      </div>
+      <div class="result-container">
+        <div class="result-label">已选:</div>
+        <div class="result-content">
+          <div class="content-count">{{ selectedCount.pName }}</div>
+          <div class="content-detail" v-show="selectedDetail">{{ selectedCount.detail }}</div>
+          <p class="update-tips" v-if="isUpdate" @click="onUpdateVip">超出已购买省份数量,您可前往升级超级订阅> </p>
+        </div>
+      </div>
+    </div>
     <div class="search-container" v-if="showSearchInput">
       <el-input v-model.trim="searchContent" placeholder="搜索" prefix-icon="el-icon-search"></el-input>
     </div>
-    <div class="result-container" v-if="showSelectResult">
-      <div class="result-label">已选择:</div>
-      <div class="result-content">
-        <div class="content-count">{{ selectedCount.count }}</div>
-        <div class="content-detail">{{ selectedCount.detail }}</div>
-      </div>
-    </div>
     <div class="select-list scrollbar" ref="selectList">
       <div v-for="(item, key) in provinceListMap" :key="key" class="select-group-container">
         <div class="index-anchor" :id="key" :data-index="key" v-if="key !== '#'">{{ key }}</div>
@@ -121,7 +130,7 @@ import 'element-ui/lib/theme-chalk/base.css'
 import CollapseTransition from 'element-ui/lib/transitions/collapse-transition'
 import chinaMapJSON from '@/assets/js/china_area.js'
 import { provinceListMapExp } from '@/assets/js/selector.js'
-import { debounce, getRandomString } from '@/utils/'
+import { debounce, getRandomString, openSelfLink } from '@/utils/'
 export default {
   name: 'area-selector-content',
   components: {
@@ -182,6 +191,13 @@ export default {
           // '重庆': []
         }
       }
+    },
+    // 买了几个省份,-1代表全国
+    areaCount: Number,
+    // 是否显示选择的省市详情
+    selectedDetail: {
+      type: Boolean,
+      default: true
     }
   },
   data () {
@@ -242,11 +258,30 @@ export default {
         }
         detail = arr.join('、')
       }
-
+      // eslint-disable-next-line
+      // const pStr = detail ? detail.replace(/\([^\)]*\)/g, '') : ''
+      const pCount = zh.text === '全国' ? -1 : Object.keys(selected).length
+      this.$emit('selectedCount', pCount)
       return {
         count: zh.text,
-        detail
+        detail,
+        pCount,
+        pName: zh.province
       }
+    },
+    // 可选多少个省
+    canSelectedCount () {
+      if (this.areaCount === -1) {
+        return '全国'
+      } else {
+        return `${this.areaCount}个省`
+      }
+    },
+    isUpdate () {
+      const fAll = this.areaCount !== -1 && this.selectedCount.pCount > this.areaCount
+      const iAll = this.areaCount !== -1 && this.selectedCount.pCount === -1
+      // console.log(this.areaCount, this.selectedCount.pCount)
+      return fAll || iAll
     }
   },
   watch: {
@@ -469,7 +504,7 @@ export default {
 
       // 求平均值,谁的值大于平均值,就在哪一行
       let insetedLine = 0
-      const avg = tolerance.reduce((prev, item) => prev + item) / tolerance.length
+      const avg = tolerance.length > 0 ? tolerance.reduce((prev, item) => prev + item) / tolerance.length : 0
       for (let j = 0; j < tolerance.length; j++) {
         if (tolerance[j] > avg) {
           insetedLine = j + 1
@@ -850,6 +885,7 @@ export default {
       }
 
       const selected = this.getSelectedCity()
+      const provinceArr = []
       if (Object.keys(selected).length === 0) {
         // 全国
       } else {
@@ -857,11 +893,13 @@ export default {
         for (const p in selected) {
           if (selected[p].length === 0) {
             selectedCount.province++
+            provinceArr.push(p)
           } else {
             selectedCount.city.pCount++
             selected[p].forEach(() => {
               selectedCount.city.totalCount++
             })
+            provinceArr.push(`${p}(${selected[p].length}个市)`)
           }
         }
       }
@@ -870,7 +908,8 @@ export default {
         p: selectedCount.province === 0 ? '' : selectedCount.province + '个省',
         c: selectedCount.city.totalCount === 0 ? '' : selectedCount.city.totalCount + '个市',
         s: selectedCount.city.pCount === 1 ? '' : '(分布在' + selectedCount.city.pCount + '个省内)',
-        text: ''
+        text: '',
+        province: Object.keys(selected).length === 0 ? '全国' : `${Object.keys(selected).length}个省: ${provinceArr.join('、')}`
       }
 
       if (selectedCount.country === 1) {
@@ -891,6 +930,12 @@ export default {
         data: selectedCount,
         zh: tipText
       }
+    },
+    onUpdateVip () {
+      const routeUrl = this.$router.resolve({
+        path: '/free/svip/buy?type=upgrade'
+      })
+      openSelfLink(routeUrl)
     }
   }
 }
@@ -929,9 +974,12 @@ export default {
     .result-container {
       display: flex;
       justify-content: space-between;
-      padding: 20px;
+      padding: 0 20px;
       font-size: 14px;
       line-height: 22px;
+      &:nth-child(1) {
+        margin-top: 10px;
+      }
       .result-label {
         color: #686868;
       }
@@ -944,6 +992,12 @@ export default {
       .content-detail {
         color: $color-text--highlight;
       }
+      .update-tips{
+        color: #FF3A20;
+        font-size: 14px;
+        line-height: 22px;
+        cursor: pointer;
+      }
     }
 
     .select-group {

+ 1 - 1
src/components/selector/BusinessScopeSelectorContent.vue

@@ -11,7 +11,7 @@
       <div
         v-for="(item, index) in bScopeList"
         :key="index"
-        class="j-button-item bgc"
+        class="j-button-item bgc hover"
         :class="{
           active: item.selected,
           all: item.name === '全部'

+ 5 - 0
src/components/selector/BuyerclassSelector.vue

@@ -13,6 +13,7 @@
       :otherMatch="otherMatch"
       :selectorType="selectorType"
       :initCate="initCate"
+      :getContainer="getContainer"
       @onChange="onChange"
     />
   </selector-card>
@@ -45,6 +46,10 @@ export default {
     otherMatch: {
       type: Number,
       default: 1
+    },
+    getContainer: {
+      type: String,
+      default: '.config > .subscribe'
     }
   },
   data () {

+ 9 - 2
src/components/selector/BuyerclassSelectorContent.vue

@@ -36,14 +36,14 @@
       </div>
     </div>
     <el-dialog
-      custom-class="buyer-dialog"
+      custom-class="buyer-dialog small-dialog nesting-dialog r100"
       title="匹配未分类类型"
       ref="matchDialog"
-      v-component-change-mount="{ selector: '.config > .subscribe' }"
       :visible.sync="dialogTip"
       width="24%"
       center
       :show-close="false"
+      v-component-change-mount="{ selector: getContainer }"
       >
       <p style="font-size:14px;color:#1D1D1D;line-height:22px;">每条信息的采购单位类型属性由机器自动识别,会存在少数无法识别进行分类的情况。</p>
       <p style="margin-top:4px;font-size:14px;color:#686868;line-height:22px;">注:选择全部类型时,未分类类型默认被选中,关闭无效</p>
@@ -102,6 +102,10 @@ export default {
     otherMatch: {
       type: Number,
       default: 1
+    },
+    getContainer: {
+      type: String,
+      default: '.config > .subscribe'
     }
   },
   data () {
@@ -310,6 +314,9 @@ export default {
       height: 18px;
       margin-left: 2px;
       cursor: pointer;
+      ::before{
+        content: ''!important;
+      }
     }
   }
 

+ 4 - 4
src/components/selector/InfoTypeSelectorContent.vue

@@ -37,7 +37,7 @@
               <div>
                 <span class="remindtwo"><span>"采购意向"</span>是指未发布招标公告前1-3个月,政府单位的采购意向信息,包含采购内容、预算金额、预计采购时间、采购联系人及联系方式等相关信息。</span>
               </div>
-              <i style="cursor: pointer" class="icon-help" slot="reference"></i>
+              <i style="cursor: pointer" class="icon-help-img" slot="reference"></i>
             </el-popover>
         </div>
       </div>
@@ -74,7 +74,7 @@
           <div>
             <span class="remindtwo"><span>"拟建项目"</span>是指那些处于前期立项、审批阶段的项目。供应商应在立项阶段掌握项目信息,做到早介入,稳拿单。</span>
           </div>
-          <i style="cursor: pointer" class="icon-help" slot="reference"></i>
+          <i style="cursor: pointer" class="icon-help-img" slot="reference"></i>
         </el-popover>
         <el-popover
           v-if="item.name === '采购意向'"
@@ -85,7 +85,7 @@
           <div>
             <span class="remindtwo"><span>"采购意向"</span>是指未发布招标公告前1-3个月,政府单位的采购意向信息,包含采购内容、预算金额、预计采购时间、采购联系人及联系方式等相关信息。</span>
           </div>
-          <i style="cursor: pointer" class="icon-help" slot="reference"></i>
+          <i style="cursor: pointer" class="icon-help-img" slot="reference"></i>
         </el-popover>
       </div>
     </div>
@@ -355,7 +355,7 @@ export default {
 }
 </style>
 <style lang="scss" scoped>
-  .icon-help {
+  .icon-help-img {
     display: inline-block;
     width: 18px;
     height: 18px;

+ 76 - 0
src/components/selector/MedicalSelector.vue

@@ -0,0 +1,76 @@
+<template>
+  <selector-card
+    class="medical-selector"
+    :cardType="selectorType"
+    @onConfirm="onConfirm"
+    @onCancel="onCancel"
+  >
+    <div slot="header" :class="{ 's-header': selectorType === 'line' }">
+      <slot name="header">{{ header }}</slot>
+    </div>
+    <MedicalSelectorContent
+      ref="content"
+      :selectorType="selectorType"
+      :initCate="initCate"
+      :isWeight="isWeight"
+      @onChange="onChange"
+    />
+  </selector-card>
+</template>
+
+<script>
+import SelectorCard from '@/components/selector/SelectorCard.vue'
+import MedicalSelectorContent from '@/components/selector/MedicalSelectorContent.vue'
+export default {
+  name: 'medical-selector',
+  components: {
+    SelectorCard,
+    MedicalSelectorContent
+  },
+  props: {
+    header: {
+      type: String,
+      default: ''
+    },
+    selectorType: {
+      type: String,
+      default: 'card' // card/line
+    },
+    initCate: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    isWeight: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data () {
+    return {}
+  },
+  methods: {
+    setCateState (data) {
+      return this.$refs.content.setInfoTypeState(data)
+    },
+    getSelected () {
+      return this.$refs.content.getSelected()
+    },
+    onCancel () {
+      this.$emit('onCancel')
+    },
+    onConfirm () {
+      const selected = this.getSelected()
+      this.$emit('onConfirm', selected)
+    },
+    onChange (selected) {
+      this.$emit('onChange', selected)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

File diff suppressed because it is too large
+ 319 - 0
src/components/selector/MedicalSelectorContent.vue


+ 1 - 0
src/components/selector/PopSelector.vue

@@ -536,6 +536,7 @@ export default {
       text-overflow: ellipsis;
       white-space: nowrap;
       text-align: justify;
+      max-width: 14em;
       &.checked {
         color: #fff;
         background: #2cb7ca;

+ 108 - 0
src/components/selector/PushTimeSelector.vue

@@ -0,0 +1,108 @@
+<template>
+  <div class="push-time-selector">
+    <div class="push-time-title" v-if="title">{{ title }}</div>
+    <div class="spec-container">
+      <SpecCard v-for="(item, index) in list" :key="index" :active="item[activeType] === active" @onClick="clickSpec(item)">
+        <div class="spec-c-title" v-html="item.title"></div>
+        <div class="spec-c-label">{{ item.label }}</div>
+      </SpecCard>
+    </div>
+  </div>
+</template>
+
+<script>
+import SpecCard from '@/components/common/SpecCard'
+export default {
+  name: 'pushTimeSelector',
+  props: {
+    title: {
+      type: String,
+      default: '订阅推送时间'
+    },
+    active: [String, Number, Boolean],
+    activeType: {
+      type: String,
+      default: 'value'
+    },
+    list: {
+      type: Array,
+      default () {
+        return []
+      }
+    }
+  },
+  components: {
+    SpecCard
+  },
+  model: {
+    prop: 'active',
+    event: 'activeChange'
+  },
+  computed: {
+    activeItem () {
+      return this.getActiveItem()
+    }
+  },
+  watch: {
+    active: function () {
+      this.$emit('change', this.activeItem)
+    }
+  },
+  created () {
+    this.getActiveItem()
+  },
+  methods: {
+    clickSpec (item) {
+      this.$emit('activeChange', item.value)
+    },
+    getActiveItem () {
+      return this.list.find(spec => spec[this.activeType] === this.active)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.push-time-selector{
+  .push-time-title{
+    padding: 32px 0 0;
+  }
+  .spec-container{
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-start;
+    .spec-c-title{
+      color: #1D1D1D;
+      font-size: 14px;
+      line-height: 18px;
+    }
+    .spec-c-label{
+      font-size: 12px;
+      line-height: 16px;
+      color: #999999;
+    }
+  }
+  ::v-deep {
+    .spec-card {
+      width: 176px;
+      height: 80px;
+      border: 1px solid #E0E0E0;
+      background: #fff;
+      border-radius: 4px;
+      align-items: inherit;
+      justify-content: inherit;
+      &.active{
+        border-width: 2px;
+        border-color: #2abed1;
+      }
+    }
+    .spec-card {
+      margin-right: 10px;
+      margin-top: 12px;
+    }
+    .spec-card:nth-child(3n) {
+      margin-right: 0;
+    }
+  }
+}
+</style>

+ 8 - 1
src/components/selector/SelectorCard.vue

@@ -14,7 +14,7 @@
     </div>
     <div class="selector-card-footer">
       <slot name="footer">
-        <el-button v-if="cardType === 'card'" type="primary" class="confirm" @click="onConfirm">{{ confirmText }}</el-button>
+        <el-button v-if="cardType === 'card'" type="primary" class="confirm" :disabled="confirmDisabled" @click="onConfirm">{{ confirmText }}</el-button>
         <el-button v-if="cardType === 'card'" class="cancel" @click="onCancel">{{ cancelText }}</el-button>
       </slot>
     </div>
@@ -43,6 +43,10 @@ export default {
     cancelText: {
       type: String,
       default: '取消'
+    },
+    confirmDisabled: {
+      type: Boolean,
+      default: false
     }
   },
   methods: {
@@ -164,6 +168,9 @@ export default {
           &:hover {
             color: #fff;
             background-color: #2cb7ca;
+            &:disabled{
+              opacity: 0.6;
+            }
           }
         }
         .el-button--default {

+ 201 - 0
src/components/subscribe-manager/KeyConfig.vue

@@ -0,0 +1,201 @@
+<template>
+  <div class="keywords-config">
+    <div class="key-title">
+      <div>
+        <span>关键词组设置</span>
+        <span style="font-size:14px;"><em style="color: #2cb7ca;"> {{keyCounts}}</em>/{{ maxCount }}</span>
+        <p style="font-size:14px;color: #2cb7ca;">注:任意1组关键词组匹配成功即推送相关信息</p>
+      </div>
+      <div class="add-classify" @click="addClassifyFn"><i class="el-icon-plus"></i> 新增分类</div>
+    </div>
+    <div class="key-content">
+      <KeyList ref="keyConfigRef" :list="keywordsList" :max-count="maxCount" :key="sort" @update="onUpdateKey"></KeyList>
+    </div>
+    <el-dialog
+      custom-class="sub-dialog small-dialog nesting-dialog r70"
+      :visible.sync="add.dialog"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+    >
+      <KeyCard @onCancel="add.dialog = false" @onConfirm="confirmEditClassFn">
+        <div slot="header">新增关键词分类</div>
+        <div class="class-edit-content">
+          <div class="item">
+            <div class="item-label">关键词分类:</div>
+            <div class="item-value">
+              <el-input class="custom-long-input" v-model.trim="add.className"  maxlength="20" placeholder="请输入关键词分类"></el-input>
+            </div>
+          </div>
+        </div>
+      </KeyCard>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { Input, Button, Dialog, RadioGroup, Radio } from 'element-ui'
+import KeyCard from '@/components/selector/SelectorCard'
+import KeyList from './components/List.vue'
+export default {
+  name: 'keyConfig',
+  props: {
+    list: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    maxCount: Number
+  },
+  components: {
+    [Input.name]: Input,
+    [Dialog.name]: Dialog,
+    [Button.name]: Button,
+    [RadioGroup.name]: RadioGroup,
+    [Radio.name]: Radio,
+    KeyCard,
+    KeyList
+  },
+  data () {
+    return {
+      keywordsList: this.list,
+      add: {
+        dialog: false,
+        className: ''
+      },
+      sort: 1
+    }
+  },
+  computed: {
+    keyCounts () {
+      let count = 0
+      this.keywordsList.forEach(v => {
+        if (v && v.a_key) {
+          count += v.a_key.length
+        }
+      })
+      return count
+    }
+  },
+  methods: {
+    onUpdateKey (data) {
+      this.keywordsList = data
+      this.$emit('update', this.keywordsList)
+    },
+    parentGetCurEdit () {
+      const t = this.$refs.keyConfigRef.getCurEdit()
+      return t
+    },
+    // 新增关键词分类弹框
+    addClassifyFn () {
+      const t = this.parentGetCurEdit()
+      if (t) return this.$toast('请先保存或取消正在操作的关键词组')
+      this.add.className = ''
+      this.add.dialog = true
+    },
+    // 确认添加分类
+    confirmEditClassFn () {
+      const classArr = this.getClassArray()
+      const list = this.keywordsList
+      if (classArr.indexOf(this.add.className) > -1) {
+        return this.$message({
+          type: 'warning',
+          message: '分类名不能重复'
+        })
+      } else if (this.add.className === '') {
+        return this.$message({
+          type: 'warning',
+          message: '分类名不能为空'
+        })
+      }
+      if (list.length === 1 && list[0].s_item === '未分类') {
+        list[0].s_item = this.add.className
+        list[0].updatetime = parseInt(Date.now() / 1000)
+      } else {
+        list.push({
+          s_item: this.add.className,
+          a_key: [],
+          showForm: false,
+          updatetime: parseInt(Date.now() / 1000)
+        })
+      }
+      this.add.dialog = false
+      this.sort = Date.now()
+      this.$refs.keyConfigRef.$forceUpdate()
+      this.$emit('update', this.keywordsList)
+    },
+    // 获取所有分类名
+    getClassArray () {
+      const data = this.keywordsList
+      const classArr = []
+      data.forEach((v) => {
+        if (v.s_item) {
+          classArr.push(v.s_item)
+        }
+      })
+      return classArr
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.keywords-config{
+  margin-top: 12px;
+  padding: 0 30px;
+  background: #fff;
+  .key-title{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 13px 0 8px;
+    font-size: 18px;
+    color: #1d1d1d;
+    line-height: 28px;
+    border-bottom: 1px solid #ececec;
+    .add-classify{
+      width: 110px;
+      height: 38px;
+      line-height: 38px;
+      border: 1px solid #2cb7ca;
+      border-radius: 6px;
+      text-align: center;
+      color: #2CB7CA;
+      font-size: 14px;
+      cursor: pointer;
+    }
+  }
+  .key-content{
+    padding: 20px 0;
+    .classify-title{
+      display: flex;
+      align-items: center;
+      .title-text{
+        font-size: 16px;
+        color: #1d1d1d;
+      }
+      .icon-edit,.icon-delete{
+        display: inline-block;
+        width: 16px;
+        height: 16px;
+        background-repeat: no-repeat;
+        background-position: center center;
+        cursor: pointer;
+        &::before{
+          content: ''!important;
+        }
+      }
+      .icon-edit{
+        margin: 0 10px;
+        background-image: url('~@/assets/images/icon-edit.png');
+        background-size: contain;
+      }
+      .icon-delete{
+        background-image: url('~@/assets/images/icon-delete.png');
+        background-size: contain;
+      }
+    }
+  }
+}
+</style>

+ 254 - 0
src/components/subscribe-manager/PushConfig.vue

@@ -0,0 +1,254 @@
+<template>
+  <div class="push-config">
+    <div class="push-title">推送设置</div>
+    <div class="push-content">
+      <PushTimeSelector :list="rateModeList" v-model="active" @activeChange="onSpecChange"></PushTimeSelector>
+      <div class="push-way-container">
+        <div class="push-item-label">推送方式:</div>
+        <span class="switch-item">
+          <em>APP提醒</em>
+          <el-switch
+            :disabled="!mailModel"
+            v-model="appModel"
+            :width="44"
+            :active-value="1"
+            :inactive-value="0"
+            active-text="关"
+            inactive-text="开"
+            active-color="#2cb7ca"
+            inactive-color="#bfbfbf"
+            @change="onSwitchApp($event)"
+          >
+          </el-switch>
+        </span>
+        <span class="switch-item">
+          <em>邮件提醒</em>
+          <el-switch
+            :disabled="!appModel"
+            v-model="mailModel"
+            :width="44"
+            :active-value="1"
+            :inactive-value="0"
+            active-text="关"
+            inactive-text="开"
+            active-color="#2cb7ca"
+            inactive-color="#bfbfbf"
+            @change="onSwitchMail($event)"
+          >
+          </el-switch>
+        </span>
+      </div>
+      <div class="email-container" v-if="mailModel">
+        <div class="push-item-label">邮箱地址:</div>
+        <span class="email-text" v-if="mailVal">{{ mailVal || '暂未设置邮箱' }}</span>
+        <span class="email-edit" @click.stop="onEditEmail">编辑</span>
+      </div>
+    </div>
+    <el-dialog
+      custom-class="sub-dialog nesting-dialog email-dialog r100"
+      :visible.sync="showEmail"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      title="设置邮箱地址"
+      width="380px"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+    >
+      <el-input v-model="mailInput" placeholder="请输入邮箱" @input="onMailInput"></el-input>
+      <p class="error-tips" v-show="mailDisabled">邮箱格式不正确</p>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="onConfirmMail" :disabled="mailDisabled">确 定</el-button>
+        <el-button @click="showEmail = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { Switch, Input, Dialog, Button } from 'element-ui'
+import PushTimeSelector from '@/components/selector/PushTimeSelector'
+export default {
+  name: 'pushConfig',
+  props: {
+    rateModel: [String, Number],
+    mailPush: [String, Number, Boolean],
+    appPush: [String, Number, Boolean],
+    mail: String
+  },
+  components: {
+    [Switch.name]: Switch,
+    [Input.name]: Input,
+    [Dialog.name]: Dialog,
+    [Button.name]: Button,
+    PushTimeSelector
+  },
+  data () {
+    return {
+      active: this.rateModel,
+      // 推送时间
+      rateModeList: [
+        {
+          value: 1,
+          title: '实时推送',
+          label: '上午8点至晚上10点不定时推送'
+        },
+        {
+          value: 2,
+          title: '每日推送<span class="highlight-text"> 1 </span>次',
+          label: '上午9点推送一次信息'
+        },
+        {
+          value: 5,
+          title: '每日推送<span class="highlight-text"> 2 </span>次',
+          label: '上午9点推送一次信息,下午2点推送一次信息'
+        },
+        {
+          value: 3,
+          title: '每周推送',
+          label: '每周五上午9点推送一次信息'
+        },
+        {
+          value: 4,
+          title: '每月推送',
+          label: '每月28日上午9点推送一次信息'
+        }
+      ],
+      appModel: this.appPush,
+      mailModel: this.mailPush,
+      mailVal: this.mail,
+      mailInput: this.mail,
+      showEmail: false
+    }
+  },
+  computed: {
+    mailDisabled () {
+      return !(/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(this.mailInput))
+    }
+  },
+  methods: {
+    onSwitchMail (event) {
+      this.mailModel = event
+      this.saveCommonData()
+    },
+    onSwitchApp (event) {
+      this.appModel = event
+      this.saveCommonData()
+    },
+    onEditEmail () {
+      this.showEmail = true
+    },
+    onMailInput (val) {
+      this.mailInput = val
+    },
+    onConfirmMail () {
+      this.mailVal = this.mailInput
+      this.showEmail = false
+      this.saveCommonData()
+    },
+    onSpecChange (data) {
+      this.active = data
+      this.saveCommonData()
+    },
+    saveCommonData () {
+      const data = {
+        mail: this.mailVal,
+        mailpush: this.mailModel,
+        apppush: this.appModel,
+        ratemode: this.active
+      }
+      this.$emit('update', data)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.push-config{
+  margin-top: 12px;
+  padding-bottom: 80px;
+  background: #fff;
+  .push-title{
+    padding: 26px 30px;
+    font-size: 18px;
+    color: #1d1d1d;
+    line-height: 28px;
+    border-bottom: 1px solid #ececec;
+  }
+  .push-content{
+    padding: 0 0 0 30px;
+  }
+  .push-item-label{
+    margin-right: 8px;
+    color: #1D1D1D;
+    font-size: 14px;
+    line-height: 22px;
+    flex-shrink: 0;
+  }
+  .push-way-container {
+    display: flex;
+    align-items: center;
+    margin-top: 24px;
+    .switch-item{
+      display: flex;
+      align-items: center;
+      margin-right: 24px;
+      font-size: 14px;
+      line-height: 22px;
+      > em{
+        margin-right: 8px;
+      }
+    }
+  }
+  .email-container{
+    display: flex;
+    align-items: center;
+    margin-top: 24px;
+    .email-text,
+    .email-edit{
+      margin-right: 24px;
+      color: #999999;
+      font-size: 14px;
+      line-height: 24px;
+    }
+    .email-edit{
+      color: #2ABED1;
+      cursor: pointer;
+    }
+  }
+  ::v-deep {
+    .el-switch{
+      position: relative;
+    }
+    .el-switch__label--left {
+      position: absolute;
+      left: 6px;
+      top: 0;
+      color: #fff;
+      z-index: 9;
+      font-size: 12px;
+    }
+    .el-switch__label--right {
+      position: absolute;
+      right: 6px;
+      top: 0;
+      color: #fff;
+      z-index: 9;
+      font-size: 12px;
+    }
+    .el-switch__label *{
+      font-size: 12px;
+    }
+    .el-switch,.el-switch__core{
+      height: 22px;
+      line-height: 22px;
+    }
+    .el-switch__core:after{
+      width: 18px;
+      height: 18px;
+    }
+    .el-switch.is-checked .el-switch__core::after{
+      margin-left: -18px!important;
+    }
+  }
+}
+</style>

+ 409 - 0
src/components/subscribe-manager/SubConfig.vue

@@ -0,0 +1,409 @@
+<template>
+  <div class="subscribe-config">
+    <div class="sub-title">订阅设置</div>
+    <div class="sub-content">
+      <div class="item">
+        <div class="item-label item-label-required">区域:</div>
+        <div class="item-value" @click="dialog.area = true">
+          <el-input class="custom-long-input" v-model="areaStr" readonly unselectable="on"></el-input>
+        </div>
+      </div>
+      <div class="item">
+        <div class="item-label item-label-required">采购单位类型:</div>
+        <div class="item-value" @click="dialog.buyClass = true">
+          <el-input class="custom-long-input" v-model="buyClassStr" readonly unselectable="on"></el-input>
+        </div>
+      </div>
+      <div class="item">
+        <div class="item-label item-label-required">信息类型:</div>
+        <div class="item-value"  @click="dialog.infoType = true">
+          <el-input class="custom-long-input" v-model="infoTypeStr" readonly unselectable="on"></el-input>
+        </div>
+      </div>
+      <div class="item-other">
+        <div class="item-label">项目匹配:</div>
+        <div class="item-value item-other-value">
+          <el-switch
+            @change="switchProjectMatch($event)"
+            v-model="projectMatchData"
+            :width="44"
+            :active-value="1"
+            :inactive-value="0"
+            active-text="关"
+            inactive-text="开"
+            active-color="#2cb7ca"
+            inactive-color="#bfbfbf">
+          </el-switch>
+          <p class="math-tips">开启后,系统将根据你订阅的关键词自动匹配出相关联的项目,并将相关连项目的后续动态在超级订阅内一并推送。</p>
+        </div>
+      </div>
+      <div class="item-other">
+        <div class="item-label">关键词匹配方式:</div>
+        <div class="item-value item-other-value">
+          <el-radio-group v-model="matchWayData" @change="chooseMatchWay($event)">
+            <div class="radio-item">
+              <el-radio name="matchway" :label="1">按标题匹配</el-radio>
+            </div>
+            <div>
+              <el-radio  name="matchway" :label="2">按全文匹配</el-radio>
+            </div>
+          </el-radio-group>
+          <p class="math-tips">会产生无效信息,请根据需要选择</p>
+        </div>
+      </div>
+    </div>
+    <!-- 区域选择dialog -->
+    <el-dialog
+      custom-class="sub-dialog nesting-dialog r70"
+      :visible.sync="dialog.area"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+    >
+      <AreaSelect
+        :initCityMap="areaData"
+        :showSelectResult="true"
+        :areaCount="areaCount"
+        :selectedDetail="false"
+        @onCancel="dialog.area = false"
+        @onConfirm="saveAreaData"
+        @selectedCount="onSelectedCount"
+      ></AreaSelect>
+    </el-dialog>
+    <!-- 采购单位行业dialog -->
+    <el-dialog
+      custom-class="sub-dialog nesting-dialog r70"
+      :visible.sync="dialog.buyClass"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+    >
+      <BuyClassSelect
+        :initCate="buyerClassData"
+        :otherMatch="otherBuyerClassData"
+        getContainer=".drawer-dialog"
+        @onCancel="dialog.buyClass = false"
+        @onConfirm="saveBuyClassData"
+        @onOtherConfirm="saveOtherBuyClass">
+        <div slot="header">选择采购单位类型</div>
+      </BuyClassSelect>
+    </el-dialog>
+    <!-- 信息类型dialog -->
+    <el-dialog
+      custom-class="sub-dialog nesting-dialog r70"
+      :visible.sync="dialog.infoType"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+    >
+      <InfoTypeSelect
+        :initInfoType="infoTypeData"
+        @onCancel="dialog.infoType = false"
+        @onConfirm="saveInfoTypeData">
+        <div slot="header">选择信息类型</div>
+      </InfoTypeSelect>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { Switch, Input, RadioGroup, Radio, Button, Dialog } from 'element-ui'
+import AreaSelect from '@/components/selector/AreaSelector'
+import BuyClassSelect from '@/components/selector/BuyerclassSelector'
+import InfoTypeSelect from '@/components/selector/InfoTypeSelector'
+export default {
+  name: 'sub-config',
+  props: {
+    projectMatch: [String, Number, Boolean],
+    otherBuyerClass: [String, Number, Boolean],
+    matchWay: [String, Number, Boolean],
+    area: Object,
+    buyerClass: Array,
+    infoType: Array,
+    areaCount: Number
+  },
+  components: {
+    [Switch.name]: Switch,
+    [Input.name]: Input,
+    [RadioGroup.name]: RadioGroup,
+    [Radio.name]: Radio,
+    [Button.name]: Button,
+    [Dialog.name]: Dialog,
+    AreaSelect,
+    BuyClassSelect,
+    InfoTypeSelect
+  },
+  data () {
+    return {
+      areaStr: '',
+      buyClassStr: '',
+      infoTypeStr: '',
+      projectMatchData: this.projectMatch,
+      matchWayData: this.matchWay,
+      otherBuyerClassData: this.otherBuyerClass,
+      areaData: this.area,
+      buyerClassData: this.buyerClass,
+      infoTypeData: this.infoType,
+      dialog: {
+        area: false,
+        buyClass: false,
+        infoType: false
+      },
+      selectedAreaCount: 0
+    }
+  },
+  watch: {
+    areaCount (val) {
+      // console.log(val, 'cccc')
+    }
+  },
+  mounted () {
+    this.initData()
+  },
+  methods: {
+    onSelectedCount (data) {
+      // console.log('父组件接收选中的省份数量', data)
+      this.selectedAreaCount = data
+    },
+    initData () {
+      this.formatterArea(this.area)
+      this.formatterBuyerClass(this.buyerClass)
+      this.formatterInfoType(this.infoType)
+    },
+    // 处理区域数据
+    formatterArea (area = {}) {
+      if (area && Object.keys(area).length > 0) {
+        const provinceArr = []
+        let areaArr = []
+        for (const key in area) {
+          provinceArr.push({
+            province: key,
+            citys: area[key]
+          })
+        }
+        provinceArr.forEach((province, i) => {
+          const cityArr = []
+          let pStr = ''
+          province.citys.forEach((city, ii) => {
+            cityArr.push(city)
+          })
+          if (cityArr.length === 0) {
+            pStr = province.province
+          } else {
+            pStr = province.province + '(' + cityArr.join('、') + ')'
+          }
+          areaArr.push(pStr)
+        })
+        areaArr = areaArr.join('、')
+        this.areaStr = areaArr
+      } else {
+        this.areaStr = '全国'
+      }
+    },
+    formatterBuyerClass (data = []) {
+      if (data && data.length > 0) {
+        this.buyClassStr = data.join('、') + (this.otherBuyerClassData ? '、匹配未分类行业' : '')
+      } else {
+        this.buyClassStr = '全部' + (this.otherBuyerClassData ? '、匹配未分类类型' : '')
+      }
+    },
+    formatterInfoType (data = []) {
+      if (data && data.length > 0) {
+        this.infoTypeStr = data.join('、')
+      } else {
+        this.infoTypeStr = '全部类型'
+      }
+    },
+    // 切换项目匹配按钮
+    switchProjectMatch (event) {
+      this.projectMatchData = event
+      this.saveCommonData()
+    },
+    // 关键词匹配方式
+    chooseMatchWay (event) {
+      this.matchWayData = event
+      this.saveCommonData()
+    },
+    // 冒泡区域修改数据
+    saveAreaData (data) {
+      this.areaData = data
+      this.formatterArea(data)
+      this.dialog.area = false
+      this.saveCommonData()
+    },
+    // 冒泡采购单位行业数据及匹配未分类行业数据
+    saveBuyClassData (data) {
+      this.buyerClassData = data
+      this.formatterBuyerClass(data)
+      this.saveCommonData()
+      this.dialog.buyClass = false
+    },
+    // 冒泡未匹配采购单位行业
+    saveOtherBuyClass (data) {
+      this.otherBuyerClassData = data.selected ? 1 : 0
+      this.formatterBuyerClass(this.buyerClassData)
+      this.saveCommonData()
+    },
+    // 冒泡信息类型数据
+    saveInfoTypeData (data) {
+      this.infoTypeData = data
+      this.formatterInfoType(data)
+      this.saveCommonData()
+      this.dialog.infoType = false
+    },
+    saveCommonData () {
+      this.$emit('update', {
+        area: this.areaData,
+        buyerClass: this.buyerClassData,
+        infotype: this.infoTypeData,
+        projectmatch: this.projectMatchData,
+        matchway: this.matchWayData,
+        otherbuyerclass: this.otherBuyerClassData
+      })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.subscribe-config{
+  background: #fff;
+  .sub-title{
+    padding: 26px 30px;
+    font-size: 18px;
+    color: #1d1d1d;
+    line-height: 28px;
+    border-bottom: 1px solid #ececec;
+  }
+  .sub-content{
+    padding: 20px 0;
+  }
+  .item{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 10px;
+  }
+  .item-label{
+    margin-right: 8px;
+    min-width: 120px;
+    height: 40px;
+    color: #1d1d1d;
+    font-size: 14px;
+    line-height: 40px;
+    text-align: right;
+  }
+  .item-label-required:before{
+    content: "*";
+    color: #f56c6c;
+    margin-right: 2px;
+  }
+  .item-value{
+    width: 352px;
+  }
+  .custom-long-input{
+    width: 352px;
+  }
+  .item-other{
+    display: flex;
+    justify-content: center;
+    margin-bottom: 10px;
+  }
+  .item-other-value{
+    margin-top: 8px;
+  }
+  .math-tips{
+    margin-top: 4px;
+    font-size: 12px;
+    color: #999999;
+    line-height: 20px;
+  }
+  .radio-item{
+    margin-bottom: 10px;
+  }
+  // element-ui样式修改
+  ::v-deep {
+    .custom-long-input .el-input__inner{
+      font-size: 14px;
+      color: #2cb7ca;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      border-color: #ececec;
+    }
+    .el-loading-spinner .circular{
+      width: 22px;
+      height: 22px;
+    }
+    .el-loading-spinner{
+      margin-top: -11px;
+    }
+    .el-input__inner{
+      background: #fff;
+      cursor: pointer;
+    }
+    .el-switch{
+      position: relative;
+    }
+    .el-switch__label--left {
+      position: absolute;
+      left: 6px;
+      top: 0;
+      color: #fff;
+      z-index: 9;
+      font-size: 12px;
+    }
+    .el-switch__label--right {
+      position: absolute;
+      right: 6px;
+      top: 0;
+      color: #fff;
+      z-index: 9;
+      font-size: 12px;
+    }
+    .el-switch__label *{
+      font-size: 12px;
+    }
+    .el-switch,.el-switch__core{
+      height: 22px;
+      line-height: 22px;
+    }
+    .el-switch__core:after{
+      width: 18px;
+      height: 18px;
+    }
+    .el-switch.is-checked .el-switch__core::after{
+      margin-left: -18px!important;
+    }
+    .el-radio{
+      color: #1d1d1d;
+      font-size: 14px;
+    }
+    .el-radio__inner{
+      width: 20px;
+      height: 20px;
+    }
+    .el-radio__input.is-checked .el-radio__inner{
+      border: 0;
+      background: transparent;
+      width: 20px;
+      height: 20px;
+      background: url('~@/assets/images/icon-checked.png') no-repeat center center;
+      background-size: contain;
+    }
+    .el-radio__inner::after{
+      background: transparent;
+    }
+    .el-radio__input.is-checked+.el-radio__label{
+      color: #1d1d1d;
+    }
+    .el-radio__inner:hover{
+      border-color: #ececec;
+    }
+  }
+}
+</style>

+ 625 - 0
src/components/subscribe-manager/components/Edit.vue

@@ -0,0 +1,625 @@
+<template>
+  <div class="edit-form">
+    <div class="input-box">
+      <div class="input-box-title">{{title}}关键词组</div>
+      <div class="item">
+        <div class="item-label">关键词:</div>
+        <div class="item-value">
+          <el-input
+            type="textarea"
+            autosize
+            resize="none"
+            placeholder="请输入关键词,多个关键词用空格隔开,例如:税务局 软件"
+            debounce="600"
+            maxlength="200"
+            @input="keywordsInput"
+            @blur="keywordsBlur"
+            v-model="cur.key">
+          </el-input>
+        </div>
+      </div>
+      <div class="item" v-if="sameWordsList && sameWordsList.length > 0">
+        <div class="item-label"></div>
+        <div class="item-value">
+          <div class="recommend">
+            <div class="recommend-title">
+              <span>相似订阅推荐</span>
+              <span class="batch-btn" v-if="sameWordsList && sameWordsList.length > 9" @click="getRecommendFn"><i class="el-icon-refresh"></i> 换一批</span>
+            </div>
+            <div class="recommend-main">
+              <div class="r-list"
+              :class="{active: s.selected}"
+              v-for="(s, j) in sameWordsList.slice(0, 9)"
+              :key="'00' + j"
+              @click="addSameItem($event, s)">{{s.word}}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="item no-center">
+        <div class="item-label">匹配模式:</div>
+        <div class="item-value">
+          <el-radio-group v-model="cur.matchway" @change="chooseMatchWay($event)">
+            <el-radio class="radio-item" name="matchway" :label="1">模糊<span class="math-tips">(任意1个关键词匹配成功即推送)</span></el-radio>
+            <el-radio name="matchway" :label="0">精准<span class="math-tips">(同时包含所有关键词才推送)</span></el-radio>
+          </el-radio-group>
+        </div>
+      </div>
+      <div class="item" v-if="showNotWay" key="notway">
+        <div class="item-label">排除词:</div>
+        <div class="item-value">
+          <el-input
+            type="textarea"
+            autosize
+            resize="none"
+            placeholder="请输入排除词,多个排除词用空格隔开,不希望接收,与关键词互斥"
+            @blur="notKeyBlur"
+            v-model="cur.notkey">
+          </el-input>
+        </div>
+      </div>
+      <div class="item" v-else key="notway">
+        <div class="item-label"></div>
+        <div class="item-value">
+          <div class="add-words-btn" @click="showNotWay = true">+添加排除词(不希望接收,与关键词互斥)</div>
+        </div>
+      </div>
+      <div class="little-tips" v-show="littleTip">当前匹配信息过少,请适当修改关键词</div>
+      <div class="btn-groups">
+        <button type="button" :disabled="keyDisabled" class="confirm-btn" @click="submitKeywords">保存<span style="font-size:12px;"> (近3个月内共匹配{{pushCount}}条信息)</span></button>
+        <button type="button" class="cancle-btn" @click="cancelEdit">取消</button>
+      </div>
+    </div>
+    <!-- 关键词重复提示 -->
+    <el-dialog
+      custom-class="small-dialog nesting-dialog r100"
+      title="新增关键词组"
+      :visible.sync="dialog.repeat"
+      width="380px"
+      center
+      :show-close="false"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+      >
+      <p class="dialog-text">该组关键词已存在,请勿重复添加</p>
+      <span slot="footer" class="dialog-footer">
+        <el-button class="know-btn" type="primary" @click="dialog.repeat = false">我知道了</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { Input, Button, RadioGroup, Radio, Dialog } from 'element-ui'
+import { getPushCount, getRecommend } from '@/api/modules'
+import { debounce } from '@/utils/'
+/* eslint-disable */
+Array.prototype.indexOf = function (val) {
+  for (var i = 0; i < this.length; i++) {
+    if (this[i] === val) return i
+  }
+  return -1
+}
+Array.prototype.remove = function (val) {
+  var index = this.indexOf(val)
+  if (index > -1) {
+    this.splice(index, 1)
+  }
+}
+/* eslint-disable */
+export default {
+  name: 'key-config',
+  props: {
+    datas: Array,
+    title: String,
+    keywords: Object,
+    className: '',
+    classIndex: Number,
+    keyIndex: Number
+  },
+  components: {
+    [Input.name]: Input,
+    [Button.name]: Button,
+    [RadioGroup.name]: RadioGroup,
+    [Radio.name]: Radio,
+    [Dialog.name]: Dialog
+  },
+  data () {
+    return {
+      // 当前输入框的数据
+      cur: {
+        classify: '',
+        key: '',
+        appendkey: '',
+        notkey: '',
+        matchway: 1
+      },
+      dialog: {
+        limit: false,
+        repeat: false
+      },
+      pushCount: 0,
+      showNotWay: false, // 是否展示添加排除词输入框
+      sameWordsList: [] // 相似推荐数据
+    }
+  },
+  computed: {
+    keyDisabled () {
+      return !this.cur.key
+    },
+    // 匹配信息过少提示
+    littleTip () {
+      return this.pushCount < 30 && this.cur.key !== ''
+    }
+  },
+  watch: {
+    'className': function (newVal) {
+      this.className = newVal
+      this.getPropsData()
+    }
+  },
+  mounted () {
+    this.getPropsData()
+  },
+  methods: {
+    getPropsData () {
+      if (this.keywords) {
+        if (this.keywords.appendkey) {
+          this.cur.key = this.keywords.key.join(' ') + ' ' + this.keywords.appendkey.join(' ')
+        } else {
+          this.cur.key = this.keywords.key.join(' ')
+        }
+        if (this.keywords.notkey && this.keywords.notkey.length > 0) {
+          this.showNotWay = true
+          this.cur.notkey = this.keywords.notkey ? this.keywords.notkey.join(' ') : null
+        }
+        if (this.keywords.matchway) {
+          this.cur.matchway = 1
+        } else {
+          this.cur.matchway = 0
+        }
+      }
+      if (this.className) {
+        this.cur.classify = this.className
+      }
+      if (this.title === '修改') {
+        this.getPushCountFn()
+      }
+    },
+    // 关键词推荐数量查询
+    keywordsInput () {
+      if (this.cur.key) {
+        setTimeout(() => {
+          this.getRecommendFn()
+        }, 800)
+        setTimeout(() => {
+          this.getPushCountFn()
+        }, 2000)
+      }
+    },
+    // 关键词输入框失去焦点 查询推送数量
+    keywordsBlur () {
+      if (this.cur.key) {
+        this.getPushCountFn()
+      }
+    },
+    // 排除词输入框失去焦点 查询推送数量
+    notKeyBlur () {
+      if (this.cur.notkey) {
+        this.getPushCountFn()
+      }
+    },
+    // 关键词近3个月推送数量查询
+    getPushCountFn: debounce(function () {
+      if (!this.cur.key) return
+      getPushCount({
+        key: this.cur.key,
+        notkey: this.cur.notkey,
+        matchway: this.cur.matchway
+      }).then(res => {
+        this.pushCount = res.count
+      })
+    }, 500),
+    // 获取相似项目推荐
+    getRecommendFn: debounce(function () {
+      getRecommend({
+        count: 20,
+        value: this.cur.key
+      }).then(res => {
+        this.sameWordsList = []
+        const arrList = []
+        if (res.length > 0) {
+          res.forEach(item => {
+            item.selected = false
+          })
+          const m = this.cur.key.split(' ')
+          m.forEach(v => {
+            res.forEach(s => {
+              if (v === s.word) {
+                s.selected = true
+              }
+            })
+          })
+        }
+        this.sameWordsList = res || []
+      })
+    }, 500),
+    chooseMatchWay () {
+      this.getPushCountFn()
+    },
+    // 添加订阅推荐到输入框
+    addSameItem (e, item) {
+      item.selected = !item.selected
+      const m = this.cur.key.split(' ')
+      if (item.selected) {
+        m.push(item.word)
+        this.cur.key = m.join(' ')
+      } else {
+        m.remove(item.word)
+        this.cur.key = m.join(' ')
+      }
+      setTimeout(() => {
+        this.getPushCountFn()
+      }, 2000)
+    },
+    getNotKeyArr () {
+      const notKey = this.cur.notkey.trim()
+      if (!notKey) return []
+      return notKey.split(/\s+/)
+    },
+    // 提交关键词
+    submitKeywords () {
+      const isRepeat = this.getRepeatFn()
+      if (isRepeat) {
+        this.dialog.repeat = true
+        return
+      }
+      const params = {
+        data: {
+          appendkey: this.cur.appendkey ? this.cur.appendkey.split(/\s+/) : null,
+          key: this.cur.key.split(/\s+/),
+          matchway: this.cur.matchway,
+          notkey: this.getNotKeyArr(),
+          updatetime: parseInt(Date.now() / 1000)
+        },
+        keyIndex: this.keyIndex,
+        classifyName: this.cur.classify,
+        classIndex: this.classIndex
+      }
+      if (this.title === '修改') {
+        this.$emit('update', Object.assign({ state: 'edit' }, params))
+      } else {
+        this.$emit('update', Object.assign({ state: 'add' }, params))
+      }
+      this.$nextTick(() => {
+        this.$emit('closeForm', {
+          keywords: this.keywords,
+          classIndex: this.classIndex,
+          keyIndex: this.keyIndex
+        })
+      })
+    },
+    // 判断附加词排除词输入框是否有空
+    inputIsEmpty (arr) {
+      return arr.some((v) => !v)
+    },
+     getRepeatFn () {
+      const datas = JSON.parse(JSON.stringify(this.datas)) || []
+      let newArr = []
+      datas.forEach(v => {
+        v.a_key.forEach(s => {
+          newArr.push(s)
+        })
+      })
+      const arr = this.cur.key.split(/\s+/)
+      const keyArr = arr[0].split(',')
+      const appendArr = arr.length > 1 ? arr.slice(1) : null
+      const notKeyArr = this.cur.notkey ? this.cur.notkey.split(',') : null
+      let isRepeat
+      for (let i = 0; i < newArr.length; i++) {
+        const v = newArr[i]
+        v.appendkey = v.appendkey && v.appendkey.length > 0 ? v.appendkey : null
+        v.notkey = v.notkey && v.notkey.length > 0 ? v.notkey : null
+        const rKey = JSON.stringify(v.key) === JSON.stringify(keyArr)
+        const rAppend = JSON.stringify(v.appendkey) === JSON.stringify(appendArr)
+        const rNot = JSON.stringify(v.notkey) === JSON.stringify(notKeyArr)
+        const rWay = v.matchway === this.cur.matchway
+        if (rKey && rAppend && rNot && rWay) {
+          isRepeat = rKey && rAppend && rNot && rWay
+          break
+        }
+      }
+      return isRepeat
+    },
+    // 获取所有关键词的key的属性,并返回一个数组(主要用于判断添加关键词是否重复)
+    getKeyTotalArray () {
+      if (!this.datas) return
+      const keysArr = []
+      this.datas.forEach((s) => {
+        if (s && s.a_key && Array.isArray(s.a_key)) {
+          s.a_key.forEach((v) => {
+            if (Array.isArray(v.key)) {
+              keysArr.push(v.key.toString().replace(',', ' '))
+            } else {
+              keysArr.push(v.key)
+            }
+          })
+        }
+      })
+      return keysArr
+    },
+    // 判断关键词出现的个数
+    getRepeatCounts (arr, value) {
+      arr.reduce((a, v) => {
+        v === value ? a + 1 : a + 0
+      }, 0)
+    },
+    cancelEdit () {
+      this.$emit('closeForm', {
+        keywords: this.keywords,
+        classIndex: this.classIndex,
+        keyIndex: this.keyIndex
+      })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.edit-form{
+  margin-top: 20px;
+  .classify-title{
+    padding-bottom: 20px;
+    .icon-edit,.icon-delete{
+      display: inline-block;
+      width: 16px;
+      height: 16px;
+      background-repeat: no-repeat;
+      background-position: center center;
+      cursor: pointer;
+      &::before{
+        content: ''!important;
+      }
+    }
+    .icon-edit{
+      margin: 0 10px;
+      background-image: url('~@/assets/images/icon-edit.png');
+      background-size: contain;
+    }
+    .icon-delete{
+      background-image: url('~@/assets/images/icon-delete.png');
+      background-size: contain;
+    }
+  }
+  .input-box{
+    margin-top: 24px;
+    padding: 20px 30px;
+    background: #f6f7f9;
+    border-radius: 8px;
+  }
+  .input-box-title{
+    margin-bottom: 16px;
+    font-size: 16px;
+    text-align: center;
+    color: #1d1d1d;
+  }
+  .item{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 16px;
+    &.no-center{
+      align-items: unset;
+    }
+  }
+  .item-label{
+    margin-right: 8px;
+    min-width: 78px;
+    height: 40px;
+    color: #1d1d1d;
+    font-size: 14px;
+    line-height: 40px;
+    text-align: right;
+  }
+  .no-center{
+    .item-label{
+      line-height: 20px;
+    }
+  }
+  .item-label-required:before{
+    content: "*";
+    color: #f56c6c;
+    margin-right: 2px;
+  }
+  .recommend{
+    padding: 16px;
+    background: #fff;
+    border-radius: 4px;
+    .recommend-title{
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      color: #686868;
+      font-size: 14px;
+      .batch-btn{
+        color: #2cb7ca;
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+    .recommend-main{
+      display: flex;
+      align-items: center;
+      flex-wrap: wrap;
+      .r-list{
+        padding: 0 8px;
+        margin: 12px 12px 0 0;
+        height: 22px;
+        line-height: 22px;
+        background: #f5f6f7;
+        border-radius: 4px;
+        color: #1d1d1d;;
+        font-size: 14px;
+        cursor: pointer;
+      }
+      .active{
+        background: #2cb7ca;
+        color: #fff;
+      }
+    }
+  }
+  .add-words-btn{
+    width: 352px;
+    height: 40px;
+    line-height: 40px;
+    border: 1px dashed #2cb7ca;
+    border-radius: 6px;
+    background: #fff;
+    color: #2cb7ca;
+    text-align: center;
+    cursor: pointer;
+    font-size: 14px;
+  }
+  .little-tips{
+    margin-top: 20px;
+    height: 32px;
+    line-height: 32px;
+    background: rgba(255, 159, 64,0.1);
+    border-radius: 4px;
+    color: #ff9f40;
+    font-size: 13px;
+    text-align: center;
+  }
+  .item-value{
+    flex: 1;
+  }
+  .custom-long-input{
+    width: 352px;
+  }
+  .custom-short-input{
+    width: 170px;
+  }
+  .item-other{
+    display: flex;
+    justify-content: center;
+    margin-bottom: 10px;
+  }
+  .item-other-value{
+    margin-top: 8px;
+  }
+  .math-tips{
+    margin-top: 4px;
+    font-size: 12px;
+    color: #999999;
+    line-height: 20px;
+  }
+  .radio-item{
+    margin-bottom: 10px;
+  }
+  .item-keywords-value{
+    display: flex;
+    justify-content: space-between;
+  }
+  .add-tag{
+    width: 170px;
+    height: 40px;
+    line-height: 40px;
+    color: #2cb7ca;
+    border-radius: 6px;
+    text-align: center;
+    cursor: pointer;
+    color: #2cb7ca;
+    border: 1px dashed #2cb7ca;
+  }
+  .add-word-list{
+    margin-bottom: 6px;
+  }
+  .btn-groups{
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin-top: 20px;
+  }
+  .confirm-btn{
+    display: block;
+    width: 240px;
+    height: 46px;
+    line-height: 46px;
+    font-size: 16px;
+    background: #2cb7ca;
+    border-radius: 6px;
+    color: #fff;
+    cursor: pointer;
+    &:disabled{
+      opacity: 0.5;
+      cursor: not-allowed;
+    }
+  }
+  .cancle-btn{
+    display: block;
+    width: 240px;
+    height: 46px;
+    margin-left: 20px;
+    line-height: 46px;
+    background: #fff;
+    border: 1px solid #2cb7ca;
+    color: #2CB7CA;
+    border-radius: 6px;
+    text-align: center;
+    cursor: pointer;
+  }
+  .keywords-help{
+    width: 412px;
+    margin-top: 20px;
+    font-size: 12px;
+    color: #999999;
+    line-height: 20px;
+    text-align: justify;
+  }
+  .radio-item{
+    margin-bottom: 18px;
+  }
+}
+// element-ui样式修改
+::v-deep {
+  .el-input__inner,
+  .el-textarea__inner{
+    font-size: 14px;
+    color: #1D1D1D;
+    border-color: #ececec;
+  }
+  .el-textarea__inner{
+    padding: 8px 15px;
+  }
+  .el-input__inner:hover,
+  .el-textarea__inner:hover{
+    border-color: #ececec;
+  }
+  .el-input__inner:focus,
+  .el-textarea__inner:focus{
+    border-color: #2cb7ca;
+  }
+  .el-radio{
+    color: #1d1d1d;
+    font-size: 14px;
+  }
+  .el-radio__inner{
+    width: 20px;
+    height: 20px;
+  }
+  .el-radio__input.is-checked .el-radio__inner{
+    border: 0;
+    background: transparent;
+    width: 20px;
+    height: 20px;
+    background: url('~@/assets/images/icon-checked.png') no-repeat center center;
+    background-size: contain;
+  }
+  .el-radio__inner::after{
+    background: transparent;
+  }
+  .el-radio__input.is-checked+.el-radio__label{
+    color: #1d1d1d;
+  }
+  .el-radio__inner:hover{
+    border-color: #ececec;
+  }
+}
+</style>

+ 736 - 0
src/components/subscribe-manager/components/List.vue

@@ -0,0 +1,736 @@
+<template>
+  <div class="classify" id="auxiliaryFindRange">
+    <div class="fixed-top-group">
+      <div class="classify-list " v-for="(item,index) in newWordsList" :key="'top-1' + index">
+        <div style="display: none" class="classify-title flex-r-c sb" @click="goThisTop(index)" :data-diy-sticky-mapping="'sticky-'+index">
+          <div class="flex-r-c">
+            <span class="title-text">{{item.s_item}}</span>
+            <span class="icon-edit" @click="editClassFn(item.s_item, index)"></span>
+            <span class="icon-delete" @click="deleteClassFn(item, index)"></span>
+          </div>
+          <div class="flex-r-c right">
+            <el-button type="primary" class="add-classfily" icon="el-icon-plus" @click="addNewKeyword(item, index)">新增关键词组</el-button>
+            <div class="flex-r-c center list-item-opened" @click="slideToggle(item)">
+              <span>{{item.opened ? '收起' : '展开'}}</span>
+              <i :class="item.opened ? 'el-icon-arrow-up': 'el-icon-arrow-down'"></i>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="classify-list" v-for="(item,index) in newWordsList" :key="'1' + index">
+      <div class="classify-title flex-r-c sb" :data-diy-sticky-origin="'sticky-'+index">
+        <div class="flex-r-c">
+          <span class="title-text">{{item.s_item}}</span>
+          <span class="icon-edit" @click="editClassFn(item.s_item, index)"></span>
+          <span class="icon-delete" @click="deleteClassFn(item, index)"></span>
+        </div>
+        <div class="flex-r-c right">
+          <div class="flex-r-c center list-item-opened" @click="slideToggle(item)">
+            <span>{{item.opened ? '收起' : '展开'}}</span>
+            <i :class="item.opened ? 'el-icon-arrow-up': 'el-icon-arrow-down'"></i>
+          </div>
+        </div>
+      </div>
+      <el-collapse-transition>
+        <div class="classify-content" v-show="item.opened">
+        <div class="add-words-box" @click="addNewKeyword(item, index)" v-if="!item.showForm" key="add">+新增关键词组</div>
+        <div style="width:100%;" v-else key="add">
+          <Edit
+            :datas="newWordsList"
+            :className="item.s_item"
+            title="新增"
+            :classIndex="index"
+            :keyIndex="item.a_key.length"
+            @closeForm="onCloseForm"
+            @update="getUpdateKey"
+          >
+          </Edit>
+        </div>
+        <div v-for="(v, i) in item.a_key" :key="'2' + i" style="width:100%;">
+          <Edit
+            :datas="newWordsList"
+            title="修改"
+            :className="item.s_item"
+            :keywords="v"
+            :classIndex="index"
+            :keyIndex="i"
+            @closeForm="onCloseForm"
+            @update="getUpdateKey"
+            v-if="v.showForm" key="edit">
+          </Edit>
+          <div class="words-list" v-else key="edit">
+            <div class="list-left yellow-box" v-if="v.matchway">模糊</div>
+            <div class="list-left blue-box" v-else>精准</div>
+            <div class="list-middle">
+              <div class="list-keywords" v-if="v.appendkey" key="append">
+                {{v.key.join(' ') + ' ' +  v.appendkey.join(' ')}}
+              </div>
+              <div class="list-keywords" v-else key="append">{{v.key.join(' ')}}</div>
+              <p class="list-notkey" v-if="v.notkey && v.notkey.length > 0">排除词: {{v.notkey.join(' ')}}</p>
+            </div>
+            <div class="list-right">
+              <span class="icon-edit" @click="editKeyFn(item, v, index, i)"></span>
+              <span class="icon-delete" @click="deleteKeyFn(index, i)"></span>
+            </div>
+          </div>
+        </div>
+      </div>
+      </el-collapse-transition>
+    </div>
+    <!-- 修改分类dialog -->
+    <el-dialog
+      custom-class="sub-dialog small-dialog nesting-dialog r70"
+      :visible.sync="dialog.editClass"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+    >
+      <KeyCard @onCancel="dialog.editClass = false" @onConfirm="confirmEditClassFn">
+        <div slot="header">修改关键词分类</div>
+        <div class="class-edit-content">
+          <div class="item">
+            <div class="item-label">关键词分类:</div>
+            <div class="item-value">
+              <el-input class="custom-long-input" v-model.trim="props.className"  maxlength="20" placeholder="请输入关键词分类"></el-input>
+            </div>
+          </div>
+        </div>
+      </KeyCard>
+    </el-dialog>
+    <!-- 删除分类dialog -->
+    <el-dialog
+      custom-class="sub-dialog small-dialog nesting-dialog r100"
+      :visible.sync="dialog.delClass"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      top="30vh"
+      width="380px"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+    >
+      <div class="delete-class">
+        <div class="delete-class-header">删除关键词分类</div>
+        <div class="delete-class-content">{{props.className}}</div>
+        <div class="delete-class-footer">
+          <el-button type="primary" class="confirm" @click="confirmDeleteClassFn">删除</el-button>
+          <el-button class="cancel" @click="dialog.delClass = false">取消</el-button>
+        </div>
+      </div>
+    </el-dialog>
+    <!-- 删除关键词dialog -->
+    <el-dialog
+      custom-class="sub-dialog small-dialog nesting-dialog r100"
+      :visible.sync="dialog.delKey"
+      :close-on-click-modal="false"
+      :show-close="false"
+      top="30vh"
+      center
+      width="380px"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+    >
+      <div class="delete-class">
+        <div class="delete-class-header">删除关键词组</div>
+        <div class="delete-class-content">确定删除该关键词组吗?</div>
+        <div class="delete-class-footer">
+          <el-button type="primary" class="confirm" @click="confirmDeleteKeyFn">确定</el-button>
+          <el-button class="cancel" @click="dialog.delKey = false">取消</el-button>
+        </div>
+      </div>
+    </el-dialog>
+    <!-- 删除分类dialog -->
+    <el-dialog
+      custom-class="small-dialog nesting-dialog r100"
+      title="删除关键词分类"
+      :visible.sync="dialog.notDelClass"
+      width="380px"
+      center
+      :show-close="false"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+      >
+      <p class="dialog-text">该关键词分类下存在关键词组,无法删除,请先删除该分类下的关键词组</p>
+      <span slot="footer" class="dialog-footer">
+        <el-button class="know-btn" type="primary" @click="dialog.notDelClass = false">我知道了</el-button>
+      </span>
+    </el-dialog>
+    <!-- 关键词添加超限提示 -->
+    <el-dialog
+      custom-class="small-dialog nesting-dialog r100"
+      title="新增关键词组"
+      :visible.sync="dialog.limit"
+      width="380px"
+      center
+      :show-close="false"
+      v-component-change-mount="{ selector: '.drawer-dialog' }"
+      >
+      <p class="dialog-text">您的关键词组数量已达300组,无法继续添加</p>
+      <span slot="footer" class="dialog-footer">
+        <el-button class="know-btn" type="primary" @click="dialog.limit = false">我知道了</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { Tooltip, Dialog, Input, Button } from 'element-ui'
+import CollapseTransition from 'element-ui/lib/transitions/collapse-transition'
+import KeyCard from '@/components/selector/SelectorCard'
+import Edit from './Edit'
+let timeFn = null
+export default {
+  name: 'keywords-list',
+  components: {
+    [Tooltip.name]: Tooltip,
+    [Dialog.name]: Dialog,
+    [Input.name]: Input,
+    [Button.name]: Button,
+    KeyCard,
+    Edit,
+    [CollapseTransition.name]: CollapseTransition
+  },
+  props: {
+    list: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    maxCount: Number
+  },
+  data () {
+    return {
+      dialog: {
+        editClass: false, // 修改分类弹框
+        delClass: false, // 删除分类弹框
+        editKey: false, // 修改关键词弹框
+        delKey: false, // 删除关键词
+        notDelClass: false, // 不能删除分类弹框
+        limit: false // 关键词设置超出上限弹框
+      },
+      // 传给dialog子组件的数据
+      props: {
+        ways: '', // 编辑还是新增
+        classIndex: null, // 分类下标
+        className: '', // 分类名
+        keyIndex: null, // 关键词下标
+        key: [], // 关键词
+        notkey: [], // 排除词
+        appendkey: [] // 附加词
+      },
+      newWordsList: this.list // 将父组件传来的数据赋值给该变量,
+    }
+  },
+  watch: {
+    // list: function (newVal) {
+    //   if (newVal) {
+    //     const oldList = JSON.parse(JSON.stringify(this.newWordsList))
+    //     this.newWordsList = newVal
+    //     this.formatDataList(oldList)
+    //   }
+    // }
+  },
+  mounted () {
+    this.formatDataList()
+    timeFn = this.windowScrollFn.bind(this)
+    $('.fixed-top-group').css('top', $('#public-nav').outerHeight())
+    $(window).on('scroll', timeFn)
+  },
+  beforeDestroy () {
+    $(window).off('scroll', timeFn)
+  },
+  methods: {
+    getUpdateKey (params) {
+      const { state, classIndex, data, keyIndex } = params
+      const list = this.newWordsList
+      list.forEach((v, index) => {
+        if (index === classIndex) {
+          if (v.a_key) {
+            if (state === 'edit') {
+              v.a_key.forEach((s, j) => {
+                if (keyIndex === j) {
+                  for (const key in data) {
+                    s[key] = data[key]
+                  }
+                }
+              })
+            } else {
+              v.a_key.push(data)
+            }
+          }
+        }
+      })
+      console.log(list, 'update-list')
+      this.$emit('update', list)
+    },
+    // 当前分类下有无正在编辑的
+    getItemCurEdit (item) {
+      const s = item.a_key.some(v => {
+        return v.showForm
+      })
+      return s || item.showForm
+    },
+    slideToggle (item) {
+      const s = this.getItemCurEdit(item)
+      if (item.opened) {
+        if (s) return this.$toast('收起失败,请先保存或取消正在操作的关键词组')
+      }
+      item.opened = !item.opened
+      this.$forceUpdate()
+    },
+    goThisTop (index) {
+      const goTop = $('#auxiliaryFindRange *[data-diy-sticky-origin="sticky-' + index + '"]').offset().top - $('#public-nav').outerHeight() - 20
+      $(window).scrollTop(goTop)
+    },
+    windowScrollFn () {
+      const winScrollTop = $(window).scrollTop() + $('#public-nav').outerHeight()
+      if ($(window).width() <= 1200) {
+        $('#auxiliaryFindRange .fixed-top-group').css('left', 60 - $(window).scrollLeft())
+      } else {
+        $('#auxiliaryFindRange .fixed-top-group').css('left', '50%')
+      }
+
+      $('#auxiliaryFindRange *[data-diy-sticky-origin]').each(function () {
+        const onlyKey = $(this).attr('data-diy-sticky-origin')
+        const itemOffsetTop = $(this).offset().top
+        const itemHeight = $(this).outerHeight()
+        const visibility = winScrollTop >= itemOffsetTop - itemHeight
+        const mapNodeDom = $('#auxiliaryFindRange *[data-diy-sticky-mapping="' + onlyKey + '"]')
+        if (visibility) {
+          $('#auxiliaryFindRange *[data-diy-sticky-mapping]').hide()
+        }
+        mapNodeDom[visibility ? 'show' : 'hide']()
+      })
+    },
+    sortData (arr) {
+      return arr.sort((a, b) => {
+        return b.updatetime - a.updatetime
+      })
+    },
+    // 处理数据 添加showForm字段
+    formatDataList (oldData) {
+      const lists = this.newWordsList
+      this.sortData(lists)
+      lists.forEach((v, index) => {
+        if (v) {
+          if (!(typeof v.opened === 'string' || typeof v.opened === 'boolean')) {
+            v.opened = true
+            // TODO 生成索引缓存结果减少遍历优化性能
+            if (oldData && Array.isArray(oldData)) {
+              const findResult = oldData.filter(s => s.s_item === v.s_item)
+              if (findResult.length && Object.prototype.hasOwnProperty.call(findResult[0], 'opened')) {
+                v.opened = findResult[0].opened
+              }
+            }
+          }
+          if (v.a_key && v.a_key.length > 0) {
+            this.sortData(v.a_key)
+          } else {
+            v.a_key = []
+            // if (lists.length === 1) {
+            //   v.showForm = true
+            // }
+          }
+        }
+      })
+      this.newWordsList = lists
+    },
+    // 打开修改关键词分类弹框
+    editClassFn (name, index) {
+      const t = this.getCurEdit()
+      if (t) return this.$toast('请先保存或取消正在操作的关键词组')
+      this.dialog.editClass = true
+      this.props.className = name
+      this.props.classIndex = index
+    },
+    // 确认修改分类
+    confirmEditClassFn () {
+      const data = this.newWordsList
+      const classArr = this.getClassArray()
+      if (classArr.indexOf(this.props.className) > -1) {
+        return this.$message({
+          type: 'warning',
+          message: '分类名不能重复'
+        })
+      } else if (this.props.className === '') {
+        return this.$message({
+          type: 'warning',
+          message: '分类名不能为空'
+        })
+      }
+      data.forEach((v, i) => {
+        if (this.props.classIndex === i) {
+          v.s_item = this.props.className
+          v.updatetime = parseInt(Date.now() / 1000)
+        }
+      })
+      this.$nextTick(() => {
+        this.dialog.editClass = false
+        this.$emit('update', data)
+        this.$forceUpdate()
+      })
+    },
+    // 新增关键词 打开编辑表单
+    addNewKeyword (item, index) {
+      const t = this.getCurEdit()
+      if (t) return this.$toast('请先保存或取消正在操作的关键词组')
+      const isCan = this.getIsAdd()
+      if (isCan) {
+        this.dialog.limit = true
+        return
+      }
+      item.showForm = true
+      item.opened = true
+      this.$forceUpdate()
+    },
+    // 打开删除关键词分类弹框
+    deleteClassFn (item, index) {
+      if (item.a_key && item.a_key.length > 0) {
+        this.dialog.notDelClass = true
+        return
+      }
+      this.props.classIndex = null
+      this.dialog.delClass = true
+      this.props.className = item.s_item
+      this.props.classIndex = index
+    },
+    // 确认删除分类
+    confirmDeleteClassFn () {
+      const list = this.newWordsList
+      list.splice(this.props.classIndex, 1)
+      this.$nextTick(() => {
+        this.dialog.delClass = false
+      })
+    },
+    // 编辑单个关键词
+    editKeyFn (item, v, index, i) {
+      const t = this.getCurEdit()
+      if (t) return this.$toast('请先保存或取消正在操作的关键词组')
+      this.clearPropsData()
+      this.props.className = item.s_item
+      this.props.key = v.key
+      this.props.notkey = v.notkey
+      this.props.appendkey = v.appendkey
+      this.props.keyIndex = i
+      this.props.classIndex = index
+      v.showForm = true
+    },
+    // 打开删除关键词dialog
+    deleteKeyFn (index, i) {
+      this.props.classIndex = index
+      this.props.keyIndex = i
+      this.dialog.delKey = true
+    },
+    // 确定删除关键词组
+    confirmDeleteKeyFn () {
+      const data = this.newWordsList
+      console.log(data, this.props.classIndex, this.props.keyIndex)
+      data[this.props.classIndex].a_key.splice(this.props.keyIndex, 1)
+      this.dialog.delKey = false
+      this.$forceUpdate()
+    },
+    // 清空传值
+    clearPropsData () {
+      this.props.className = ''
+      this.props.classIndex = null
+      this.props.key = []
+      this.props.notkey = []
+      this.props.appendkey = []
+    },
+    // 获取所有分类名 用户判断添加分类是否重复
+    getClassArray () {
+      const data = this.newWordsList
+      const classArr = []
+      data.forEach((v) => {
+        if (v.s_item) {
+          classArr.push(v.s_item)
+        }
+      })
+      return classArr
+    },
+    // 添加了多少组关键词
+    addTotalCount () {
+      let count = 0
+      const data = this.newWordsList
+      data.forEach(v => {
+        if (v && v.a_key) {
+          count += v.a_key.length
+        }
+      })
+      return count
+    },
+    // 判断是否超限
+    getIsAdd () {
+      const len = this.addTotalCount()
+      if (len >= this.maxCount) {
+        return true
+      } else {
+        return false
+      }
+    },
+    onCloseForm (data) {
+      if (!data) return
+      if (data.keywords) {
+        data.keywords.showForm = false
+        this.$forceUpdate()
+      } else {
+        this.newWordsList[data.classIndex].showForm = false
+        this.$forceUpdate()
+      }
+      this.formatDataList()
+    },
+    // 查询有无正在编辑的表单
+    getCurEdit () {
+      const list = this.newWordsList
+      let t
+      const s = list.some(v => {
+        if (v.a_key) {
+          t = v.a_key.some(s => {
+            return s.showForm
+          })
+        }
+        return v.showForm || t
+      })
+      return s
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.fixed-top-group {
+  position: fixed;
+  width: 1080px;
+  top: 0;
+  left: 50%;
+  z-index: 99;
+  transform: translateX(-50%);
+  @media only screen and (max-width: 1200px) {
+    background-color: red;
+    transform: unset;
+    left: 60px;
+  }
+  .classify-list {
+    margin-bottom: 0;
+  }
+  .classify-list  .classify-title {
+    width: 100%;
+    position: absolute;
+    background: #fff;
+    padding: 9px 32px;
+    margin: 0;
+    box-shadow: 0px 0px 28px 0px rgba(0,0,0,0.16);
+  }
+}
+.classify-list{
+  margin-bottom: 8px;
+  .list-item-opened {
+    font-size: 14px;
+    font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+    font-weight: 400;
+    text-align: left;
+    color: #686868;
+    text-shadow: 0px 0px 28px 0px rgba(0,0,0,0.16);
+    cursor: pointer;
+    i {
+      color: #aaaaaa;
+      margin-left: 7px;
+    }
+  }
+  .add-classfily{
+    line-height: 22px;
+    background-color: transparent;
+    border: 1px solid #2cb7ca;
+    border-radius: 6px;
+    text-align: center;
+    color: #2CB7CA;
+    font-size: 14px;
+    cursor: pointer;
+    padding: 4px 16px;
+    box-sizing: border-box;
+    & + .list-item-opened {
+      margin-left: 16px;
+    }
+  }
+  .classify-title{
+    display: flex;
+    align-items: center;
+    padding: 9px 0;
+    .title-text{
+      font-size: 16px;
+      color: #1d1d1d;
+    }
+  }
+  .icon-edit,.icon-delete{
+    display: inline-block;
+    width: 16px;
+    height: 16px;
+    background-repeat: no-repeat;
+    background-position: center center;
+    cursor: pointer;
+    &::before{
+      content: ''!important;
+    }
+  }
+  .icon-edit{
+    margin: 0 10px;
+    background-image: url('~@/assets/images/icon-edit.png');
+    background-size: contain;
+  }
+  .icon-delete{
+    background-image: url('~@/assets/images/icon-delete.png');
+    background-size: contain;
+  }
+  .classify-content{
+    .edit-form {
+      margin-top: 0;
+    }
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+    flex-wrap: wrap;
+    .add-words-box{
+      width: 100%;
+      height: 38px;
+      line-height: 38px;
+      border: 1px dashed #2cb7ca;
+      border-radius: 6px;
+      color: #2cb7ca;
+      font-size: 14px;
+      text-align: center;
+      cursor: pointer;
+    }
+    .words-list{
+      width: 100%;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 12px 16px;
+      margin-top: 24px;
+      border: 1px solid #ececec;
+      border-radius: 8px;
+      box-sizing: border-box;
+      .list-left{
+        width: 40px;
+        height: 22px;
+        line-height: 22px;
+        margin-right: 12px;
+        font-size: 12px;
+        text-align: center;
+        box-sizing: border-box;
+      }
+      .yellow-box{
+        border: 1px solid #ff9f40;
+        color: #ff9f40;
+        border-radius: 4px;
+      }
+      .blue-box{
+        border: 1px solid #2cb7ca;
+        color: #2cb7ca;
+        border-radius: 4px;
+      }
+      .list-middle{
+        flex: 1;
+      }
+      .list-keywords{
+        color: #1d1d1d;
+        line-height: 22px;
+        font-size: 14px;
+      }
+      .list-addkey,
+      .list-notkey{
+        color: #686868;
+        line-height: 20px;
+        font-size: 12px;
+      }
+      .list-right{
+        margin-left:15px;
+      }
+    }
+
+    .list{
+      padding-top: 10px;
+      margin-right: 10px;
+      margin-bottom: 10px;
+      width: 160px;
+      height: 80px;
+      box-sizing: border-box;
+      &:hover .list-edit{
+        transform: scaleY(1);
+        transition: transform .1s;
+      }
+      &:hover .list-box{
+        border: 1px solid #2cb7ca;
+        box-sizing: border-box;
+      }
+    }
+    :list:nth-child(6n) {
+      margin-right: 0;
+    }
+    .list-box{
+      position: relative;
+      display: flex;
+      padding: 10px 10px 10px 16px;
+      border: 1px solid #ececec;
+      border-radius: 9px;
+      box-sizing: border-box;
+      cursor: pointer;
+    }
+    .list-edit{
+      transform: scaleY(0);
+      transition: transform .1s;
+      position: absolute;
+      top: -40px;
+      left: 50%;
+      margin-left: -22px;
+      padding: 10px;
+      color: #fff;
+      background: #1d1d1d;
+      border-radius: 4px;
+      font-size: 12px;
+      cursor: pointer;
+      .tri-down{
+        position: absolute;
+        bottom: -6px;
+        left: 50%;
+        margin-left: -5px;
+        width: 0;
+        height: 0;
+        border-left: 6px solid transparent;
+        border-right: 6px solid transparent;
+        border-top: 6px solid #1d1d1d;
+      }
+    }
+    .list-value{
+      width: 124px;
+      font-size: 12px;
+      line-height: 20px;
+      color: #000;
+      text-overflow: ellipsis;
+      overflow: hidden;
+      white-space: nowrap;
+      p{
+        @extend .list-value
+      }
+    }
+    .list-icon{
+      @extend .icon-delete;
+      width: 12px;
+      height: 12px;
+    }
+    .words-add{
+      width: 162px;
+      height: 80px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 24px;
+      border: 1px solid #ececec;
+      margin: 10px 0 0 0;
+      border-radius: 9px;
+      color: #c4c4c4;
+      cursor: pointer;
+    }
+  }
+}
+</style>

+ 351 - 0
src/components/subscribe-manager/index.vue

@@ -0,0 +1,351 @@
+<template>
+  <div class="subscribe-manage">
+    <SubConfig
+      :other-buyer-class="setData.otherBuyerClass"
+      :match-way="setData.matchWay"
+      :project-match="setData.projectMatch"
+      :area="setData.area"
+      :buyer-class="setData.buyerClass"
+      :info-type="setData.infoType"
+      :area-count="setData.areaCount"
+      @update="onUpdateSub">
+    </SubConfig>
+    <KeyConfig
+      :list="setData.keyList"
+      :max-count="setData.maxCount"
+      @update="onUpdateKey"
+    >
+    </KeyConfig>
+    <PushConfig
+      :rateModel="setData.rateModel"
+      :mailPush="setData.mailPush"
+      :appPush="setData.appPush"
+      :mail="setData.mail"
+      @update="onUpdatePush"
+    >
+    </PushConfig>
+  </div>
+</template>
+
+<script>
+import SubConfig from '@/components/subscribe-manager/SubConfig'
+import KeyConfig from '@/components/subscribe-manager/KeyConfig'
+import PushConfig from '@/components/subscribe-manager/PushConfig'
+export default {
+  name: 'subscribeManage',
+  props: {
+    init: {
+      type: Object,
+      default () {
+        return {}
+      }
+    }
+  },
+  components: {
+    SubConfig,
+    KeyConfig,
+    PushConfig
+  },
+  data () {
+    return {
+      setData: {
+        // 区域对象
+        area: {},
+        // 采购单位行业
+        buyerClass: [],
+        // 信息类型
+        infoType: [],
+        // 关键词
+        keyList: [],
+        matchWay: 0,
+        projectMatch: 0,
+        otherBuyerClass: 0,
+        rateModel: 1,
+        mailPush: 0,
+        appPush: 0,
+        mail: '',
+        maxCount: 0,
+        areaCount: 0
+      }
+    }
+  },
+  watch: {
+    init (newVal) {
+      this.todoData(JSON.parse(JSON.stringify(newVal)))
+    }
+  },
+  created () {
+    this.todoData(JSON.parse(JSON.stringify(this.init)))
+  },
+  methods: {
+    todoData (data) {
+      const { area, industry, infotype, items, matchway, projectmatch, otherbuyerclass, key_max_length: maxCount, mail, ratemode, apppush, mailpush, buyset } = data
+      this.setData.area = area
+      this.setData.buyerClass = industry
+      this.setData.infoType = infotype
+      this.setData.matchWay = matchway
+      this.setData.projectMatch = projectmatch
+      this.setData.otherBuyerClass = otherbuyerclass
+      this.setData.rateModel = ratemode
+      this.setData.mailPush = mailpush
+      this.setData.appPush = apppush
+      this.setData.mail = mail
+      this.setData.maxCount = maxCount
+      console.log(buyset, buyset.areacount)
+      this.setData.areaCount = buyset.areacount
+      if (items) {
+        items.forEach(v => {
+          try {
+            delete v.showForm
+          } catch (error) {}
+        })
+      }
+      this.setData.keyList = items
+    },
+    onUpdateSub (data) {
+      // console.log(data, '冒泡来的订阅数据')
+      const { area, buyerClass, infotype, matchway, otherbuyerclass, projectmatch } = data
+      this.setData.area = area
+      this.setData.buyerClass = buyerClass
+      this.setData.infoType = infotype
+      this.setData.matchWay = matchway
+      this.setData.projectMatch = projectmatch
+      this.setData.otherBuyerClass = otherbuyerclass
+    },
+    onUpdatePush (data) {
+      // console.log(data, '冒泡来的推送设置数据')
+      const { apppush, mail, mailpush, ratemode } = data
+      this.setData.rateModel = ratemode
+      this.setData.mailPush = mailpush
+      this.setData.appPush = apppush
+      this.setData.mail = mail
+    },
+    onUpdateKey (data) {
+      // console.log(data, '冒泡来的关键词数据')
+      if (!data) return
+      this.sortData(data)
+      data.forEach(v => {
+        try {
+          delete v.showForm
+        } catch (error) {}
+        if (v.a_key) {
+          v.a_key.forEach(s => {
+            try {
+              delete s.showForm
+            } catch (error) {}
+          })
+          this.sortData(v.a_key)
+        }
+      })
+      this.setData.keyList = data
+    },
+    sortData (arr) {
+      return arr.sort((a, b) => {
+        return b.updatetime - a.updatetime
+      })
+    },
+    onConfirm () {
+      const { area, buyerClass, infoType, keyList, matchWay, projectMatch, otherBuyerClass, rateModel, mailPush, appPush, mail } = this.setData
+      const params = {
+        area,
+        buyerclass: buyerClass,
+        infotype: infoType,
+        items: keyList,
+        ratemode: rateModel ? rateModel.toString() : '',
+        projectmatch: projectMatch ? projectMatch.toString() : '',
+        matchway: matchWay ? matchWay.toString() : '',
+        apppush: appPush ? appPush.toString() : '',
+        mailpush: mailPush ? mailPush.toString() : '',
+        mail,
+        otherbuyerclass: otherBuyerClass ? otherBuyerClass.toString() : ''
+      }
+      return params
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.drawer-dialog{
+  .nesting-dialog{
+    position: absolute;
+    &.r70{
+      right: 70px;
+    }
+    &.r100{
+      right: 100px;
+    }
+  }
+  .sub-dialog .el-dialog__header,
+  .sub-dialog .el-dialog__body{
+    padding: 0;
+  }
+  .sub-dialog{
+    .el-dialog__header,
+    .sub-dialog .el-dialog__body{
+      padding: 0;
+    }
+    &.small-dialog{
+      .selector-card,
+      .selector-card.s-card{
+        height: auto;
+      }
+    }
+    .class-edit-content{
+      padding: 30px;
+      width: 100%;
+      height: auto;
+      max-height: 340px;
+      margin: 0 auto;
+      border: 1px solid #ececec;
+      overflow-y: scroll;
+      box-sizing: border-box;
+      .item{
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-bottom: 10px;
+      }
+      .item-label{
+        margin-right: 8px;
+        // min-width: 120px;
+        height: 40px;
+        color: #1d1d1d;
+        font-size: 14px;
+        line-height: 40px;
+        text-align: right;
+      }
+      .item-label-required:before{
+        content: "*";
+        color: #f56c6c;
+        margin-right: 2px;
+      }
+      .item-value{
+        flex: 1;
+      }
+    }
+    .delete-class{
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      padding: 32px 32px 42px;
+      background: #FFFFFF;
+      border-radius: 6px;
+      .delete-class-header{
+        font-size: 18px;
+        line-height: 28px;
+        color: #1D1D1D;
+      }
+      .delete-class-content{
+        padding: 20px 0 42px;
+        color: #686868;
+        font-size: 14px;
+        line-height: 22px;
+      }
+      .delete-class-footer{
+        display: flex;
+        align-items: center;
+        .confirm,
+        .cancel {
+          padding: 10px 50px;
+          margin: 0 20px;
+        }
+        .el-button--primary {
+          color: #2cb7ca;
+          background: none;
+          border-color: #2cb7ca;
+          &:hover {
+            color: #fff;
+            background-color: #2cb7ca;
+          }
+        }
+        .el-button--default {
+          &:hover,
+          &:active {
+            color: #2cb7ca;
+            border-color: #2cb7ca;
+            background: #eaf8fa;
+          }
+        }
+      }
+    }
+    // 修改输入框默认focus边框颜色
+    .el-input.is-active .el-input__inner,
+    .el-input__inner:focus {
+      border-color: $color-text--highlight;
+    }
+  }
+  .small-dialog{
+    border-radius: 8px;
+    .dialog-text{
+      margin-top:4px;
+      font-size:14px;
+      color:#686868;
+      line-height:22px;
+      text-align:center;
+    }
+    .know-btn{
+      background: #2cb7ca;
+      border-radius: 6px;
+      border:0;
+    }
+    .dialog-update-black{
+      color: #1D1D1D;
+      line-height: 22px;
+      text-align: justify;
+    }
+    .dialog-update-gray{
+      margin-top: 20px;
+      color: #686868;
+      line-height: 22px;
+      text-align: justify;
+    }
+  }
+  .email-dialog{
+    padding: 32px;
+    border-radius: 8px;
+    .el-dialog__body{
+      padding: 20px 0;
+      .error-tips{
+        margin-top: 4px;
+        font-size: 14px;
+        line-height: 22px;
+        color: #FF3A20;
+      }
+    }
+    .el-input__inner{
+      height: 36px;
+      line-height: 36px;
+    }
+    .el-dialog__footer{
+      padding: 0;
+      .dialog-footer{
+        width: 100%;
+        display: flex;
+        justify-content: space-between;
+      }
+      .el-button{
+        width: 132px;
+        height: 36px;
+        font-size: 16px;
+        line-height: 36px;
+        padding: 0;
+      }
+      .el-button--primary{
+        border-color: $color_main;
+        background: $color_main;
+        &:disabled{
+          opacity: 0.6;
+        }
+      }
+      .el-button--default{
+        background: #fff;
+        &:hover{
+          color: #686868;
+          border-color:#E0E0E0;
+        }
+      }
+    }
+  }
+}
+</style>

+ 4 - 4
src/components/work-desktop/CommonUse.vue

@@ -2,7 +2,7 @@
   <div class="work-common">
     <div class="common-title">
       <span>常用功能</span>
-      <span class="set" @click="setFunctions"><i class="icon-set"></i> 设置</span>
+      <span class="set" @click="setFunctions"><i class="icon-set-img"></i> 设置</span>
     </div>
     <div class="common-lists">
       <div class="list-item" v-for="(item, index) in commonList" :key="index" @click="openRouter(item.url)">
@@ -10,7 +10,7 @@
         <span v-html="item.name" class="item-name"></span>
       </div>
       <div v-if="commonList && commonList.length < 8" class="list-add" @click="setFunctions">
-        <span class="icon-add"></span>
+        <span class="icon-add-img"></span>
         <span class="add-text">添加常用功能</span>
       </div>
     </div>
@@ -182,7 +182,7 @@ export default {
       font-size: 14px;
       cursor: pointer;
     }
-    .icon-set{
+    .icon-set-img{
       display: inline-block;
       width: 18px;
       height: 18px;
@@ -222,7 +222,7 @@ export default {
       width: 44px;
       height: 44px;
     }
-    .icon-add{
+    .icon-add-img{
       display: inline-block;
       width: 44px;
       height: 44px;

+ 32 - 0
src/router/medical-field.js

@@ -0,0 +1,32 @@
+// 医疗领域化路由
+export default [
+  {
+    path: '/medical/buy',
+    name: 'medical_buy',
+    component: () => import('@/views/medical-field/Buy.vue')
+  },
+  // 经销商搜索(找经销商)
+  {
+    path: '/medical/distributorSearch',
+    name: 'distrubutorSearch',
+    component: () => import('@/views/medical/DistributorSearch.vue')
+  },
+  //完善信息
+  {
+    path: '/medical/Credentials',
+    name: 'medical_Credentials',
+    component: () => import('@/views/medical-field/Credentials.vue')
+  },
+  // 医疗领域化-找医疗机构
+  {
+    path: '/medical/find_medical',
+    name: 'find_medical',
+    component: () => import('@/views/medical/FindMedical.vue')
+  },
+  // 医疗机构画像
+  {
+    path: '/medical/medical_portrait/:mid',
+    name: 'medical_portrait',
+    component: () => import('@/views/medical-field/MedicalPortrait.vue')
+  }
+]

+ 14 - 6
src/router/router-interceptors.js

@@ -6,7 +6,8 @@ import { getEntNicheAuth } from '@/api/modules'
 // 权限控制白名单-路由path
 const powerCheckPathWhiteRegList = [
   /free_*/,
-  /\/big\/page\/index/
+  /\/big\/page\/index/,
+  /medical/
 ]
 // 权限控制白名单-路由名
 const powerCheckWhiteList = [
@@ -27,7 +28,6 @@ const regListCheck = function (regList, path) {
     return reg.test(path)
   })
 }
-
 router.beforeEach(async (to, from, next) => {
   let { power, info } = store.state.user
   if (!Object.keys(info).length) {
@@ -47,11 +47,19 @@ router.beforeEach(async (to, from, next) => {
   }
   if (powerCheckWhiteList.includes(to.name) || regListCheck(powerCheckPathWhiteRegList, to.path)) {
     if (to.name === 'ent_ser_portrait') {
+      console.log(to.query.ismedical)
       if (info.memberStatus > 0 && (power.indexOf(4) !== -1 || power.indexOf(13) !== -1)) {
-        next({
-          path: `/ent_portrait/${encodeURIComponent(to.params.eId)}`,
-          replace: true
-        })
+        if (to.query.ismedical === '1') {
+          next({
+            path: `/ent_portrait/${encodeURIComponent(to.params.eId)}?ismedical=1`,
+            replace: true
+          })
+        } else {
+          next({
+            path: `/ent_portrait/${encodeURIComponent(to.params.eId)}`,
+            replace: true
+          })
+        }
       } else {
         next()
       }

+ 4 - 0
src/router/router.js

@@ -3,11 +3,14 @@ import VueRouter from 'vue-router'
 import routers from './routers'
 import workspace from './workspace'
 import vipRouters from './svip-routers'
+import medicalFields from './medical-field'
 
 if (process.env.NODE_ENV !== 'production') {
   Vue.use(VueRouter)
 }
 
+console.log(medicalFields)
+
 const router = new VueRouter({
   mode: 'history',
   base: window.__POWERED_BY_QIANKUN__ ? window.__QIANKUN_ROUTER_BASE : process.env.VUE_APP_BASE_URL,
@@ -15,6 +18,7 @@ const router = new VueRouter({
     ...routers,
     ...workspace,
     ...vipRouters,
+    ...medicalFields,
     {
       path: '/404',
       name: '404',

+ 13 - 1
src/router/routers.js

@@ -1,7 +1,7 @@
 export default [
   {
     path: '/',
-    redirect: '/desktop'
+    redirect: '/workspace/dashboard'
   },
   {
     path: '/desktop',
@@ -173,5 +173,17 @@ export default [
     path: '/free/custom_unit_portrayal/:entName',
     name: 'custom_unit_portrayal',
     component: () => import('@/views/customer/Buyer.vue')
+  },
+  // 医疗领域化-关注的医疗机构
+  {
+    path: '/medical/institution_followed',
+    name: 'institution_followed',
+    component: () => import('@/views/medical/institutionFollowed.vue')
+  },
+  // 医疗领域化-关注的经销商
+  {
+    path: '/medical/distributor_followed',
+    name: 'distributor_follow',
+    component: () => import('@/views/medical/distributorFollowed.vue')
   }
 ]

+ 4 - 2
src/store/index.js

@@ -3,6 +3,7 @@ import Vuex from 'vuex'
 
 import user from './user'
 import forcast from './forcast'
+import medical from './medical'
 import workspace from './workspace'
 
 if (process.env.NODE_ENV !== 'production') {
@@ -18,7 +19,8 @@ export default new Vuex.Store({
   getters: {},
   modules: {
     user,
-    workspace,
-    forcast
+    forcast,
+    medical,
+    workspace
   }
 })

+ 31 - 0
src/store/medical.js

@@ -0,0 +1,31 @@
+import { defaultLocalPageData } from '@/utils/'
+export default {
+  namespaced: true,
+  state: () => ({
+    // 医院等级
+    hospitalLevel: defaultLocalPageData('medical-login-clear-hospital-level', []),
+    // 医院类型
+    hospitalType: defaultLocalPageData('medical-login-clear-hospital-type', []),
+    drawerModal: false
+  }),
+  mutations: {
+    // 存储医院等级
+    setHospitalLevel (state, list) {
+      state.hospitalLevel = list
+      localStorage.setItem('medical-login-clear-hospital-level', JSON.stringify(state.hospitalLevel))
+    },
+    // 存储医院类型
+    setHospitalType (state, list) {
+      state.hospitalType = list
+      localStorage.setItem('medical-login-clear-hospital-type', JSON.stringify(state.hospitalType))
+    },
+    // 设置抽屉组件遮罩显示隐藏(css伪类实现),解决弹层多层嵌套挂载body样式失效(qiankun沙箱bug)
+    setDrawerModal (state, data) {
+      state.drawerModal = data
+    }
+  },
+  actions: {},
+  getters: {
+    showDrawerModal: state => state.drawerModal
+  }
+}

+ 45 - 9
src/store/user.js

@@ -1,4 +1,4 @@
-import { recoveryPageData, defaultLocalPageData } from '@/utils/'
+import { defaultLocalPageData } from '@/utils/'
 import {
   getEntApi,
   getUserPower,
@@ -6,9 +6,10 @@ import {
   getSVIPBuyInfo,
   getEntNicheSubKeyList,
   entBaseInfo,
-  getBigMemberInfo
+  getBigMemberInfo,
+  getPushSetDetail
 } from '@/api/modules'
-import { formatKeywordsList } from '@/utils/format'
+import { formatKeywordsList, formatVipKeywordsList } from '@/utils/format'
 
 const vtMap = {
   free: 'f',
@@ -26,7 +27,7 @@ export default {
     info: {},
     // 业务范围
     scope: defaultLocalPageData('bigmember-login-clear-SCOPE', []),
-    // 采购内容设置
+    // 采购内容设置(大会员)
     buyClass: defaultLocalPageData('bigmember-login-clear-BUY_CLASS', []),
     // 大会员基本信息数据
     bigKeywordsData: defaultLocalPageData('bigmember-login-clear-keywords', []),
@@ -39,7 +40,11 @@ export default {
     // entInfo企业相关信息
     entInfo: {},
     // 用户访问灰显记录列表
-    visitedList: defaultLocalPageData('visited-path-list', [])
+    visitedList: defaultLocalPageData('visited-path-list', []),
+    vipSubscribeInfo: {},
+    vipScope: defaultLocalPageData('vip-login-clear-SCOPE', []),
+    // 超级订阅业务范围是否全部删除
+    isDeleteVipScope: defaultLocalPageData('vip-login-clear-DELETE_SCOPE', false)
   }),
   mutations: {
     setNewEnt (state, data) {
@@ -75,7 +80,7 @@ export default {
         state.entInfo = obj
       }
     },
-    // 设置的业务范围
+    // 设置的业务范围(大会员)
     setScope (state, data) {
       state.scope = data
       localStorage.setItem('bigmember-login-clear-SCOPE', JSON.stringify(data))
@@ -121,6 +126,19 @@ export default {
     addVisited (state, list) {
       state.visitedList = list
       localStorage.setItem('visited-path-list', JSON.stringify(state.visitedList))
+    },
+    setVipSubscribeInfo (state, data) {
+      state.vipSubscribeInfo = data
+    },
+    // 设置的业务范围(超级订阅)
+    setVipScope (state, data) {
+      state.vipScope = data
+      localStorage.setItem('vip-login-clear-SCOPE', JSON.stringify(data))
+    },
+    // 删除全部业务范围
+    clearVipScope (state, data) {
+      state.isDeleteVipScope = data
+      localStorage.setItem('vip-login-clear-DELETE_SCOPE', data)
     }
   },
   actions: {
@@ -188,11 +206,17 @@ export default {
       } catch (error) {}
     },
     // 超级订阅用户获取订阅关键词
-    async getSvipUserSubscribeList ({ commit }) {
+    async getSvipUserSubscribeList ({ commit, state }) {
       try {
         const { data = {} } = await getSVIPBuyInfo()
-        if (data && data.items) {
-          commit('setSubscribeList', data.items)
+        if (data) {
+          commit('setVipSubscribeInfo', data)
+          if (data.items) {
+            commit('setSubscribeList', data.items)
+            if (state.isDeleteVipScope) return
+            commit('setVipScope', formatVipKeywordsList(data.items))
+          }
+          return formatVipKeywordsList(data.items)
         }
       } catch (error) {}
     },
@@ -214,6 +238,7 @@ export default {
       } else if (vt === 'v') {
         // 超级订阅
         await dispatch('getSvipUserSubscribeList')
+        await dispatch('getVipPushDetailInfo')
       } else if (vt === 'm') {
         // 大会员
         await dispatch('getKeywordsList')
@@ -223,6 +248,17 @@ export default {
       } else {
         // 无操作
       }
+    },
+    // 获取超级订阅推送设置信息(移动端接口,pc未整合到一块)
+    async getVipPushDetailInfo ({ commit, state }) {
+      try {
+        const { data = {} } = await getPushSetDetail({
+          item: 'super_subscribe'
+        })
+        if (data) {
+          commit('setVipSubscribeInfo', Object.assign(state.vipSubscribeInfo, data))
+        }
+      } catch (error) {}
     }
   },
   getters: {

+ 15 - 1
src/utils/format.js

@@ -1,4 +1,4 @@
-// 处理关键词数据 转化成美元分类的关键词数组
+// 处理大会员关键词数据 转化成没有分类的关键词数组
 export function formatKeywordsList (res) {
   if (!res || !res.member_jy.a_items) return
   const data = res.member_jy.a_items
@@ -12,3 +12,17 @@ export function formatKeywordsList (res) {
   })
   return newArr
 }
+
+// 处理超级订阅关键词数据 转化成没有分类的关键词数组
+export function formatVipKeywordsList (res) {
+  if (!res) return
+  const newArr = []
+  res.forEach((v) => {
+    if (v.a_key) {
+      v.a_key.forEach((s) => {
+        newArr.push(s)
+      })
+    }
+  })
+  return newArr
+}

+ 26 - 0
src/utils/globalFunctions.js

@@ -589,3 +589,29 @@ export function dateMatter(time, drag = 'normal') {
 
   return timeStr;
 }
+
+export function transData (jsonArr, idStr, pidStr, childrenStr) {
+  // 存放的最终结果树数组
+  const result = []
+  const id = idStr
+  const pid = pidStr
+  const children = childrenStr
+  const len = jsonArr.length
+  // 遍历得到以id为键名的对象(建立整棵树的索引)
+  const hash = {}
+  jsonArr.forEach(item => {
+    hash[item[id]] = item
+  })
+  for (let j = 0; j < len; j++) {
+    const jsonArrItem = jsonArr[j];
+    const hashItem = hash[jsonArrItem[pid]];
+    if (hashItem) {
+      // 如果当前项还没有children属性,则添加该属性并设置为空数组
+      !hashItem[children] && (hashItem[children] = [])
+      hashItem[children].push(jsonArrItem)
+    } else {
+      result.push(jsonArrItem)
+    }
+  }
+  return result
+}

+ 62 - 20
src/views/SubPush.vue

@@ -19,28 +19,28 @@
       </div>
       <div class="border-box">
         <TimeSelector ref="timeSelector" selectorTime="sub"  @onChange="changeTime" selectorType="line">
-          <div class="filter-label" slot="header">选择时间:<span class="el-icon-jy-vip visibility-hidden" v-if="showVipTag"></span></div>
+          <div class="filter-label" :class="{'label-width':showVipTag}" slot="header">选择时间:<span class="el-icon-jy-vip visibility-hidden" v-if="showVipTag"></span></div>
         </TimeSelector>
         <AreaSelector @onChange="changeArea" :beforeTabClick="beforeSelected" ref="areaSelector" selectorType="line">
-          <div class="filter-label" slot="header">选择区域:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
+          <div class="filter-label" :class="{'label-width':showVipTag}" slot="header">选择区域:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
         </AreaSelector>
         <IndustrySelector @onChange="changeIndustry" :beforeChange="beforeSelected" selectorType="line">
-          <div class="filter-label" slot="header">行业:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
+          <div class="filter-label" :class="{'label-width':showVipTag}" slot="header">行业:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
         </IndustrySelector>
         <PriceSelector @onChange="onPriceChange" :beforeConfirm="beforeSelected" selectorType="line">
-          <div class="filter-label" slot="header">价格区间:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
+          <div class="filter-label" :class="{'label-width':showVipTag}" slot="header">价格区间:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
         </PriceSelector>
         <IndustrySelector :class="{ pdl6: this.showVipTag }" @onChange="changeBuyer" :beforeChange="beforeSelected" dataType="buyer" selectorType="line">
-          <div class="filter-label" slot="header">采购单位类型:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
+          <div class="filter-label" :class="{'label-width':showVipTag}" slot="header">采购单位类型:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
         </IndustrySelector>
         <InfoTypeSelector class="my-line" @onChange="changeInfo" :beforeChange="beforeSelected" selectorType="line">
-          <div class="filter-label" slot="header">信息类型:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
+          <div class="filter-label" :class="{'label-width':showVipTag}" slot="header">信息类型:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
         </InfoTypeSelector>
         <PopSelector @onChange="changeKeys" ref="keySelector" :beforeTabClick="beforeSelected" selectorType="line">
-          <div class="filter-label" slot="header">关键词:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
+          <div class="filter-label" :class="{'label-width':showVipTag}" slot="header">关键词:<span class="el-icon-jy-vip" v-if="showVipTag"></span></div>
         </PopSelector>
         <selectorCard cardType="line">
-          <div slot="header" class="s-header filter-label">
+          <div slot="header" class="s-header filter-label" :class="{'label-width':showVipTag}">
             <slot name="header">附件:<span class="el-icon-jy-vip" v-if="showVipTag"></span></slot>
           </div>
           <RadioGroup
@@ -67,11 +67,15 @@
       </Dialog>
       <FollowOfficialAccountDialog :visible.sync="dialog.toFollowOfficialAccount"></FollowOfficialAccountDialog>
     </div>
+    <DrawerCard customClass="drawer-class" :with-header="false" percent="600px" confirmText="保存" v-model="showDrawer" @close="onCloseDrawer" @saveData="onSaveDrawer">
+      <ConfigContent :init="vipSubscribeInfo" ref="configContentRef"></ConfigContent>
+    </DrawerCard>
   </div>
 </template>
 
 <script>
 import { Button } from 'element-ui'
+import DrawerCard from '@/components/drawer/Drawer.vue'
 import PushList from '@/components/push-list/PushList.vue'
 import SelectorCard from '@/components/selector/SelectorCard.vue'
 import AreaSelector from '@/components/selector/AreaSelector.vue'
@@ -83,7 +87,8 @@ import PriceSelector from '@/components/selector/PriceSelector.vue'
 import RadioGroup from '@/components/selector/RadioGroup.vue'
 import Dialog from '@/components/dialog/Dialog.vue'
 import FollowOfficialAccountDialog from '@/components/dialog/FollowOfficialAccountDialog.vue'
-import { getFreeUserPushInfo } from '@/api/modules/'
+import ConfigContent from '@/components/subscribe-manager/index'
+import { getFreeUserPushInfo, updateVipSubscribe } from '@/api/modules/'
 import { dateFormatter, openSelfLink } from '@/utils/'
 import { mapState, mapGetters, mapActions } from 'vuex'
 
@@ -99,8 +104,10 @@ export default {
     InfoTypeSelector,
     PriceSelector,
     RadioGroup,
+    DrawerCard,
     Dialog,
     FollowOfficialAccountDialog,
+    ConfigContent,
     [Button.name]: Button
   },
   data () {
@@ -145,7 +152,8 @@ export default {
         fileExists: '',
         price: '',
         exportNum: ''
-      }
+      },
+      showDrawer: false // 订阅设置弹窗
     }
   },
   computed: {
@@ -166,6 +174,7 @@ export default {
       // 免费f / 商机管理s / 超级订阅v / 大会员m
       // 商机管理不显示
       if (this.vt === 's') {
+        // return true
         const { admin_system: adminSystem, admin_department: adminDepartment } = this.entInfo
         return adminSystem || adminDepartment
       }
@@ -177,7 +186,8 @@ export default {
     },
     ...mapState({
       entInfo: state => state.user.entInfo,
-      subscribeKeyList: state => state.user.subscribeKeyList
+      subscribeKeyList: state => state.user.subscribeKeyList,
+      vipSubscribeInfo: state => state.user.vipSubscribeInfo
     }),
     ...mapGetters('user', {
       autoVt: 'vt'
@@ -188,7 +198,8 @@ export default {
       'isNewEntNiche',
       'bigmember',
       'svip'
-    ])
+    ]),
+    ...mapGetters('medical', ['showDrawerModal'])
   },
   async created () {
     this.getParams()
@@ -331,7 +342,7 @@ export default {
         this.dialog.toFollowOfficialAccount = true
       } else if (this.vt === 'v') {
         // 超级订阅
-        this.dialog.toFollowOfficialAccount = true
+        this.showDrawer = true
       } else if (this.vt === 'm') {
         openSelfLink(this.$router.resolve('/set_subscribe/config'))
       } else if (this.vt === 's') {
@@ -384,6 +395,23 @@ export default {
       this.filters.area = area.join(',')
       this.filters.city = citys.join(',')
       this.doQuery()
+    },
+    onCloseDrawer (data) {
+      this.showDrawer = data
+    },
+    async onSaveDrawer () {
+      const params = this.$refs.configContentRef.onConfirm()
+      console.log(params, 'fsfff')
+      const { data, error_msg: msg } = await updateVipSubscribe(params)
+      if (data && data.status === 1) {
+        this.$nextTick(() => {
+          this.showDrawer = false
+          this.getSubScribeKeyList({ vt: this.vt })
+          this.$forceUpdate()
+        })
+      } else {
+        this.$toast(msg, 2000)
+      }
     }
   }
 }
@@ -399,7 +427,7 @@ export default {
       background-color: transparent;
       .page-content {
         width: 100%;
-        padding-top: 0;
+        padding: 0;
         > .border-box {
           margin-top: 0;
           overflow: hidden;
@@ -416,8 +444,14 @@ export default {
   .page--sub-push {
     background-color: #fff;
     ::v-deep {
+      .drawer-class{
+        background: #F7F9FC;
+      }
+      .selector-card.s-line{
+        padding: 12px 16px;
+      }
       .selector-card.s-line .selector-card-header {
-        min-width: 114px;
+        // min-width: 114px;
       }
       .selector-card.industry-selector  .selector-card-content .j-button-item.active {
         color: #fff;
@@ -460,7 +494,7 @@ export default {
       display: flex;
       align-items: center;
       justify-content: space-between;
-      padding: 24px 0;
+      padding: 0 0 24px 0;
       .header-right {
         display: flex;
         align-items: center;
@@ -512,7 +546,13 @@ export default {
       display: flex;
       align-items: center;
       justify-content: flex-end;
-      font-size: 16px;
+      font-size: 14px;
+      white-space: nowrap;
+      min-width: 98px;
+      color: #686868;
+    }
+    .label-width{
+      min-width: 140px;
     }
 
     .sub-manager {
@@ -532,10 +572,9 @@ export default {
     }
 
     .page-content {
-      width: 1200px;
+      // width: 1200px;
+      padding: 24px 24px 80px;
       margin: 0 auto;
-      padding-top: 40px;
-      padding-bottom: 80px;
     }
     .border-box {
       margin-top: 12px;
@@ -546,4 +585,7 @@ export default {
       margin-top: 24px;
     }
   }
+  .drawer-class{
+    background: #F7F9FC;
+  }
 </style>

+ 3 - 0
src/views/analysisReport/components/MarketOverview.vue

@@ -121,6 +121,9 @@ export default {
     }
   }
   .icon-reverse {
+    ::before{
+      content: ''!important;
+    }
     .el-icon-top {
       transform: rotate(180deg);
     }

+ 15 - 0
src/views/medical-field/Buy.vue

@@ -0,0 +1,15 @@
+<template>
+  <div class="medical-buy">12313</div>
+</template>
+
+<script>
+export default {
+  name: 'medical-field-production-buy',
+  data () {
+    return {}
+  }
+}
+</script>
+<style lang="scss" scoped>
+
+</style>

+ 827 - 0
src/views/medical-field/Credentials.vue

@@ -0,0 +1,827 @@
+<template>
+  <div>
+    <div id="Credentials">
+      <div class="container_ clearfix" v-loading="loading">
+        <el-form class="user-form" :model="form" :rules="rules" ref="ruleForm">
+          <div class="basic clearfix">
+            <div class="form-title">基本信息</div>
+            <div class="form-main clearfix">
+              <div class="short-control">
+                <el-form-item label="姓名 :" prop="name">
+                  <el-input v-model.trim="form.name" class="data-short-input item-input" placeholder="请输入姓名"
+                    @focus="nameFocus" required>
+                  </el-input>
+                  <span class="item-error" slot='error' slot-scope="scope">
+                    <i class="el-icon-error" style="margin-right:2px;"></i><span>{{ scope.error }}</span>
+                  </span>
+                </el-form-item>
+              </div>
+              <div class="short-control">
+                <el-form-item label="手机号 :" prop="phone">
+                  <el-input v-model.trim="form.phone" maxlength="11" @input="inputPhone"
+                    class="data-short-input item-input" placeholder="请输入手机号" @focus="phoneFocus">
+                  </el-input>
+                  <span class="item-error" slot='error' slot-scope="scope">
+                    <i class="el-icon-error" style="margin-right:2px;"></i><span>{{ scope.error }}</span>
+                  </span>
+                </el-form-item>
+              </div>
+              <div class="short-control">
+                <el-form-item label="邮箱 :" prop="mail">
+                  <el-input v-model.trim="form.mail" class="data-short-input item-input" placeholder="请输入邮箱"
+                    @focus="mailFocus"></el-input>
+                  <span class="item-error" slot='error' slot-scope="scope">
+                    <i class="el-icon-error" style="margin-right:2px;"></i><span>{{ scope.error }}</span>
+                  </span>
+                </el-form-item>
+              </div>
+            </div>
+          </div>
+          <div class="company clearfix">
+            <div class="form-title">公司信息</div>
+            <div class="form-main">
+              <div class="short-control company_class" style="position: relative;">
+                <el-form-item label="公司名称 :" prop="companyName">
+                  <el-input v-model.trim="form.companyName" class="data-long-input item-input company-name"
+                    @focus="companyFocus" @blur="companyblur" @input="searchCompany" placeholder="请输入准确的公司名称">
+                  </el-input>
+                  <span class="item-error" slot='error' slot-scope="scope">
+                    <i class="el-icon-error" style="margin-right:2px;"></i><span>{{ scope.error }}</span>
+                  </span>
+                </el-form-item>
+                <div class="company-result" v-show="showSearchResult">
+                  <div class="company-list" v-for="item in companyList" :key="item" @click="selectCompany(item)"
+                    v-html="item"></div>
+                </div>
+              </div>
+              <div class="short-control">
+                <el-form-item label="职位 :" prop="job">
+                  <el-select popper-class="Credentials-custom-select" v-model="form.job" placeholder="请选择职位名称"
+                    class="data-short-input item-input job-input" clearable>
+                    <el-option v-for="item in jobData" :key="item.value" :label="item.label" :value="item.value">
+                    </el-option>
+                  </el-select>
+                  <el-select style="margin-left:12px;" v-if="showBranch" popper-class="Credentials-custom-select"
+                    v-model="form.branch" placeholder="请选择部门" class="data-short-input item-input job-input" clearable>
+                    <el-option v-for="item in branchData" :key="item.value" :label="item.label" :value="item.value">
+                    </el-option>
+                  </el-select>
+                  <span class="item-error" slot='error' slot-scope="scope">
+                    <i class="el-icon-error" style="margin-right:2px;"></i><span>{{ scope.error }}</span>
+                  </span>
+                </el-form-item>
+              </div>
+            </div>
+          </div>
+          <div class="dialog-footer">
+            <el-button class="submit-btn" @click="submitForm('ruleForm')" :loading="btnLoading">保存设置</el-button>
+          </div>
+        </el-form>
+      </div>
+
+    </div>
+    <informationSuccess ref="Isuccess" @know="know_"></informationSuccess>
+  </div>
+
+</template>
+<script>
+import { jobJson, branchJson } from '@/assets/js/selector.js'
+import { Form, FormItem, Button, Select, Input, Option } from 'element-ui'
+import { debounce } from '@/utils/'
+import { domainUserauthinfo, domainUsersave } from '@/api/modules/medicalField.js'
+import informationSuccess from '@/components/common/informationSuccess.vue'
+export default {
+  name: "Credentials",
+  components: {
+    [Form.name]: Form,
+    [FormItem.name]: FormItem,
+    [Button.name]: Button,
+    [Select.name]: Select,
+    [Input.name]: Input,
+    [Option.name]: Option,
+    informationSuccess
+  },
+  props: {},
+  data() {
+    var this_ = this
+    var validName = (rule, value, callback) => {
+      if (value === '') {
+        // ⓧ 
+        return callback(new Error('姓名为必填项'))
+      } else {
+        if (value.length < 2) {
+          return callback(new Error('姓名填写不正确'))
+        }
+        callback()
+      }
+    }
+    var validPhone = (rule, value, callback) => {
+      var status = /^1[3456789]\d{9}$/.test(value)
+      if (value === '') {
+        return callback(new Error('手机号为必填项'))
+      } else if (!status) {
+        return callback(new Error('手机号填写不正确'))
+      } else {
+        callback()
+      }
+    }
+    var validEmail = (rule, value, callback) => {
+      var status = /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)
+      if (value === '') {
+        return callback(new Error('邮箱为必填项'))
+      } else {
+        if (!status) {
+          return callback(new Error('邮箱填写不正确'))
+        } else {
+          callback()
+        }
+      }
+    }
+    var validCompany = (rule, value, callback) => {
+      if (value === '') {
+        return callback(new Error('公司名称为必填项'))
+      } else {
+        if (value.length < 2) {
+          return callback(new Error('公司名称至少输入2个字'))
+        } else {
+          callback()
+
+        }
+      }
+    }
+    var validJob = (rule, value, callback) => {
+      if (value === '') {
+        return callback(new Error('职位不能为空'))
+      } else {
+        if ((value.indexOf('总裁') === -1 || value.indexOf('总经理') === -1) && this.showBranch && !this.form.branch) {
+          return callback(new Error('部门不能为空'))
+        } else {
+          callback()
+        }
+      }
+    }
+    return {
+      loading: false,
+      btnLoading: false,
+      status_: false,//是否认证过
+      form: {
+        name: '', // 姓名
+        phone: '', // 手机号
+        mail: '', // 邮箱
+        companyName: '', // 公司名称
+        job: '', // 职位值
+        branch: '' // 部门
+      },
+      rules: {
+        name: [{
+          required: true,
+          validator: validName,
+          trigger: 'blur'
+        }],
+        phone: [{
+          required: true,
+          validator: validPhone,
+          trigger: 'blur'
+        }],
+        mail: [{
+          required: true,
+          validator: validEmail,
+          trigger: 'blur'
+        }],
+        companyName: [{
+          required: true,
+          validator: validCompany,
+          trigger: 'blur'
+        }],
+
+        job: [{
+          required: true,
+          validator: validJob
+        }]
+      },
+      showSearchResult: false, //公司选择框显隐
+      companyList: [],
+      jobData: [], // 职位数据
+      branchData: [], // 部门数据
+      source: 'Credentials',
+      list_choose:false,
+    };
+
+  },
+  created() {
+    this.jobData = jobJson.map((item) => {
+      return {
+        value: item,
+        label: item
+      }
+    })
+    this.branchData = branchJson.map((item) => {
+      return {
+        value: item,
+        label: item
+      }
+    })
+  },
+  mounted() {
+    this.getOldInfo()
+  },
+  filters: {},
+  computed: {
+    showBranch: function () {
+      // 切换到总裁或总经理之后 把之前选择的部门清空
+      if (this.form.job.indexOf('总裁') > -1 || this.form.job.indexOf('总经理') > -1) {
+        this.form.branch = ''
+      }
+      var zc = this.form.job.indexOf('总裁') > -1
+      var zjl = this.form.job.indexOf('总经理') > -1
+      return !(zc || zjl) && this.form.job
+    },
+  },
+  watch: {},
+  methods: {
+    know_(){
+      window.location.reload()
+
+    },
+    submitForm(formName) {//提交
+      var _this = this
+      this.$refs[formName].validate(function (valid) {
+        if (valid) {
+          _this.formAjax(_this.source)
+          console.log(valid)
+        } else {
+          console.log('error submit!!')
+          return false
+        }
+      })
+    },
+    // 查询表单回显信息
+    getOldInfo() {
+      this.loading = true
+      domainUserauthinfo().then(res => {
+        this.loading = false
+        if (res.error_code != 0) {
+          console.log(res.error_msg)
+          return
+        }
+        if (!res.data) {
+          console.log('data不存在')
+          return
+        }
+        this.status_ = res.data.status ? true : false // 是否认证过
+        this.form.name = res.data.name ? res.data.name : ''//  name  姓名
+        this.form.phone = res.data.phone ? res.data.phone : '' // phone 手机号
+        this.form.mail = res.data.mail ? res.data.mail : '' // mail 邮箱
+        this.form.job = res.data.position ? res.data.position : ''  // job 职位值
+        this.form.branch = res.data.department ? res.data.department : '' // branch 部门
+        this.form.companyName = res.data.ent_name ? res.data.ent_name : ''  // companyName公司名称
+      }).catch(err => {
+      })
+    },
+    // 表单提交请求
+    formAjax(source) {
+      this.btnLoading = true
+      let params = {
+        name: this.form.name,
+        phone: this.form.phone,
+        mail: this.form.mail,
+        ent_name: this.form.companyName, // 公司名称
+        position: this.form.job, //职级
+        department: this.form.job.indexOf('总裁') > -1 || this.form.job.indexOf('总经理') > -1 ? '' : this.form.branch,//部门
+        operation_type: this.status_ ? 'update' : 'add' //认证过编辑否则新增
+      }
+      domainUsersave(params).then(res => {
+        this.btnLoading = false
+        if (res.error_code != 0) {
+          this.$toast(res.error_msg)
+          return
+        }
+        if (this.status_) {
+          this.$toast('保存成功')
+        } else {
+          this.$refs.Isuccess.passVisible = true
+          // this.$toast('恭喜获得医械云初级版产品使用权益')
+          // const url = '/jylab/medical/index.html'
+          // tryCallHooks({
+          //   fn: () => {
+          //     this.$BRACE.methods.open({
+          //       route: {
+          //         link: url,
+          //         appType: 'iframe'
+          //       }
+          //     })
+          //   },
+          //   spareFn: () => {
+          //     location.href = url
+          //   }
+          // })
+          this.getOldInfo()
+        }
+      }).catch(err => {
+        this.btnLoading = false
+      })
+    },
+    // 公司名称联想
+    getResult(name) {
+      // 名称为空或长度小于2不发请求
+      if (!name || name.length < 2) return
+      var _this = this
+      $.ajax({
+        type: 'POST',
+        url: '/jypay/user/company/association',
+        data: {
+          name: name,
+          companyCount: 10
+        },
+        heads: {
+          'content-type': 'application/x-www-form-urlencoded'
+        },
+        success: function (res) {
+          if (res.data && res.data.length > 0) {
+            var result = []
+            res.data.map(function (item) {
+              item = _this.brightKeyword(item)
+              result.push(item)
+              return result
+            })
+            _this.companyList = result
+            _this.showSearchResult = true
+          }
+        }
+      })
+    },
+    searchCompany: debounce(function (val) {
+      if (val === '') {
+        this.showSearchResult = false
+      }
+      this.list_choose = false
+      this.getResult(val)
+    }, 400),
+    // 选择联想出来的公司名称
+    selectCompany(item) {
+      this.list_choose = true
+      this.form.companyName = item.replace(/<.*?>/ig, '')
+      this.showSearchResult = false
+    },
+    // 关键词高亮
+    brightKeyword(val) {
+      var name = this.form.companyName
+      if (val.indexOf(name) !== -1) {
+        return val.replace(name, `<font style='color:#2ABED1;'>${name}</font>`)
+      } else {
+        return val
+      }
+    },
+    inputPhone(val) {
+      this.form.phone = val.replace(/[^\d]/g, '')
+    },
+    nameFocus() {
+      this.$refs.ruleForm.clearValidate(['name'])
+    },
+    phoneFocus() {
+      this.$refs.ruleForm.clearValidate(['phone'])
+    },
+    mailFocus() {
+      this.$refs.ruleForm.clearValidate(['mail'])
+    },
+    // 公司名称获取焦点时
+    companyFocus() {
+      this.$refs.ruleForm.clearValidate(['companyName'])
+
+    },
+    companyblur() {
+      setTimeout(() => {
+        if (!this.list_choose) {
+          this.form.companyName = ""
+          // this.showSearchResult=false
+
+        } 
+      }, 100);
+
+    },
+    otherFocus() {
+      this.$refs.ruleForm.clearValidate(['job'])
+    },
+
+  },
+};
+</script>
+
+<style lang="scss">
+#Credentials {
+  .container_ {
+    width: calc(100% - 48px);
+    margin: 24px auto;
+    border-radius: 8px 8px 8px 8px;
+    background-color: #fff;
+    box-sizing: border-box;
+    padding: 24px 24px 38px 40px;
+    height: 522px;
+  }
+
+  .item-error {
+    color: #F56C6C;
+    font-size: 12px;
+    line-height: 1;
+    position: absolute;
+    left: 0;
+    padding: 0px 0 0 90px;
+    top: 99%;
+  }
+
+  /* element-ui reset */
+  .user-form {
+    .el-checkbox-group {
+      white-space: nowrap;
+    }
+
+    .company-type>.el-form-item__label,
+    .company-type>.el-form-item__content {
+      line-height: 22px;
+    }
+
+    .el-form-item {
+      margin-bottom: 20px;
+    }
+
+    .el-form-item__error {
+      padding: 0px 0 0 90px;
+      top: 99%;
+    }
+
+    .el-form-item__label {
+      width: 80px;
+      padding: 0;
+      margin-right: 8px;
+      color: #686868;
+      margin-bottom: 0;
+      font-weight: normal;
+    }
+
+    .el-input__inner {
+      height: 36px;
+      line-height: 36px;
+    }
+
+    .el-checkbox__input.is-focus {
+      border-color: #2CB7CA;
+    }
+
+    .el-checkbox {
+      margin-right: 12px;
+    }
+
+    .el-checkbox__label {
+      padding-left: 3px;
+      color: #1D1D1D;
+    }
+
+    .el-checkbox__input.is-checked+.el-checkbox__label {
+      color: #1D1D1D;
+    }
+
+    .el-checkbox__input.is-checked .el-checkbox__inner,
+    .el-checkbox__input.is-indeterminate .el-checkbox__inner {
+      background-color: #2CB7CA;
+      border-color: #2CB7CA;
+    }
+
+    .el-checkbox__inner:hover {
+      border-color: #DCDFE6;
+    }
+
+    .el-cascader-node.in-active-path,
+    .el-cascader-node.is-active,
+    .el-cascader-node.is-selectable.in-checked-path,
+    .el-select-dropdown__item.selected {
+      color: #2CB7CA;
+    }
+
+    .cancel-btn:focus,
+    .cancel-btn:hover {
+      background: #FFF;
+      border: 1px solid #DCDFE6;
+      color: #606266;
+    }
+
+    .el-textarea {
+      width: calc(100% - 90px);
+    }
+
+    .el-textarea__inner {
+      min-height: 36px;
+    }
+
+    .el-input.is-active .el-input__inner,
+    .el-input__inner:focus {
+      border-color: #2CB7CA;
+    }
+
+    .el-select .el-input.is-focus .el-input__inner,
+    .el-select .el-input__inner:focus {
+      border-color: #2CB7CA;
+    }
+
+    .el-cascader .el-input .el-input__inner:focus,
+    .el-cascader .el-input.is-focus .el-input__inner {
+      border-color: #2CB7CA;
+    }
+
+    li.el-cascader-node,
+    .el-select-dropdown__item {
+      float: none;
+    }
+
+    .job-input {
+      // width: calc(50% - 70px) !important;
+      width: 290px;
+    }
+
+    .job-name-iput {
+      width: calc(50% - 90px) !important;
+      margin-left: 12px;
+    }
+  }
+
+  .mask {
+    position: fixed;
+    width: 100%;
+    height: 100%;
+    right: 0;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, .5);
+    z-index: 1031;
+  }
+
+  /* 滚动条样式 */
+  .user-data-dialog ::-webkit-scrollbar {
+    /*滚动条整体样式*/
+    width: 6px;
+    height: 1px;
+  }
+
+  .user-data-dialog ::-webkit-scrollbar-thumb {
+    /*滚动条里面小方块*/
+    border-radius: 10px;
+    background: #e0e0e0;
+  }
+
+  .user-data-dialog ::-webkit-scrollbar-track {
+    /*滚动条里面轨道*/
+    box-shadow: inset 0 0 5px #fff;
+    border-radius: 10px;
+    background: transparent;
+  }
+
+  // ::-webkit-scrollbar-thumb:hover {
+  //     height: 50px;
+  //     background-color: #878987;
+  //     -webkit-border-radius: 6px
+  // }
+  .dialog-container {
+    position: fixed;
+    left: 50%;
+    top: 50%;
+    width: 768px;
+    max-height: 780px;
+    margin: 0 auto;
+    padding: 32px;
+    background: #fff;
+    border-radius: 8px;
+    transform: translate(-50%, -50%);
+    z-index: 2000;
+    box-sizing: border-box;
+    overflow-y: auto;
+  }
+
+  .dialog-header {
+    color: #1D1D1D;
+    font-size: 16px;
+    line-height: 28px;
+    text-align: center;
+
+    &.fs18 {
+      font-size: 18px;
+    }
+
+    &.mt-4 {
+      margin-top: 4px;
+    }
+  }
+
+  .dialog-content {
+    margin-top: 20px;
+  }
+
+  .form-title {
+    font-size: 16px;
+    line-height: 22px;
+    color: #2CB7CA;
+  }
+
+  .form-main {
+    margin: 18px 0 14px;
+  }
+
+  .short-control {
+    width: 680px
+  }
+
+  .long-control {
+    width: 100%;
+  }
+
+  .item-input {
+    width: auto;
+  }
+
+  .company-result {
+    position: absolute;
+    width: calc(600px - 16px);
+    top: 39px;
+    left: 88px;
+    max-height: 152px;
+    background-color: #fff;
+    border: 1px solid #2CB7CA;
+    z-index: 100;
+    // border-radius: 4px;
+    overflow-y: auto;
+  }
+
+  .company_class ::-webkit-scrollbar-track-piece {
+    background-color: #fff;
+    -webkit-border-radius: 0
+  }
+
+  .company_class::-webkit-scrollbar {
+    width: 8px;
+    height: 10px
+  }
+
+  .company_class ::-webkit-scrollbar-thumb {
+    height: 30px;
+    background-color: #e0e0e0;
+    -webkit-border-radius: 6px;
+    outline: 2px solid #fff;
+    outline-offset: -2px;
+    border: 2px solid #fff;
+    filter: alpha(opacity=50);
+    -moz-opacity: 0.5;
+    -khtml-opacity: 0.5;
+    opacity: 0.5
+  }
+
+  .company-list {
+    padding: 0 16px;
+    height: 30px;
+    line-height: 30px;
+    font-size: 14px;
+    color: #1D1D1D;
+    cursor: pointer;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    white-space: nowrap;
+  }
+
+  .company-list:hover {
+    background: #ECECEC;
+  }
+
+  .data-short-input {
+    width: calc(100% - 90px);
+    // height: 36px;
+    box-sizing: border-box;
+    border-radius: 4px;
+  }
+
+  .data-long-input {
+    width: calc(100% - 90px);
+    // height: 36px;
+    box-sizing: border-box;
+    border-radius: 4px;
+  }
+
+  .agree-service {
+    text-align: center;
+  }
+
+  .agree-service .el-checkbox__label,
+  .agree-service .el-checkbox__input.is-checked+.el-checkbox__label {
+    color: #686868;
+  }
+
+  .dialog-footer {
+    padding-top: 20px;
+    text-align: center;
+    float: left;
+  }
+
+  .cancel-btn {
+    width: 132px;
+    height: 36px;
+    line-height: 36px;
+    padding: 0;
+    border-radius: 6px;
+  }
+
+  .submit-btn {
+    width: 180px;
+    height: 46px;
+    padding: 0;
+    color: #fff;
+    line-height: 46px;
+    background: #2CB7CA;
+    border-radius: 6px;
+    border: 1px solid #2CB7CA;
+    font-size: 16px;
+    margin-left: 90px;
+  }
+
+  .submit-btn:hover {
+    color: #fff;
+    border-color: #2CB7CA;
+    background: #2CB7CA;
+  }
+
+  .success-dialog-container {
+    position: fixed;
+    width: 380px;
+    top: 50%;
+    left: 50%;
+    padding: 20px;
+    transform: translateX(-50%) translateY(-50%);
+    background: #ffffff;
+    border-radius: 8px;
+    transition: all 2s linear;
+    z-index: 1038;
+  }
+
+  .success-title {
+    padding: 12px 0 20px;
+    color: #1d1d1d;
+    line-height: 28px;
+    font-size: 18px;
+    text-align: center;
+  }
+
+  .success-content {
+    text-align: center;
+    color: #686868;
+    line-height: 22px;
+    font-size: 14px;
+  }
+
+  .success-footer {
+    padding: 32px 0 12px;
+    text-align: center;
+  }
+
+  .custom-btn {
+    display: inline-block;
+    width: 132px;
+    height: 36px;
+    line-height: 36px;
+    background: #2cb7ca;
+    border-radius: 6px;
+    text-align: center;
+    color: #fff;
+    font-size: 16px;
+    cursor: pointer;
+    border: 0;
+  }
+
+  .custom-btn:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+
+}
+
+.Credentials-custom-select {
+  margin-top: -23px !important;
+  border: 1px solid #2CB7CA;
+  border-radius: 2px;
+
+  .el-select-dropdown__wrap {
+    max-height: 220px;
+  }
+
+  .popper__arrow {
+    display: none;
+  }
+
+  .el-popper[x-placement^=bottom] {
+    margin-top: 0;
+  }
+
+  .el-popper[x-placement^=bottom] .popper__arrow {
+    display: none;
+  }
+
+  .el-select-dropdown__item.selected {
+    color: #2CB7CA !important;
+  }
+}
+</style>

+ 365 - 0
src/views/medical-field/MedicalPortrait.vue

@@ -0,0 +1,365 @@
+<template>
+  <div class="medical_portrait">
+    <div class="me_ent_info" v-loading="infoloading">
+      <div class="me_info_head">
+        <div class="me_head_left">
+          <div class="me_l_img">
+            <img src="@/assets/images/hospital@3x.png" alt="">
+          </div>
+          <div class="me_l_name">
+            <span>{{info.company_name}}</span>
+            <div class="me_l_tags">
+              <span v-if="info.area">{{info.area}}</span>
+              <span v-if="info.level">{{info.level}}</span>
+              <span>{{setBusstype}}</span>
+              <span v-if="info.mi_type">{{info.mi_type}}</span>
+            </div>
+          </div>
+        </div>
+        <div class="me-head-right">
+          <div class="u-follow" @click="setClickClaim">
+            <span :class="{ icon_claim_yes: follow.followed, icon_claim_no: !follow.followed }"></span>
+            <span class="follow_text">{{ follow.followed ? '取消认领' : '认领' }}</span>
+          </div>
+        </div>
+      </div>
+      <div class="me_info_statick">
+        <ul class="me_info_s_ul">
+          <!-- <li class="s_ul_li">
+            <span class="list-num">{{info.establish_date || '--'}}</span>
+            <span class="list-label">成立年份</span>
+          </li> -->
+          <li class="s_ul_li">
+            <span class="list-num">{{info.beds || '--'}}</span>
+            <span class="list-label">床位数</span>
+          </li>
+          <li class="s_ul_li">
+            <span class="list-num">{{info.visit_perday || '--'}}</span>
+            <span class="list-label">门诊量(日)</span>
+          </li>
+          <li class="s_ul_li list_last">
+            <span class="list-num">{{info.doctorsnum || '--'}}</span>
+            <span class="list-label">员工数</span>
+          </li>
+        </ul>
+      </div>
+      <div class="me_info_main">
+        <ul class="me_i_m_ul">
+          <li class="me_i_m_li">
+            <div class="me_label">地址:</div>
+            <div class="me_content">{{info.address || '--'}}</div>
+          </li>
+          <!-- <li class="me_i_m_li">
+            <div class="me_label">网址:</div>
+            <div class="me_content">{{info.website || '--'}}</div>
+          </li> -->
+          <li class="me_i_m_li">
+            <div class="me_label">医院科室:</div>
+            <div class="me_content">{{info.departnames || '--'}}</div>
+          </li>
+          <li class="me_i_m_li">
+            <div class="me_label">医院设备:</div>
+            <div class="me_content">{{info.equipment || '--'}}</div>
+          </li>
+        </ul>
+      </div>
+    </div>
+    <div class="me_ent_container">
+      <medical-multiple-list :companyName="info.company_name" :config="{baseParam:{entId: decodeURIComponent(this.$route.params.mid)},isWinner:false, isMedical: true, hasPower:showConf13}"></medical-multiple-list>
+    </div>
+  </div>
+</template>
+
+<script>
+import MedicalMultipleList from '../portrayal/components/DynamicList.vue'
+import { getInstitution, setInstitutionClaim, institutionUnClaimed, isClaimed } from '@/api/modules'
+import { mapState } from 'vuex'
+export default {
+  name: 'medical_portrait',
+  components: {
+    MedicalMultipleList
+  },
+  data () {
+    return {
+      follow: {
+        followed: false,
+        loading: false
+      },
+      mid: '',
+      infoloading: false,
+      info: {}
+    }
+  },
+  created () {
+    this.mid = this.$route.params.mid
+    this.setFollow()
+    this.getMedicalInst()
+  },
+  computed: {
+    ...mapState({
+      infoPower: state => state.user.info
+    }),
+    setBusstype () {
+      switch (this.info.business_type) {
+        case 1:
+          return '公立'
+        case 2:
+          return '民营'
+        case 3:
+          return '其它'
+        default:
+          return '其它'
+      }
+    },
+    getCompany () {
+      return this.info.company_name
+    },
+    // 项目动态
+    showConf13 () {
+      return this.infoPower.power.indexOf(13) !== -1
+    }
+  },
+  methods: {
+    // 获取医疗机构基本信息
+    getMedicalInst () {
+      this.infoloading = true
+      const params = { company_id: this.mid }
+      getInstitution(params).then(res => {
+        if (res.error_code === 0) {
+          this.info = res.data
+        } else {
+          this.$toast(res.error_msg)
+        }
+        this.infoloading = false
+      }).catch((err) => {
+        console.log(err)
+        this.infoloading = false
+      })
+    },
+    setFollow () {
+      this.follow.loading = true
+      const param = {
+        company_id: this.mid,
+        type: 1
+      }
+      // 查询是否认领
+      isClaimed(param).then(res => {
+        if (res.error_code === 0) {
+          this.follow.followed = res.data.status
+          this.follow.loading = false
+        }
+      })
+    },
+    setClickClaim () {
+      if (this.follow.followed) {
+        this.setCancelClaim()
+      } else {
+        this.setOptionClaim()
+      }
+    },
+    // 认领
+    setOptionClaim () {
+      // const param = {
+      //   ent_name: this.info.company_name,
+      //   ent_id: this.mid
+      // }
+      setInstitutionClaim({
+        ent_name: this.info.company_name,
+        ent_id: this.mid
+      }).then(res => {
+        if (res.error_code === 0) {
+          this.follow.followed = true
+          this.$toast('认领成功')
+        } else if (res.error_code === 1013) {
+          this.follow.followed = false
+          this.$toast('医疗机构认领已达上限')
+        } else {
+          this.follow.followed = false
+          this.$toast(res.error_msg)
+        }
+      })
+    },
+    // 取消认领
+    setCancelClaim () {
+      const param = {
+        ent_id: this.mid
+      }
+      institutionUnClaimed(param).then(res => {
+        if (res.error_code === 0) {
+          this.follow.followed = false
+          this.$toast('取消认领成功')
+        } else {
+          this.follow.followed = true
+          this.$toast(res.error_msg)
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.medical_portrait{
+  width: 1200px;
+  margin: 24px auto;
+  .me_ent_info{
+    padding: 24px 30px 29px;
+    height: 100%;
+    background: #fff;
+    border-radius: 8px;
+  }
+  .me_info_head{
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    min-height: 64px;
+  }
+  .me_head_left{
+    display: flex;
+    align-items: flex-start;
+    justify-content: flex-start;
+    .me_l_img{
+      margin-right: 18px;
+      width: 64px;
+      height: 64px;
+      img{
+        width: 100%;
+        height: 100%;
+      }
+    }
+    .me_l_name{
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      font-size: 24px;
+      color: #171826;
+      line-height: 36px;
+      max-width: 900px;
+      .me_l_tags{
+        line-height: 20px;
+        span{
+          margin-right: 8px;
+          padding: 1px 8px;
+          background: #F5F5FB;
+          border-radius: 4px;
+          border: 1px solid #ECECEC;
+          font-size: 12px;
+          font-weight: 400;
+          color: #686868;
+          line-height: 18px;
+        }
+      }
+    }
+  }
+  .me-head-right {
+    margin-top: 6px;
+  }
+  .u-follow{
+    display: flex;
+    align-items: center;
+    line-height: 34px;
+    cursor: pointer;
+    .icon_claim_no{
+      display: flex;
+      width: 20px;
+      height: 20px;
+      background: url('~@/assets/images/icon/renling-01.png') no-repeat;
+      background-size: contain;
+    }
+    .icon_claim_yes{
+      display: flex;
+      width: 20px;
+      height: 20px;
+      background: url('~@/assets/images/icon/renling-active.png') no-repeat;
+      background-size: contain;
+    }
+    .follow_text {
+      margin-left: 4px;
+      font-size: 14px;
+      font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+      font-weight: 400;
+      color: #686868;
+      line-height: 22px;
+    }
+  }
+  .me_info_statick{
+    margin-top: 24px;
+    .me_info_s_ul{
+      display: flex;
+      align-items: center;
+      .s_ul_li{
+        position: relative;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        width: 168px;
+        height: 72px;
+        .list-num{
+          font-size: 20px;
+          color: #1D1D1D;
+          line-height: 32px;
+        }
+        .list-label{
+          font-size: 14px;
+          color: #999999;
+          line-height: 22px;
+        }
+        &::after{
+          position: absolute;
+          right: 0;
+          top: 16px;
+          content: '';
+          width: 1px;
+          height: 40px;
+          background: #ECECEC;
+        }
+      }
+      .list_last::after{
+        display: none;
+      }
+    }
+  }
+  .me_info_main{
+    margin-top: 24PX;
+    background: #F7F9FC;
+    .me_i_m_ul{
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      padding: 16px 16px 8px;
+      height: 100%;
+      .me_i_m_li{
+        display: flex;
+        justify-content: flex-start;
+        align-items: flex-start;
+        margin-bottom: 8px;
+        line-height: 22px;
+        font-size: 14px;
+        .me_label{
+          margin-right: 10px;
+          text-align: right;
+          width: 100px;
+          color: #999999;
+        }
+        .me_content{
+          flex: 1;
+          text-align: justify;
+          color: #1D1D1D;
+        }
+      }
+    }
+  }
+  .me_ent_container{
+    padding: 24px 30px 40px;
+    margin-top: 16px;
+    background: #fff;
+    border-radius: 8px;
+    .collect-table-list{
+      .table{
+        max-width: 100%!important;
+      }
+    }
+  }
+}
+</style>

+ 548 - 0
src/views/medical/DistributorSearch.vue

@@ -0,0 +1,548 @@
+<template>
+  <div class="page--potential">
+      <div class="border-box">
+        <div class="flex-r-c center sb top-title-box">
+          <span class="top-title-text">筛选条件</span>
+          <div @click="goManage" class="flex-r-c center manage-box">
+            <i class="iconfont icon-hui6"></i>
+            <span>业务范围设置</span>
+          </div>
+        </div>
+        <div class="top-hengxian"></div>
+        <div class="distributorInputDiv">
+          <p>经销商名称:</p>
+          <el-input
+            placeholder="请输入经销商名称"
+            v-model="filters.company_name"
+            clearable
+            class="distributorInput">
+          </el-input>
+        </div>
+        <AreaSelector comtype="portrait" @onChange="changeArea" ref="areaSelector" selectorType="line">
+          <div class="fontclass" slot="header">选择区域:</div>
+        </AreaSelector>
+        <BusinessScopeSelector class="ex-line-2" @onChange="changeBusiness" ref="businessScopeSelector" :initList="getScopeKeyList"  selectorType="line">
+          <div class="fontclass" slot="header">业务范围:</div>
+        </BusinessScopeSelector>
+        <div class="brandInputDiv">
+          <p>品牌名称:</p>
+          <el-input
+            placeholder="请输入品牌名称"
+            v-model="filters.brand"
+            clearable
+            class="brandInput">
+          </el-input>
+        </div>
+        <div v-show="showProductId" class="productInputDiv" v-for="(v, i) in filters.productList" :key="'0' + i">
+          <p :class="{ 'label-invisible': i !== 0 }">产品型号:</p>
+          <el-input
+            placeholder="请输入产品型号"
+            v-model="v.value"
+            clearable
+            class="productInput">
+          </el-input>
+          <span v-if="i+1===filters.productList.length" @click="addProduct()">+添加更多型号</span>
+          <i v-else @click="delProduct(i)" class="delete_product iconfont icon-delete"></i>
+        </div>
+        <div class="flex-r-c center button-group">
+          <button class="button-default" @click="changeSubmitInfo(false)">重置</button>
+          <button class="button-submit" @click="changeSubmitInfo(true)">确定</button>
+        </div>
+      </div>
+      <div class="content-list-box">
+        <potential-list
+          @goDetail="goDetail"
+          :title="title"
+          :filters="filters"
+          ref="pushList"
+          :showMore="false">
+        </potential-list>
+      </div>
+    <DrawerCard customClass="drawer-class" percent="600px" :with-header="false" v-model="showDrawer" @close="onCloseDrawer" @saveData="onSaveDrawer">
+      <ScopeContent ref="scopeRef"></ScopeContent>
+    </DrawerCard>
+  </div>
+</template>
+
+<script>
+import '@/assets/style/iconfont.css'
+import PotentialList from '@/components/push-list/DistributorSearch.vue'
+import AreaSelector from '@/components/selector/AreaSelector.vue'
+import BusinessScopeSelector from '@/components/selector/BusinessScopeSelector.vue'
+import ScopeContent from '@/components/scope/index.vue'
+import DrawerCard from '@/components/drawer/Drawer'
+import { Input, Button } from 'element-ui'
+import { mapState, mapActions } from 'vuex'
+import { provinceListMapExp } from '@/assets/js/selector.js'
+import { openSelfLink } from '@/utils'
+export default {
+  name: 'distrubutorSearch',
+  components: {
+    PotentialList,
+    AreaSelector,
+    BusinessScopeSelector,
+    DrawerCard,
+    ScopeContent,
+    [Input.name]: Input,
+    [Button.name]: Button
+  },
+  data () {
+    return {
+      title: '经销商列表',
+      showDrawer: false,
+      filters: {
+        business_scope: [],
+        business: {},
+        searchbool: 1,
+        listKey: new Date().getTime(),
+        company_name: '',
+        brand: '',
+        product_model: '',
+        productList: [
+          {
+            value: ''
+          }
+        ]
+      },
+      selectedScope: [],
+      subArea: {},
+      showProductId: false
+    }
+  },
+  methods: {
+    changeSubmitInfo (type) {
+      this.filters.searchbool = 1
+      if (type) {
+        const getSelected = this.$refs.businessScopeSelector.getState()
+        this.changeBusiness(getSelected)
+        this.$refs.pushList.doQuery(this.filters)
+      } else {
+        // this.filters = Object.assign({}, this.$options.data.bind(this)().filters)
+        this.filters.area_code = this.subArea
+        this.filters.business_scope = []
+        this.filters.business = {}
+        this.filters.company_name = ''
+        this.filters.brand = ''
+        this.filters.product_model = ''
+        this.filters.productList = [
+          {
+            value: ''
+          }
+        ]
+        try {
+          this.$refs.areaSelector.setCitySelected()
+          this.$refs.businessScopeSelector.setState()
+          this.$refs.pushList.doQuery()
+        } catch (error) {}
+      }
+    },
+    addProduct () {
+      this.filters.productList.push({
+        value: ''
+      })
+    },
+    delProduct (i) {
+      this.filters.productList.splice(i, 1)
+    },
+    onCloseDrawer (data) {
+      this.showDrawer = data
+    },
+    onSaveDrawer () {
+      this.$nextTick(() => {
+        this.$refs.scopeRef.saveSetting()
+        this.showDrawer = false
+      })
+      setTimeout(() => {
+        const list = this.getScopeKeyList
+        const s = this.selectedScope
+        const arr = []
+        if (s && s.length > 0) {
+          s.forEach(v => {
+            arr.push(list.includes(v))
+          })
+        }
+        const flag = arr.some(v => v)
+        this.$refs.businessScopeSelector.setState(flag ? s : [])
+      }, 30)
+    },
+    goDetail (item) {
+      let routeUrl = {}
+      if (this.userInfo.memberStatus > 0) {
+        routeUrl = this.$router.resolve({
+          path: `/ent_portrait/${item.company_id}?ismedical=1`
+        })
+      } else {
+        routeUrl = this.$router.resolve({
+          path: `/svip/ent_ser_portrait/${item.company_id}?ismedical=1`
+        })
+      }
+      openSelfLink(routeUrl)
+    },
+    goManage () {
+      this.showDrawer = true
+      this.selectedScope = this.$refs.businessScopeSelector.getState()
+    },
+    changeArea (area) {
+      if (area && Object.keys(area).length > 0) {
+        this.filters.area_code = area
+      } else {
+        this.filters.area_code = this.subArea
+      }
+    },
+    changeBusiness (item) {
+      let scope = this.scope
+      if (this.userInfo.memberStatus > 0) {
+        scope = this.scope
+      } else if (this.userInfo.vipStatus > 0) {
+        scope = this.vipScope
+      }
+      item = item.map(v => v.split(' ')[0])
+      const tempArr = []
+      scope.forEach(v => {
+        if (item.includes(v.key.join(' '))) {
+          tempArr.push(v)
+        }
+      })
+      this.filters.business_scope = tempArr
+    },
+    ...mapActions({
+      getVipInfo: 'user/getSvipUserSubscribeList'
+    }),
+    getAreaConfig () {
+      let areaData = {}
+      if (this.userInfo.memberStatus > 0) {
+        // 大会员
+        try {
+          areaData = this.subscribe.member_jy.o_area || {}
+          this.subArea = this.subscribe.member_jy.o_area || {}
+          this.formatAreaData(areaData)
+        } catch (error) {}
+      } else if (this.userInfo.vipStatus > 0) {
+        // 超级订阅
+        try {
+          areaData = this.vipSubscribeInfo.area || {}
+          this.subArea = this.vipSubscribeInfo.area || {}
+          this.formatAreaData(areaData)
+        } catch (error) {}
+      }
+    },
+    formatAreaData (areaData) {
+      console.log(areaData, '111')
+      const areaArr = []
+      for (const key in areaData) {
+        areaArr.push(key)
+      }
+      if (areaArr.length > 0) {
+        const initData = this.getInitArea(areaArr)
+        this.$refs.areaSelector.initList(initData)
+      } else {
+        this.$refs.areaSelector.initList()
+      }
+      this.filters.area_code = this.subArea
+    },
+    getInitArea (areaArr) {
+      if (!Array.isArray(areaArr)) return
+      if (areaArr.indexOf('全国') === -1) {
+        areaArr.push('全国')
+      }
+      var areaMap = JSON.parse(JSON.stringify(provinceListMapExp))
+      var map = {}
+      for (var key in areaMap) {
+        var arr = []
+        areaMap[key].forEach(function (item) {
+          if (areaArr.indexOf(item) !== -1) {
+            arr.push(item)
+          }
+        })
+        if (arr.length) {
+          map[key] = arr
+        }
+      }
+      return map
+    },
+    sortData (arr) {
+      return arr.sort((a, b) => {
+        return b.updatetime - a.updatetime
+      })
+    }
+  },
+  computed: {
+    ...mapState({
+      scope: state => state.user.scope,
+      isDeleteAllScope: state => state.user.isDeleteAllScope,
+      subscribe: state => state.user.bigKeywordsData,
+      userInfo: state => state.user.info,
+      vipSubscribeInfo: state => state.user.vipSubscribeInfo,
+      vipScope: state => state.user.vipScope,
+      isDeleteVipScope: state => state.user.isDeleteVipScope
+    }),
+    getScopeKeyList () {
+      let scope
+      if (this.userInfo.memberStatus > 0) {
+        scope = JSON.parse(JSON.stringify(this.scope))
+      } else if (this.userInfo.vipStatus > 0) {
+        scope = JSON.parse(JSON.stringify(this.vipScope))
+      }
+      if (scope && scope.length > 0) {
+        this.sortData(scope)
+        return scope.map(v => {
+          let str = v.key.join(' ')
+          if (v?.appendkey) {
+            str = str + ' ' + v?.appendkey.join(' ')
+          }
+          return str
+        })
+      } else {
+        return []
+      }
+    }
+  },
+  watch: {
+    subscribe: {
+      deep: true,
+      handler (val) {
+        console.log(val, '111')
+        this.getAreaConfig()
+      }
+    },
+    vipSubscribeInfo: {
+      deep: true,
+      handler () {
+        this.getAreaConfig()
+      }
+    }
+  },
+  async mounted () {
+    await this.getAreaConfig()
+    this.changeBusiness([])
+    // this.recoverCreate()
+    this.$nextTick(() => {
+      this.$refs.pushList.doQuery()
+    })
+  },
+  activated () {
+    this.changeBusiness([])
+  },
+  async created () {
+    if (!this.isDeleteAllScope) {
+      await this.$store.dispatch('user/getKeywordsList')
+    }
+    if (this.userInfo.vipStatus > 0 && !this.isDeleteVipScope) {
+      await this.getVipInfo()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .page--potential {
+    padding: 24px;
+    @include diy-icon('top-c', 36, 36);
+    @include diy-icon('top-r', 34, 28);
+    @include diy-icon('top-manage', 18, 18);
+    ::v-deep .bidfor_text{
+      line-height: 36px;
+      margin-left: 8px;
+    }
+    ::v-deep .ex-line-2 {
+      .j-button-item {
+        display: inline-block;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        text-align: justify;
+        max-width: 14em;
+      }
+    }
+    ::v-deep .for_main{
+      margin-top: -148px;
+    }
+    .label-invisible {
+      visibility: hidden;
+    }
+    .button-group {
+      background-color: #fff;
+      padding: 32px 0 40px 0;
+      button {
+        padding: 3px 44px;
+        box-sizing: border-box;
+        border-radius: 4px;
+        font-size: 14px;
+        line-height: 22px;
+        border: 1px solid #2CB7CA;
+        background-color: inherit;
+        text-align: center;
+        & + button {
+          margin-left: 40px;
+        }
+        &.button-default {
+          color: #2CB7CA;
+        }
+        &.button-submit {
+          background-color: #2cb7ca;
+          color: #fff;
+        }
+      }
+    }
+    .border-box {
+      border: 1px solid #ececec;
+      border-radius: 8px;
+      overflow: hidden;
+      ::v-deep .selector-card .selector-card-content .j-button-item.button-level-0.active {
+        color: #fff;
+        background-color: #2cb7ca;
+      }
+    }
+    .content-list-box {
+      margin-top: 16px;
+    }
+    .top-title-box {
+      padding: 0 40px;
+      // padding-bottom: 12px;
+      box-sizing: border-box;
+      background-color: #fff;
+      border-bottom: 1px solid #ECECEC;
+      .top-title-text {
+        padding: 12px 0;
+        font-size: 16px;
+        line-height: 28px;
+        color: #2CB7CA;
+        border-bottom: 2px solid #2CB7CA;
+      }
+      .manage-box {
+        font-size: 13px;
+        line-height: 20px;
+        text-align: center;
+        color: #1D1D1D;
+        cursor: pointer;
+        span {
+          margin-left: 4px;
+          text-decoration-line: underline;
+        }
+      }
+    }
+    .distributorInput {
+      width: 590px;
+    }
+    .distributorInputDiv {
+      padding: 12px 25px;
+      background-color: #fff;
+      display: flex;
+      position: relative;
+      &::after{
+        position: absolute;
+        content: '';
+        bottom: 0;
+        width: calc(100% - 32px);
+        left: 16px;
+        right: 16px;
+        border-bottom: 1px dashed #E0E0E0;;
+      }
+      p {
+        font-size: 14px;
+        font-family: Microsoft YaHei-Regular, Microsoft YaHei;
+        font-weight: 400;
+        line-height: 40px;
+        color: #686868;
+        min-width: 95px;
+      }
+    }
+    .brandInput {
+      width: 590px;
+    }
+    .brandInputDiv {
+      padding: 12px 40px;
+      background-color: #fff;
+      display: flex;
+      position: relative;
+      &::after{
+        position: absolute;
+        content: '';
+        bottom: 0;
+        width: calc(100% - 32px);
+        left: 16px;
+        right: 16px;
+        border-bottom: 1px dashed #E0E0E0;;
+      }
+      p {
+        font-size: 14px;
+        font-family: Microsoft YaHei-Regular, Microsoft YaHei;
+        font-weight: 400;
+        line-height: 40px;
+        min-width: 81px;
+        color: #686868;
+      }
+    }
+    .productInput {
+      width: 590px;
+    }
+    .productInputDiv {
+      padding: 12px 40px;
+      background-color: #fff;
+      display: flex;
+      position: relative;
+      &::after{
+        position: absolute;
+        content: '';
+        bottom: 0;
+        width: calc(100% - 32px);
+        left: 16px;
+        right: 16px;
+        border-bottom: 1px dashed #E0E0E0;;
+      }
+      p {
+        font-size: 14px;
+        font-family: Microsoft YaHei-Regular, Microsoft YaHei;
+        font-weight: 400;
+        line-height: 40px;
+        min-width: 81px;
+        color: #686868;
+      }
+      span {
+        font-weight: 400;
+        color: #2ABED1;
+        line-height: 40px;
+        margin-left: 10px;
+        text-decoration-line: underline;
+        cursor: pointer;
+      }
+    }
+    .delete_product {
+      font-size: 22px;
+      color: #AAAAAA;
+      padding: 0px 10px;
+      line-height: 40px;
+      cursor: pointer;
+    }
+    .drawer-class {
+      .el-main {
+        padding: 0;
+      }
+    }
+
+    .fontclass {
+      font-family: Microsoft YaHei-Regular, Microsoft YaHei;
+      font-weight: 400;
+      color: #686868;
+      font-size: 14px;
+      width: 98px;
+      text-align: right;
+    }
+    .selector-card.s-line {
+      padding: 12px 13px;
+      position: relative;
+      &::after{
+        position: absolute;
+        content: '';
+        bottom: 0;
+        width: calc(100% - 32px);
+        left: 16px;
+        right: 16px;
+        border-bottom: 1px dashed #E0E0E0;;
+      }
+    }
+    .selector-card:not(:last-of-type){
+      border-bottom: 0;
+    }
+  }
+</style>

+ 471 - 0
src/views/medical/FindMedical.vue

@@ -0,0 +1,471 @@
+<template>
+  <div class="find-medical">
+    <div class="filter-container">
+      <CommonTab title='筛选条件'>
+        <div slot="right" class="tab-slot-right" @click="onTabRight">
+          <i class="iconfont icon-hui6"></i>
+          <span style="text-decoration: underline;">业务范围设置</span>
+        </div>
+      </CommonTab>
+      <div class="input-Selector">
+        <div class="filter-label filter-input-label">医疗机构名称:</div>
+        <BaseInput placeholder="请输入医疗机构名称" v-model="filters.name"></BaseInput>
+      </div>
+      <AreaSelector comtype="portrait" @onChange="changeArea" ref="areaSelector" selectorType="line">
+        <div slot="header" class="filter-label">选择区域:</div>
+      </AreaSelector>
+      <MedicalSelector @onChange="changeHospitalType" ref="typeSelector" selectorType="line" :initCate="hospitalTypeData" :is-weight="false">
+        <div slot="header" class="filter-label">机构类型:</div>
+      </MedicalSelector>
+      <MedicalSelector  @onChange="changeHospitalLevel" ref="levelSelector" selectorType="line" :initCate="hospitalLevelData">
+        <div slot="header" class="filter-label">医院等级:</div>
+      </MedicalSelector>
+      <MedicalSelector  @onChange="changeHospitalNature" ref="natureSelector" selectorType="line" :initCate="hospitalNatureData" :is-weight="false">
+        <div slot="header" class="filter-label">经营性质:</div>
+      </MedicalSelector>
+      <BusinessScopeSelector class="ex-line-2" @onChange="changeBusiness" ref="businessScopeSelector" :initList="getScopeKeyList"  selectorType="line">
+        <div slot="header" class="filter-label">业务范围:</div>
+      </BusinessScopeSelector>
+      <div class="flex-r-c center button-group">
+        <button class="button-default" @click="resetFilter">重置</button>
+        <button class="button-submit" @click="changeSubmitInfo(true)">确定</button>
+      </div>
+    </div>
+    <div class="medical-list-container">
+      <MedicalList :filters="filters" ref="pushList" @goDetail="goDetail"></MedicalList>
+    </div>
+    <DrawerCard customClass="drawer-class" percent="600px" :with-header="false" v-model="showDrawer" @close="onCloseDrawer" @saveData="onSaveDrawer">
+      <ScopeContent ref="scopeRef"></ScopeContent>
+    </DrawerCard>
+  </div>
+</template>
+
+<script>
+import '@/assets/style/iconfont.css'
+import { Drawer } from 'element-ui'
+import BaseInput from '@/components/input/BaseInput'
+import CommonTab from '@/components/medical/CommonTab.vue'
+import AreaSelector from '@/components/selector/AreaSelector.vue'
+import BusinessScopeSelector from '@/components/selector/BusinessScopeSelector.vue'
+import MedicalSelector from '@/components/selector/MedicalSelector'
+import MedicalList from '@/components/push-list/MedicalList.vue'
+import ScopeContent from '@/components/scope/index.vue'
+import DrawerCard from '@/components/drawer/Drawer'
+import { mapState, mapMutations, mapActions } from 'vuex'
+import { getMedicalFilter } from '@/api/modules/'
+import { transData, openSelfLink } from '@/utils/'
+import { hospitalNatureExp, provinceListMapExp } from '@/assets/js/selector.js'
+// import { formatVipKeywordsList } from '@/utils/format'
+export default {
+  name: 'findMedical',
+  components: {
+    [Drawer.name]: Drawer,
+    CommonTab,
+    BaseInput,
+    AreaSelector,
+    BusinessScopeSelector,
+    MedicalSelector,
+    MedicalList,
+    ScopeContent,
+    DrawerCard
+  },
+  data () {
+    return {
+      hospitalTypeData: this.hospitalType,
+      hospitalLevelData: this.hospitalLevel,
+      hospitalNatureData: hospitalNatureExp,
+      showDrawer: false,
+      showTips: '暂无符合条件的医疗机构',
+      filters: {
+        name: '',
+        scope: [],
+        area: {},
+        type: '',
+        level: '',
+        nature: ''
+      },
+      selectedScope: [],
+      subArea: {}
+    }
+  },
+  computed: {
+    ...mapState({
+      scope: state => state.user.scope,
+      isDeleteAllScope: state => state.user.isDeleteAllScope,
+      hospitalLevel: state => state.medical.hospitalLevel,
+      hospitalType: state => state.medical.hospitalType,
+      subscribe: state => state.user.bigKeywordsData,
+      userInfo: state => state.user.info,
+      vipSubscribeInfo: state => state.user.vipSubscribeInfo,
+      vipScope: state => state.user.vipScope,
+      isDeleteVipScope: state => state.user.isDeleteVipScope
+    }),
+    getScopeKeyList () {
+      // console.log(this.scope, '大会员业关键词')
+      // console.log(formatVipKeywordsList(this.vipSubscribeInfo.items), '超级订阅关键词')
+      let scope
+      if (this.userInfo.memberStatus > 0) {
+        scope = JSON.parse(JSON.stringify(this.scope))
+      } else if (this.userInfo.vipStatus > 0) {
+        scope = JSON.parse(JSON.stringify(this.vipScope))
+      }
+      if (scope && scope.length > 0) {
+        this.sortData(scope)
+        return scope.map(v => {
+          let str = v.key.join(' ')
+          if (v?.appendkey) {
+            str = str + ' ' + v?.appendkey.join(' ')
+          }
+          return str
+        })
+      } else {
+        return []
+      }
+    }
+  },
+  watch: {
+    subscribe: {
+      deep: true,
+      handler (val) {
+        // console.log(val)
+        this.getAreaConfig()
+      }
+    },
+    vipSubscribeInfo: {
+      deep: true,
+      handler (val) {
+        this.getAreaConfig()
+      }
+    }
+  },
+  async created () {
+    if (this.userInfo.vipStatus > 0 && !this.isDeleteVipScope) {
+      await this.getVipInfo()
+    }
+    if (!this.isDeleteAllScope) {
+      await this.$store.dispatch('user/getKeywordsList')
+    }
+    this.hospitalTypeData = this.hospitalType
+    this.hospitalLevelData = this.hospitalLevel
+    this.getMedicalFilterItem()
+  },
+  async mounted () {
+    await this.getAreaConfig()
+    this.$nextTick(() => {
+      this.$refs.pushList.doQuery()
+    })
+  },
+  methods: {
+    ...mapMutations({
+      setHospitalLevel: 'medical/setHospitalLevel',
+      setHospitalType: 'medical/setHospitalType'
+    }),
+    ...mapActions({
+      getVipInfo: 'user/getSvipUserSubscribeList'
+    }),
+    sortData (arr) {
+      return arr.sort((a, b) => {
+        return b.updatetime - a.updatetime
+      })
+    },
+    getAreaConfig () {
+      let areaData = {}
+      if (this.userInfo.memberStatus > 0) {
+        // 大会员
+        try {
+          areaData = this.subscribe.member_jy.o_area || {}
+          this.subArea = this.subscribe.member_jy.o_area || {}
+          this.formatAreaData(areaData)
+        } catch (error) {}
+      } else if (this.userInfo.vipStatus > 0) {
+        // 超级订阅
+        try {
+          areaData = this.vipSubscribeInfo.area || {}
+          this.subArea = this.vipSubscribeInfo.area || {}
+          this.formatAreaData(areaData)
+        } catch (error) {}
+      }
+    },
+    formatAreaData (areaData) {
+      const areaArr = []
+      for (const key in areaData) {
+        areaArr.push(key)
+      }
+      if (areaArr.length > 0) {
+        const initData = this.getInitArea(areaArr)
+        this.$refs.areaSelector.initList(initData)
+      } else {
+        this.$refs.areaSelector.initList()
+      }
+      this.filters.area = this.subArea
+    },
+    getInitArea (areaArr) {
+      if (!Array.isArray(areaArr)) return
+      if (areaArr.indexOf('全国') === -1) {
+        areaArr.push('全国')
+      }
+      var areaMap = JSON.parse(JSON.stringify(provinceListMapExp))
+      var map = {}
+      for (var key in areaMap) {
+        var arr = []
+        areaMap[key].forEach(function (item) {
+          if (areaArr.indexOf(item) !== -1) {
+            arr.push(item)
+          }
+        })
+        if (arr.length) {
+          map[key] = arr
+        }
+      }
+      return map
+    },
+    changeSubmitInfo () {
+      const getSelected = this.$refs.businessScopeSelector.getState()
+      this.changeBusiness(getSelected)
+      this.$refs.pushList.doQuery()
+    },
+    resetFilter () {
+      this.filters.name = ''
+      this.filters.area = this.subArea
+      this.filters.scope = []
+      this.filters.type = ''
+      this.filters.level = ''
+      this.filters.nature = ''
+      try {
+        this.$refs.areaSelector.setCitySelected()
+        this.$refs.typeSelector.setCateState()
+        this.$refs.levelSelector.setCateState()
+        this.$refs.natureSelector.setCateState()
+        this.$refs.businessScopeSelector.setState()
+        this.$refs.pushList.doQuery()
+      } catch (error) {}
+    },
+    // 获取医疗机构筛选条件
+    async getMedicalFilterItem () {
+      if (this.hospitalType.length > 0 && this.hospitalLevel.length > 0) return
+      const { data } = await getMedicalFilter()
+      if (data) {
+        if (data.level_code) {
+          data.level_code.unshift({
+            name: '全部',
+            code: '0',
+            level: '0'
+          })
+          const levelData = transData(data.level_code, 'code', 'pcode', 'children')
+          levelData.forEach(v => {
+            if (!v.children) {
+              v.children = []
+            }
+          })
+          this.hospitalLevelData = levelData
+          this.setHospitalLevel(levelData)
+        }
+        if (data.mi_type_code) {
+          data.mi_type_code.unshift({
+            name: '全部',
+            code: '0',
+            level: '0'
+          })
+          const typeData = transData(data.mi_type_code, 'code', 'pcode', 'children')
+          typeData.forEach(v => {
+            if (!v.children) {
+              v.children = []
+            }
+          })
+          this.hospitalTypeData = typeData
+          this.setHospitalType(typeData)
+        }
+      }
+    },
+    onTabRight () {
+      this.showDrawer = true
+      this.selectedScope = this.$refs.businessScopeSelector.getState()
+    },
+    changeArea (area) {
+      if (area && Object.keys(area).length > 0) {
+        this.filters.area = area
+      } else {
+        this.filters.area = this.subArea
+      }
+    },
+    changeBusiness (item) {
+      let scope = this.scope
+      if (this.userInfo.memberStatus > 0) {
+        scope = this.scope
+      } else if (this.userInfo.vipStatus > 0) {
+        scope = this.vipScope
+      }
+      item = item.map(v => v.split(' ')[0])
+      const tempArr = []
+      scope.forEach(v => {
+        if (item.includes(v.key.join(' '))) {
+          tempArr.push(v)
+        }
+      })
+      // if (item.length === 0) {
+      //   tempArr = scope
+      // }
+      this.filters.scope = tempArr
+    },
+    changeHospitalType (data) {
+      this.filters.type = data.toString() || ''
+    },
+    changeHospitalLevel (data) {
+      this.filters.level = data.toString() || ''
+    },
+    changeHospitalNature (data) {
+      this.filters.nature = data.toString() || ''
+    },
+    onCloseDrawer (data) {
+      this.showDrawer = data
+    },
+    onSaveDrawer () {
+      this.$nextTick(() => {
+        this.$refs.scopeRef.saveSetting()
+        this.showDrawer = false
+      })
+      setTimeout(() => {
+        const list = this.getScopeKeyList
+        const s = this.selectedScope
+        const arr = []
+        if (s && s.length > 0) {
+          s.forEach(v => {
+            arr.push(list.includes(v))
+          })
+        }
+        const flag = arr.some(v => v)
+        this.$refs.businessScopeSelector.setState(flag ? s : [])
+      }, 30)
+    },
+    goDetail (item) {
+      const routeUrl = this.$router.resolve({
+        path: `/medical/medical_portrait/${item.company_id}`
+      })
+      openSelfLink(routeUrl)
+      // window.open(routeUrl.href, '_blank')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.find-medical{
+  padding: 24px;
+  .filter-container{
+    background: #fff;
+    border-radius: 8px;
+    overflow: hidden;
+    .tab-slot-right{
+      display: flex;
+      align-items: center;
+      padding-right: 16px;
+      cursor: pointer;
+      font-size: 13px;
+      .icon-hui6{
+        margin-right: 4px;
+        font-size: 18px;
+      }
+    }
+    .input-Selector{
+      position: relative;
+      display: flex;
+      align-items: center;
+      padding: 18px 16px;
+      &::after{
+        position: absolute;
+        content: '';
+        bottom: 0;
+        width: calc(100% - 32px);
+        left: 16px;
+        right: 16px;
+        border-bottom: 1px dashed #E0E0E0;;
+      }
+    }
+    .selector-card.s-line{
+      position: relative;
+      padding: 12px 16px;
+      &::after{
+        position: absolute;
+        content: '';
+        bottom: 0;
+        width: calc(100% - 32px);
+        left: 16px;
+        right: 16px;
+        border-bottom: 1px dashed #E0E0E0;;
+      }
+    }
+    .selector-card:not(:last-of-type){
+      border-bottom: 0;
+    }
+    .filter-label{
+      width: 98px;
+      text-align: right;
+      font-size: 14px;
+      color: #686868;
+      &.filter-input-label{
+        margin-right: 8px;
+      }
+    }
+    .button-group {
+      background-color: #fff;
+      padding: 32px 0 40px 0;
+      button {
+        padding: 3px 44px;
+        box-sizing: border-box;
+        border-radius: 4px;
+        font-size: 14px;
+        line-height: 22px;
+        border: 1px solid #2CB7CA;
+        background-color: inherit;
+        text-align: center;
+        & + button {
+          margin-left: 40px;
+        }
+        &.button-default {
+          color: #2CB7CA;
+        }
+        &.button-submit {
+          background-color: #2cb7ca;
+          color: #fff;
+        }
+      }
+    }
+  }
+  .medical-list-container{
+    margin: 16px 0;
+    background: #fff;
+    border-radius: 8px;
+  }
+  ::v-deep{
+    .selector-card.s-line .selector-card-header{
+      margin-right: 8px;
+      min-width: 98px;
+    }
+    .filter-input{
+      .item-label{
+        margin-right: 8px;
+      }
+    }
+    .select-group-container{
+      margin: 0;
+    }
+    .ex-line-2{
+      .j-button-item{
+        display: inline-block;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        text-align: justify;
+        max-width: 14em;
+      }
+    }
+  }
+}
+</style>
+<style lang="scss">
+.drawer-class{
+  .el-main{
+    padding: 0;
+  }
+}
+</style>

+ 166 - 0
src/views/medical/distributorFollowed.vue

@@ -0,0 +1,166 @@
+<template>
+  <div class="ent-intel">
+    <forLayOut :search="false">
+      <template v-slot:main>
+        <CommonTab title='筛选条件' v-if="false"></CommonTab>
+        <FollowFilter @onPageChange="updateFilters" searchName="经销商名称" searchPlaceholder="请输入经销商名称"
+                      @flushResource="getResourceSurplus" :showGroup=true v-if="false"></FollowFilter>
+        <CommonTab title='我认领的经销商'>
+          可认领{{ resource.total }}个客户,已认领<p style="color: #2CB7CA;padding: 0 5px">
+          {{ resource.total - resource.surplus }}</p>个
+        </CommonTab>
+        <FollowList class="followList"
+                    :loading="loading" ref="myList" slot="main"
+                    @onPageChange="getMyFollowList" @flushResource="getResourceSurplus"
+        ></FollowList>
+      </template>
+    </forLayOut>
+  </div>
+</template>
+<script>
+import forLayOut from '@/components/forecast/ForLayout.vue'
+import CommonTab from '@/components/medical/CommonTab.vue'
+import FollowList from '@/components/medical/FollowList.vue'
+import FollowFilter from '@/components/medical/FollowFilter.vue'
+import { followDistributorList, GetResourceSurplus } from '@/api/modules/medical'
+
+// 关注的医疗机构页面
+export default {
+  name: 'distributor-followed',
+  components: {
+    CommonTab,
+    forLayOut,
+    FollowFilter,
+    FollowList
+  },
+  data () {
+    return {
+      filter: {
+        pageNum: 1,
+        match: '',
+        group: '',
+        pageSize: 10
+      },
+      resource: {
+        total: 0,
+        surplus: 0
+      },
+      myDataObj: {
+        maxCount: 0,
+        list: [],
+        total: 0, // 列表总数 用于分页
+        initTotal: 0 // 已关注总数
+      },
+      loading: true
+    }
+  },
+  computed: {},
+  created () {
+    this.getMyFollowList()
+    this.getResourceSurplus()
+  },
+  methods: {
+    updateFilters (p = 0, match, group) {
+      this.filter.pageNum = p
+      this.filter.match = match
+      this.filter.group = group
+      this.getMyFollowList()
+    },
+    // 查询经销商关注资源详情
+    getResourceSurplus () {
+      GetResourceSurplus({
+        functionCode: 'lyh_yl_jxsrl'
+      }).then(res => {
+        if (res.data) { // res.error_code === 0 &&
+          this.resource.surplus = res.data.surplus || 0
+          this.resource.total = res.data.total || 0
+        }
+      })
+    },
+    // 查询我关注的经销商
+    getMyFollowList (num) {
+      if (num) {
+        this.filter.pageNum = num
+      }
+      this.loading = true
+      if (this.$refs.myList) {
+        this.myDataObj.list = []
+      }
+      followDistributorList({
+        page: this.filter.pageNum,
+        page_size: this.filter.pageSize
+        //   match: this.filter.match,
+        //   group: this.filter.group
+      }).then(res => {
+        if (res.error_code === 0 && res.data) {
+          if (res.data.list) {
+            res.data.list.forEach(v => {
+              if (v.s_city && v.s_city.length > 5) {
+                v.s_city = v.s_city.substring(0, 4) + '...'
+              }
+              if (v.create_time) {
+                v.create_time = v.create_time.split(' ')[0]
+              }
+              if (v.establish_date) {
+                v.establish_date = v.establish_date.split(' ')[0]
+              }
+            })
+          }
+          this.myDataObj.total = res.data.total
+          this.myDataObj.list = res.data.list || []
+          this.$refs.myList.entInitData(this.myDataObj, this.filter)
+        }
+        this.loading = false
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.in-app {
+  .ent-intel {
+    @extend .sub-page-container;
+    background-color: transparent;
+    min-width: 1180px;
+  }
+}
+
+.ent-intel {
+  width: 100%;
+
+  ::v-deep .for_header {
+    display: none;
+  }
+
+  ::v-deep .for_main {
+    margin-top: unset;
+    width: 100%;
+  }
+
+  ::v-deep .sub-dialog {
+    .el-dialog__header,
+    .el-dialog__body {
+      padding: 0;
+    }
+  }
+
+  .icon_ent {
+    display: flex;
+    align-items: center;
+    margin-right: 6px;
+    width: 36px;
+    height: 36px;
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAADfUlEQVR4Xu2bz4uOURTHv99Qo/wBdixmQSxMsVA2yoIoKWWjzGaiLCh2RkbGQlEslAmh7FAWCsXOQlFmoRCK3SztjEwdnbpTj+n58d57z3tf4z13NVP33vN9P/d77/Pe5z2H8NZKgM6nnYAD6nCIA3JAeYeIO8gd5A7KI+AOyuPnZ5A7yB2URyDXQSIyAuA4gMMANgDQ/1PaDwAfATwBcJvkXMwkIrIWwBEA+wBsAbAmZnyl7wKAWQD3AcyQnG+bp/UMEpF1AJ4FMIl6aoepqGkAl0iq4MYmIisBnAj9UxenaX5dsN0kvzd1aAQUnPOuD3CqWhT+gaZVDBoeBNdYLlB1LoU01qShDZCu2tV+qarMe4vkRF0cEbkDYLyAhtMkr9TFaQP0BsDWAuI0hNr8eTWWiOwH8LhQ/FmSY7GAfgPQ/V+ivSa5fQmgkgs0T3J1LCApQaYSY5TkV/1fREYBfC4Zn2TtbmrbYqUBjZO8FwDp4/yuA/qbwCTJiwHQmfBYL8ZoOThoiuT5AOgcgKlidAA4oA7aDqggoFcAJkjqN9CemojoHe4mgB0tA2K2mLkGSwdtjIGzCCRA+mAEyFyDJaARkr96sk6lU7hX/TQCZK7BEtAxkjMJgI4CuGEEyFyDJSB9PfEwvNvplZOeQQc7ri4xZ5C5BktAvUKJ7RcDKHbuzv4OqOBjvnM1Eju4gzrAOSAHlLi3wjB30LA4yPwelOAgcw2Wj3nze1ACIHMNloDM70EJgMw1WAIyvwclADLXYAnI/B4UAE2TPKt/i8gFAJMth7q5BktAeQ/z5tHfAOwEsCLkA+hPP8XacgBUDEZdIAf0H1xW3UEDJeAOysPvZ5A7yB2UR8AdlMcv5QzSH/mss0rzPkX/Ri+QXFX7BbIppohohqvmIw9De0tyWyygUwAuDwMdACdJXosFpNur33nS/wL/tDzp8NphPYCnfU4mHyQkhbOHpL5JqG2d5VAhK0MTD7RWQ8+kUqnB/QKnZRAKRms1rmfVauQqDHUWWoSyKSRPLRai5EythShawvACwCcAc131HjnBOh2UM3nd2JADfSiUGPT6UkxXXNOCH5H8Yq2pbb7igKpiQtbZXgC7AGwGoG7TpqVS74NLXpJU1wykDRTQQD5xZFAH1AHMATmgyD21pLs7yB3kDsoj4A7K4+dnkDvIHZRHwB2Ux+8P+R7bWOxBzR4AAAAASUVORK5CYII=) no-repeat;
+    background-size: contain;
+  }
+
+  .followList {
+    border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+    display: flex;
+    flex-direction: column;
+    padding: 16px
+  }
+}
+</style>

+ 362 - 0
src/views/medical/institutionFollowed.vue

@@ -0,0 +1,362 @@
+<template>
+  <div class="ent-intel">
+    <forLayOut :search="false">
+      <template v-slot:main>
+        <CommonTab title='筛选条件' v-if="false"></CommonTab>
+        <ForFilter @onPageChange="updateFilters" searchName="医疗机构名称" searchPlaceholder="请输入已认领的医疗机构名称"
+                   v-if="false"></ForFilter>
+        <CommonTab title='我认领的医疗机构'>
+          可认领{{ resource.total }}个客户,已认领<p style="color: #2CB7CA;padding: 0 5px">
+          {{ resource.total - resource.surplus }}</p>个
+        </CommonTab>
+        <div class="client-list client-container">
+          <div class="l-thead">
+            <span class="w-800">医疗机构名称</span>
+            <span class="w-100">认领时间</span>
+            <span class="w-100">操作</span>
+          </div>
+          <div class="l-tbody" v-loading="loading" element-loading-background="#fff"
+               v-if="client.list && client.list.length > 0">
+            <ul class="items">
+              <li class="item" :class="{ visited: item.visited }" v-for="(item, index) in client.list"
+                  :key="'00' + index">
+                <div class="item-info w-800">
+                  <div class="item-line">
+                    <i class="company-icon"></i>
+                    <div class="info-name visited-hd" @click="goUnit(item)">{{ item.ent_name }}</div>
+                  </div>
+                  <div class="info-other">
+                      <span class="other-list" v-if="item.address">
+                        <em class="text-label">所在地:</em>
+                        <span class="visited-ft">{{ item.address }}</span>
+                      </span>
+                  </div>
+                </div>
+                <div class="item-time w-100">{{ item.create_time }}</div>
+                <div class="item-handle w-100" @click="cancelFollow(item)">取消认领</div>
+              </li>
+            </ul>
+            <div class="el-pagination-container">
+              <el-pagination
+                background
+                layout="prev, pager, next, ->"
+                :hide-on-single-page="true"
+                :current-page="filter.pageNum"
+                :page-size="filter.pageSize"
+                :total="client.total"
+                @current-change="onPageChange"
+              >
+              </el-pagination>
+            </div>
+          </div>
+          <Empty v-show="showEmpty" :images="require('@/assets/images/empty/jy-back.png')">
+            <div name="default">{{ showTips }}</div>
+          </Empty>
+        </div>
+      </template>
+    </forLayOut>
+  </div>
+</template>
+
+<script>
+import { Button, Pagination } from 'element-ui'
+import forLayOut from '@/components/forecast/ForLayout.vue'
+import ForFilter from '@/components/medical/FollowFilter.vue'
+import CommonTab from '@/components/medical/CommonTab.vue'
+import Empty from '@/components/common/Empty.vue'
+import { mixinVisited } from '@/utils/mixins/visited'
+import { followInstitutionList, GetResourceSurplus } from '@/api/modules/medical'
+import { institutionUnClaimed } from '@/api/modules/medicalField'
+import { openSelfLink } from '@/utils'
+// 关注的经销商页面
+export default {
+  name: 'distributor-followed',
+  mixins: [mixinVisited],
+  components: {
+    [Button.name]: Button,
+    [Pagination.name]: Pagination,
+    forLayOut,
+    ForFilter,
+    CommonTab,
+    Empty
+  },
+  data () {
+    return {
+      filter: {
+        pageNum: 1,
+        match: '',
+        group: '',
+        pageSize: 10
+      },
+      resource: {
+        total: 0,
+        surplus: 0
+      },
+      client: {
+        total: 0,
+        list: []
+      },
+      loading: true
+    }
+  },
+  computed: {
+    showTips () {
+      if (this.client.total === 0) {
+        return '暂未认领任何医疗机构'
+      } else {
+        return '暂未匹配数据'
+      }
+    },
+    showEmpty () {
+      return this.client.list.length === 0 && !this.loading
+    }
+  },
+  created () {
+  },
+  mounted () {
+    this.getClientList()
+    this.getResourceSurplus()
+  },
+  methods: {
+    updateFilters (p = 0, match, group) {
+      this.filter.pageNum = p
+      this.filter.match = match
+      this.filter.group = group
+      this.getClientList()
+      this.getResourceSurplus()
+    },
+    // 查询医疗机构关注资源详情
+    getResourceSurplus () {
+      GetResourceSurplus({ functionCode: 'lyh_yl_yljgrl' }).then(res => {
+        if (res.data) { // res.error_code === 0 &&
+          this.resource.surplus = res.data.surplus || 0
+          this.resource.total = res.data.total || 0
+        }
+      })
+    },
+    getClientList () {
+      this.loading = true
+      followInstitutionList({
+        // keyword: this.filter.match,
+        page: this.filter.pageNum,
+        page_size: this.filter.pageSize
+      }).then(res => {
+        if (res.error_code === 0 && res.data) {
+          if (res.data.list) {
+            res.data.list.forEach(v => {
+              v.create_time = v.create_time ? v.create_time.split(' ')[0] : ''
+            })
+          }
+          this.client.list = res.data.list || []
+          this.client.total = res.data.total
+        }
+        this.loading = false
+      })
+    },
+    goUnit (item) {
+      const routeUrl = this.$router.resolve({
+        path: `/medical/medical_portrait/${item.ent_id}`
+      })
+      openSelfLink(routeUrl)
+    },
+    onPageChange (p) {
+      this.filter.pageNum = p
+      this.getClientList()
+    },
+    // 取消关注
+    cancelFollow (item) {
+      institutionUnClaimed({
+        ent_id: item.ent_id
+      }).then(res => {
+        if (res.error_code === 0) {
+          this.$toast('取消认领成功', 1000)
+
+          // 取消认领当前页面仅一条数据后 页面减1
+          if (this.client.list.length === 1 && this.filter.pageNum > 0) {
+            this.filter.pageNum--
+          }
+
+          this.getClientList()
+          this.getResourceSurplus()
+        } else {
+          this.$toast(res.error_msg, 1500)
+        }
+      })
+      this.getResourceSurplus()
+    }
+    // goFollowClient () {
+    //   this.$router.replace('/potential_cor_list/c')
+    // }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.in-app {
+  .ent-intel {
+    @extend .sub-page-container;
+    background-color: transparent;
+    min-width: 1180px;
+  }
+}
+
+.ent-intel {
+  width: 100%;
+
+  ::v-deep .ent-filter {
+    border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+  }
+
+  ::v-deep .for_header {
+    display: none;
+  }
+
+  ::v-deep .for_main {
+    margin-top: unset;
+    width: 100%;
+  }
+
+  ::v-deep .sub-dialog {
+    .el-dialog__header,
+    .el-dialog__body {
+      padding: 0;
+    }
+  }
+
+  .icon_ent {
+    display: flex;
+    align-items: center;
+    margin-right: 6px;
+    width: 36px;
+    height: 36px;
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAADfUlEQVR4Xu2bz4uOURTHv99Qo/wBdixmQSxMsVA2yoIoKWWjzGaiLCh2RkbGQlEslAmh7FAWCsXOQlFmoRCK3SztjEwdnbpTj+n58d57z3tf4z13NVP33vN9P/d77/Pe5z2H8NZKgM6nnYAD6nCIA3JAeYeIO8gd5A7KI+AOyuPnZ5A7yB2URyDXQSIyAuA4gMMANgDQ/1PaDwAfATwBcJvkXMwkIrIWwBEA+wBsAbAmZnyl7wKAWQD3AcyQnG+bp/UMEpF1AJ4FMIl6aoepqGkAl0iq4MYmIisBnAj9UxenaX5dsN0kvzd1aAQUnPOuD3CqWhT+gaZVDBoeBNdYLlB1LoU01qShDZCu2tV+qarMe4vkRF0cEbkDYLyAhtMkr9TFaQP0BsDWAuI0hNr8eTWWiOwH8LhQ/FmSY7GAfgPQ/V+ivSa5fQmgkgs0T3J1LCApQaYSY5TkV/1fREYBfC4Zn2TtbmrbYqUBjZO8FwDp4/yuA/qbwCTJiwHQmfBYL8ZoOThoiuT5AOgcgKlidAA4oA7aDqggoFcAJkjqN9CemojoHe4mgB0tA2K2mLkGSwdtjIGzCCRA+mAEyFyDJaARkr96sk6lU7hX/TQCZK7BEtAxkjMJgI4CuGEEyFyDJSB9PfEwvNvplZOeQQc7ri4xZ5C5BktAvUKJ7RcDKHbuzv4OqOBjvnM1Eju4gzrAOSAHlLi3wjB30LA4yPwelOAgcw2Wj3nze1ACIHMNloDM70EJgMw1WAIyvwclADLXYAnI/B4UAE2TPKt/i8gFAJMth7q5BktAeQ/z5tHfAOwEsCLkA+hPP8XacgBUDEZdIAf0H1xW3UEDJeAOysPvZ5A7yB2UR8AdlMcv5QzSH/mss0rzPkX/Ri+QXFX7BbIppohohqvmIw9De0tyWyygUwAuDwMdACdJXosFpNur33nS/wL/tDzp8NphPYCnfU4mHyQkhbOHpL5JqG2d5VAhK0MTD7RWQ8+kUqnB/QKnZRAKRms1rmfVauQqDHUWWoSyKSRPLRai5EythShawvACwCcAc131HjnBOh2UM3nd2JADfSiUGPT6UkxXXNOCH5H8Yq2pbb7igKpiQtbZXgC7AGwGoG7TpqVS74NLXpJU1wykDRTQQD5xZFAH1AHMATmgyD21pLs7yB3kDsoj4A7K4+dnkDvIHZRHwB2Ux+8P+R7bWOxBzR4AAAAASUVORK5CYII=) no-repeat;
+    background-size: contain;
+  }
+
+  .client-container {
+    border-bottom-right-radius: 4px;
+    border-bottom-left-radius: 4px;
+    background: #fff;
+    padding: 16px;
+
+    .l-thead {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      height: 48px;
+      line-height: 48px;
+      padding: 0 20px;
+      background: #f7f9fc;
+
+      span {
+        text-align: center;
+        color: #1D1D1D;
+        font-size: 14px;
+      }
+    }
+
+    .items {
+      display: flex;
+      flex-direction: column;
+
+      .item {
+        display: flex;
+        align-items: center;
+        padding: 20px 20px;
+        -webkit-box-shadow: 0px -1px 0px 0px rgb(0 0 0 / 5%) inset;
+        box-shadow: 0px -1px 0px 0px rgb(0 0 0 / 5%) inset;
+
+        &:hover {
+          background: #f5f6f7;
+          box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.05) inset;
+
+          .info-name {
+            color: #2CB7CA;
+          }
+
+        }
+      }
+
+      .item-info {
+        padding: 0 12px;
+
+        .item-line {
+          display: flex;
+          flex-direction: row;
+        }
+
+      }
+
+      .info-name {
+        margin-bottom: 8px;
+        font-size: 16px;
+        color: #1d1d1d;
+        line-height: 24px;
+        cursor: pointer;
+      }
+
+      .info-other {
+        display: flex;
+        align-items: center;
+
+        .other-list {
+          display: flex;
+          align-items: center;
+          margin-right: 28px;
+          font-size: 14px;
+          color: #1d1d1d;
+          line-height: 22px;
+
+          .text-label {
+            color: #999999;
+          }
+        }
+      }
+
+      .item-time,
+      .item-handle {
+        font-size: 14px;
+        text-align: center;
+        color: #999999;
+      }
+
+      .item-handle {
+        color: #2CB7CA;
+        cursor: pointer;
+      }
+    }
+
+    .w-800 {
+      flex: 8;
+      display: inline-block;
+    }
+
+    .w-100 {
+      flex: 1;
+      display: inline-block;
+    }
+  }
+
+  .add-btn {
+    margin: 16px 0;
+    background: #2cb7ca;
+    border-radius: 6px;
+    border: 0;
+  }
+
+  .el-pagination-container {
+    margin-top: 40px;
+  }
+
+  .company-icon {
+    display: inline-block;
+    width: 20px;
+    height: 20px;
+    margin-right: 4px;
+    background: url('~@/assets/images/icon/icon-company.png') no-repeat center center;
+    background-size: contain;
+  }
+
+}
+</style>

+ 111 - 3
src/views/portrayal/EntPortrayal.vue

@@ -2,7 +2,15 @@
   <Layout class="ent-portrayal">
     <div class="ent-header">
       <div class="name">{{ entName }}</div>
-      <EntFollowStar :id="eId" />
+      <div class="ent_option">
+        <EntFollowStar :id="eId" />
+        <div style="margin-left:10px;" @click="setClickClaim" v-if="showClaim">
+          <div class="u-follow">
+            <span :class="{ icon_claim_yes: claim.claimed, icon_claim_no: !claim.claimed }"></span>
+            <span class="follow_text">{{ claim.claimed ? '取消认领' : '认领' }}</span>
+          </div>
+        </div>
+      </div>
     </div>
     <div class="ent-content" v-loading="loading">
       <div class="tab-header" :class="{'fixed-nav': navFixed}" id="entTabNav">
@@ -76,7 +84,7 @@ import EntFollowStar from './components/EntFollowStar.vue'
 import { mapState } from 'vuex'
 import { Dialog, Input, TabPane, Tabs } from 'element-ui'
 import { dateFormatter, moneyUnit } from '@/utils'
-import { getEntWinnerSelect } from '@/api/modules'
+import { getEntWinnerSelect, distributorClaim, distributorUnClaimed, isClaimed } from '@/api/modules'
 
 function getImgForVipUpgrade (name, bg = false, suffix = '.png') {
   return require('@/assets/images/vip/' + (bg ? 'bg/mask/' : '') + name + suffix)
@@ -114,6 +122,10 @@ export default {
         start: 0,
         end: 0
       },
+      claim: {
+        claimed: false,
+        loading: false
+      },
       eId: this.$route.params.eId,
       entName: '',
       EntHistoryTip: {
@@ -215,6 +227,10 @@ export default {
     ...mapState({
       info: state => state.user.info
     }),
+    // 是否显示认领按钮
+    showClaim () {
+      return this.$route.query.ismedical === '1'
+    },
     showConf12 () {
       // 企业情报历史记录
       return this.info.power.indexOf(12) !== -1
@@ -243,6 +259,7 @@ export default {
   },
   created () {
     this.getSelect()
+    this.getClaimStatus()
   },
   mounted () {
     this.$nextTick(() => {
@@ -329,6 +346,64 @@ export default {
       this.dateRange.start = dateFormatter(data.timeRange.start * 1000, 'yyyy/MM/dd')
       this.dateRange.end = dateFormatter(data.timeRange.end * 1000, 'yyyy/MM/dd')
     },
+    // 查询是否认领
+    getClaimStatus () {
+      this.claim.loading = true
+      const param = {
+        company_id: this.$route.params.eId,
+        type: 2
+      }
+      isClaimed(param).then(res => {
+        if (res.error_code === 0) {
+          this.claim.claimed = res.data.status
+        }
+        this.claim.loading = false
+      })
+    },
+    // 认领点击事件
+    setClickClaim () {
+      if (this.claim.claimed) {
+        this.setCancelClaim()
+      } else {
+        this.setOptionClaim()
+      }
+    },
+    // 认领
+    setOptionClaim () {
+      // const param = {
+      //   ent_name: this.entName,
+      //   ent_id: this.eId
+      // }
+      distributorClaim({
+        ent_name: this.entName,
+        ent_id: this.eId
+      }).then(res => {
+        if (res.error_code === 0) {
+          this.claim.claimed = true
+          this.$toast('认领成功')
+        } else if (res.error_code === 1013) {
+          this.claim.claimed = false
+          this.$toast('经销商认领已达上限')
+        } else {
+          this.claim.claimed = false
+          this.$toast(res.error_msg)
+        }
+      })
+    },
+    // 取消认领
+    setCancelClaim () {
+      const param = {
+        ent_id: this.eId
+      }
+      distributorUnClaimed(param).then(res => {
+        if (res.error_code === 0) {
+          this.claim.claimed = false
+          this.$toast('取消认领成功')
+        } else {
+          this.$toast(res.error_msg)
+        }
+      })
+    },
     openBigPage (item) {
       try {
         // eslint-disable-next-line no-undef
@@ -353,11 +428,44 @@ export default {
     align-items: center;
     padding: 32px 40px;
     background: #fff;
-
+    .ent_option{
+      display: flex;
+      align-items: center;
+    }
     .name {
       font-size: 24px;
       color: #171826;
     }
+    .follow_text{
+      margin-left: 4px;
+    }
+    .icon_claim_no{
+      display: flex;
+      width: 20px;
+      height: 20px;
+      background: url('~@/assets/images/icon/renling-01.png') no-repeat;
+      background-size: contain;
+    }
+    .icon_claim_yes{
+      display: flex;
+      width: 20px;
+      height: 20px;
+      background: url('~@/assets/images/icon/renling-active.png') no-repeat;
+      background-size: contain;
+    }
+    .u-follow {
+      display: flex;
+      align-items: center;
+      cursor: pointer;
+
+      .follow_text {
+        font-size: 14px;
+        font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+        font-weight: 400;
+        color: #686868;
+        line-height: 22px;
+      }
+    }
   }
 
   .tab-header {

+ 112 - 3
src/views/portrayal/EntSearchPortrayal.vue

@@ -2,7 +2,15 @@
   <Layout class="ent-portrayal">
     <div class="ent-header">
       <div class="name">{{ entName }}</div>
-      <EntFollowStar :id="eId" />
+      <div class="ent_option">
+        <EntFollowStar :id="eId" />
+        <div style="margin-left:10px;" @click="setClickClaim" v-if="showClaim">
+          <div class="u-follow">
+            <span :class="{ icon_claim_yes: claim.claimed, icon_claim_no: !claim.claimed }"></span>
+            <span class="follow_text">{{ claim.claimed ? '取消认领' : '认领' }}</span>
+          </div>
+        </div>
+      </div>
     </div>
     <div class="ent-content" v-loading="loading">
       <div class="tab-header" :class="{'fixed-nav': navFixed}" id="entTabNav">
@@ -101,7 +109,7 @@ import EntFollowStar from './components/EntFollowStar.vue'
 import { mapState } from 'vuex'
 import { TabPane, Tabs } from 'element-ui'
 import { dateFormatter, moneyUnit } from '@/utils'
-import { getEntSearchPower, getSubVipEntChart, getsubVipPortraitSelect, getUsage, getUserPower } from '@/api/modules'
+import { getEntSearchPower, getSubVipEntChart, getsubVipPortraitSelect, getUsage, getUserPower, distributorClaim, distributorUnClaimed, isClaimed } from '@/api/modules'
 
 function getImgForVipUpgrade (name, bg = false, suffix = '.png') {
   return require('@/assets/images/vip/' + (bg ? 'bg/mask/' : '') + name + suffix)
@@ -139,6 +147,10 @@ export default {
         start: 0,
         end: 0
       },
+      claim: {
+        claimed: false,
+        loading: false
+      },
       eId: this.$route.params.eId,
       entName: '',
       pagePowerInfo: {
@@ -250,6 +262,10 @@ export default {
     ...mapState({
       info: state => state.user.info
     }),
+    // 是否显示认领按钮
+    showClaim () {
+      return this.$route.query.ismedical === '1'
+    },
     getVipUpgradeMap () {
       const tempMap = this.vipUpgradeMap
       const info = this.info
@@ -347,6 +363,7 @@ export default {
   created () {
     var _this = this
     this.getPower()
+    this.getClaimStatus()
     this.checkShowExp((res) => {
       this.getUserForPagePower().then(() => {
         this.getSelect()
@@ -514,6 +531,64 @@ export default {
       this.dateRange.start = dateFormatter(data.timeRange.start * 1000, 'yyyy/MM/dd')
       this.dateRange.end = dateFormatter(data.timeRange.end * 1000, 'yyyy/MM/dd')
     },
+    // 查询是否认领
+    getClaimStatus () {
+      this.claim.loading = true
+      const param = {
+        company_id: this.$route.params.eId,
+        type: 2
+      }
+      isClaimed(param).then(res => {
+        if (res.error_code === 0) {
+          this.claim.claimed = res.data.status
+        }
+        this.claim.loading = false
+      })
+    },
+    // 认领点击事件
+    setClickClaim () {
+      if (this.claim.claimed) {
+        this.setCancelClaim()
+      } else {
+        this.setOptionClaim()
+      }
+    },
+    // 认领
+    setOptionClaim () {
+      // const param = {
+      //   ent_name: this.entName,
+      //   ent_id: this.eId
+      // }
+      distributorClaim({
+        ent_name: this.entName,
+        ent_id: this.eId
+      }).then(res => {
+        if (res.error_code === 0) {
+          this.claim.claimed = true
+          this.$toast('认领成功')
+        } else if (res.error_code === 1013) {
+          this.claim.claimed = false
+          this.$toast('经销商认领已达上限')
+        } else {
+          this.claim.claimed = false
+          this.$toast(res.error_msg)
+        }
+      })
+    },
+    // 取消认领
+    setCancelClaim () {
+      const param = {
+        ent_id: this.eId
+      }
+      distributorUnClaimed(param).then(res => {
+        if (res.error_code === 0) {
+          this.claim.claimed = false
+          this.$toast('取消认领成功')
+        } else {
+          this.$toast(res.error_msg)
+        }
+      })
+    },
     openVipPage (item) {
       const flag = item.title.indexOf('大会员') > -1
       try {
@@ -595,11 +670,45 @@ export default {
     align-items: center;
     padding: 32px 40px;
     background: #fff;
-
+    .ent_option{
+      display: flex;
+      align-items: center;
+    }
     .name {
       font-size: 24px;
       color: #171826;
     }
+
+    .follow-text{
+      margin-left: 4px;
+    }
+    .icon_claim_no{
+      display: flex;
+      width: 20px;
+      height: 20px;
+      background: url('~@/assets/images/icon/renling-01.png') no-repeat;
+      background-size: contain;
+    }
+    .icon_claim_yes{
+      display: flex;
+      width: 20px;
+      height: 20px;
+      background: url('~@/assets/images/icon/renling-active.png') no-repeat;
+      background-size: contain;
+    }
+    .u-follow {
+      display: flex;
+      align-items: center;
+      cursor: pointer;
+
+      .follow_text {
+        font-size: 14px;
+        font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+        font-weight: 400;
+        color: #686868;
+        line-height: 22px;
+      }
+    }
   }
 
   .tab-header {

+ 21 - 19
src/views/portrayal/components/AnalysisDetailList.vue

@@ -132,33 +132,35 @@ export default {
           list[index].s_winner = []
         }
         if (item.budget) {
-          let budgetmoney = parseInt(item.budget)
-          budgetmoney = budgetmoney + ''
-          if (budgetmoney.length < 5) {
-            item.budget = (moneyUnit(item.budget).substring(0, (moneyUnit(item.budget) + '').length - 1) / 10000).toFixed(6)
-          } else if (budgetmoney.length > 8) {
-            item.budget = (moneyUnit(item.budget).substring(0, (moneyUnit(item.budget) + '').length - 2) * 10000).toFixed(2)
-          } else {
-            item.budget = moneyUnit(item.budget).substring(0, (moneyUnit(item.budget) + '').length - 2)
-          }
+          const budgetmoney = parseInt(item.budget)
+          // budgetmoney = budgetmoney + ''
+          // if (budgetmoney.length < 5) {
+          //   item.budget = (moneyUnit(item.budget).substring(0, (moneyUnit(item.budget) + '').length - 1) / 10000).toFixed(6)
+          // } else if (budgetmoney.length > 8) {
+          //   item.budget = (moneyUnit(item.budget).substring(0, (moneyUnit(item.budget) + '').length - 2) * 10000).toFixed(2)
+          // } else {
+          //   item.budget = moneyUnit(item.budget).substring(0, (moneyUnit(item.budget) + '').length - 2)
+          // }
+          item.budget = (budgetmoney / 10000).fixed(2)
         } else {
           item.budget = '--'
         }
         if (item.bidamount) {
-          let bidamountmoney = parseInt(item.bidamount)
-          bidamountmoney = bidamountmoney + ''
-          if (bidamountmoney.length < 5) {
-            item.bidamount = (moneyUnit(item.bidamount).substring(0, (moneyUnit(item.bidamount) + '').length - 1) / 10000).toFixed(6)
-          } else if (bidamountmoney.length > 8) {
-            item.bidamount = (moneyUnit(item.bidamount).substring(0, (moneyUnit(item.bidamount) + '').length - 2) * 10000).toFixed(2)
-          } else {
-            item.bidamount = moneyUnit(item.bidamount).substring(0, (moneyUnit(item.bidamount) + '').length - 2)
-          }
+          const bidamountmoney = parseInt(item.bidamount)
+          // bidamountmoney = bidamountmoney + ''
+          // if (bidamountmoney.length < 5) {
+          //   item.bidamount = (moneyUnit(item.bidamount).substring(0, (moneyUnit(item.bidamount) + '').length - 1) / 10000).toFixed(6)
+          // } else if (bidamountmoney.length > 8) {
+          //   item.bidamount = (moneyUnit(item.bidamount).substring(0, (moneyUnit(item.bidamount) + '').length - 2) * 10000).toFixed(2)
+          // } else {
+          //   item.bidamount = moneyUnit(item.bidamount).substring(0, (moneyUnit(item.bidamount) + '').length - 2)
+          // }
+          item.bidamount = (bidamountmoney / 10000).fixed(2)
         } else {
           item.bidamount = '--'
         }
         if (item.project_rate) {
-          item.project_rate = Number((item.project_rate * 100)).toFixed(2) + '%'
+          item.project_rate = Number((item.project_rate * 100)).fixed(2) + '%'
         } else {
           item.project_rate = '--'
         }

File diff suppressed because it is too large
+ 75 - 23
src/views/portrayal/components/DynamicList.vue


+ 6 - 0
src/views/portrayal/components/DynamicListItem.vue

@@ -144,11 +144,17 @@ $border-color: #ECECEC;
     background-size: contain;
     cursor: pointer;
     vertical-align: sub;
+    ::before{
+      content: ''!important;
+    }
   }
 
   .icon-collect.checked {
     background: transparent url(https://cdn-ali.jianyu360.com/images/collected.png) center no-repeat;
     background-size: contain;
+    ::before{
+      content: ''!important;
+    }
   }
 
   .a-i-left {

+ 1 - 1
src/views/portrayal/components/EntForm.vue

@@ -158,7 +158,7 @@ export default {
     .ent_list:nth-child(13) {
       border-right: 1px solid #ececec;
     }
-    .li_long{
+    .ent_list.li_long{
       width: 100%;
     }
   }

+ 3 - 0
src/views/subscribe/components/key/Edit.vue

@@ -376,6 +376,9 @@ export default {
       background-repeat: no-repeat;
       background-position: center center;
       cursor: pointer;
+      &::before{
+        content: ''!important;
+      }
     }
     .icon-edit{
       margin: 0 10px;

+ 3 - 0
src/views/subscribe/components/key/KeyConfig.vue

@@ -194,6 +194,9 @@ export default {
         background-repeat: no-repeat;
         background-position: center center;
         cursor: pointer;
+        &::before{
+          content: ''!important;
+        }
       }
       .icon-edit{
         margin: 0 10px;

+ 3 - 0
src/views/subscribe/components/key/List.vue

@@ -661,6 +661,9 @@ export default {
     background-repeat: no-repeat;
     background-position: center center;
     cursor: pointer;
+    &::before{
+      content: ''!important;
+    }
   }
   .icon-edit{
     margin: 0 10px;

+ 3 - 0
src/views/subscribe/components/scope/Edit.vue

@@ -324,6 +324,9 @@ export default {
       background-repeat: no-repeat;
       background-position: center center;
       cursor: pointer;
+      &::before{
+        content: ''!important;
+      }
     }
     .icon-edit{
       margin: 0 10px;

+ 3 - 0
src/views/subscribe/components/scope/List.vue

@@ -395,6 +395,9 @@ export default {
       background-repeat: no-repeat;
       background-position: center center;
       cursor: pointer;
+      &::before{
+        content: ''!important;
+      }
     }
     .icon-edit{
       margin: 0 10px;

+ 1 - 1
src/views/vipsubscribe/Buy.vue

@@ -728,7 +728,7 @@ export default {
       // eslint-disable-next-line
       const { data, error_msg } = await this.submitXHR()
       const duration = 2000
-      if (this.activity && Object.keys(this.activity).length > 0) {
+      if (this.isActivity && this.activity && Object.keys(this.activity).length > 0) {
         const { stockNumber, isReceive } = this.activity
         if (stockNumber === 0) {
           this.autoPass = false

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

@@ -1,12 +1,12 @@
 <template>
   <WorkspaceCard class="work-common" title="常用功能">
-    <span slot="header-right" class="header-right-set" @click="changeDialogState(true)"><i class="icon-set"></i> 设置</span>
+    <span slot="header-right" class="header-right-set" @click="changeDialogState(true)"><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" v-if="item.icon && item.icon.indexOf('icon-') === 0">
+        <div class="icon-box-container" v-if="item.icon && item.icon.indexOf('icon-') === 0">
           <JyIcon :name="item.icon" classPrefix=""></JyIcon>
         </div>
-        <div v-else class="icon-box">
+        <div v-else class="icon-box-container">
           <el-image :src="item.icon" alt="常用功能">
             <img slot="error" src="https://www.jianyu360.cn/common-module/public/image/auto.png" />
           </el-image>
@@ -14,7 +14,7 @@
         <span v-html="item.name" class="item-name"></span>
       </div>
       <div v-if="commonList && commonList.length < maxCount" class="list-add" @click="changeDialogState(true)">
-        <span class="icon-add"></span>
+        <span class="icon-add-img"></span>
         <span class="add-text">添加常用功能</span>
       </div>
     </div>
@@ -149,12 +149,15 @@ $main: #2cb7ca;
     color: #686868;
   }
 }
-.icon-box {
+.icon-box-container {
   display: flex;
   align-items: center;
   justify-content: center;
   width: 44px;
   height: 44px;
+  ::before{
+    content: ''
+  }
   ::v-deep {
     .el-image {
       width: 100%;
@@ -167,7 +170,7 @@ $main: #2cb7ca;
   }
 }
 
-.icon-set{
+.icon-set-img{
   display: inline-block;
   width: 18px;
   height: 18px;
@@ -175,7 +178,7 @@ $main: #2cb7ca;
   background: url('~@/assets/images/icon/icon-set.png') no-repeat center center;
   background-size: contain;
 }
-.icon-add{
+.icon-add-img{
   display: inline-block;
   width: 44px;
   height: 44px;

Some files were not shown because too many files changed in this diff