Bladeren bron

feat:找医疗机构相关&订阅设置相关

yangfeng 3 jaren geleden
bovenliggende
commit
af64a23992

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

@@ -122,3 +122,13 @@ export function getPushCount (data) {
     data: data
   })
 }
+
+// 超级订阅订阅设置-推送设置
+export function getPushSetDetail (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/pushset/detail',
+    method: 'post',
+    data: data
+  })
+}

+ 20 - 8
src/components/drawer/Drawer.vue

@@ -11,13 +11,17 @@
       @before-close="beforeClose"
       @close="close"
       >
-      <div slot="title">
-        {{ title }}
-      </div>
-      <slot></slot>
-      <div class="el-footer">
+      <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>
@@ -108,10 +112,18 @@ export default {
 
 }
 ::v-deep .el-drawer__body{
-  overflow: scroll;
+  height: 100%;
+  .el-container{
+    display: flex;
+    justify-content: space-between;
+    flex-direction: column;
+    height: 100%;
+  }
+  .el-main{
+    flex: 1;
+    overflow-y: scroll;
+  }
   .el-footer {
-    position: fixed;
-    bottom: 0;
     display: flex;
     justify-content: center;
     align-items: center;

+ 4 - 20
src/components/push-list/MedicalList.vue

@@ -132,23 +132,6 @@ export default {
   created () {},
   methods: {
     async changeClaim (item) {
-      // this.selectItem = item
-      // this.cur.fid = item.entId
-      // if (this.filters.pcor === 'R') {
-      //   if (!item.follow) {
-      //     this.dialog.group = true
-      //   } else {
-      //     this.$emit(item.follow ? 'remove' : 'follow', item)
-      //     item.follow = !item.follow
-      //     this.$forceUpdate()
-      //   }
-      // } else if (this.filters.pcor === 'C') {
-      //   this.$emit(item.follow ? 'remove' : 'follow', item)
-      //   setTimeout(() => {
-      //     item.follow = !item.follow
-      //   }, 1000)
-      //   this.$forceUpdate()
-      // }
       if (item.isClaim) {
         // 取消认领
         const { error_code: code, error_msg: msg } = await institutionUnClaimed({
@@ -164,7 +147,8 @@ export default {
       } else {
         // 认领
         const { error_code: code, error_msg: msg } = await setInstitutionClaim({
-          ent_id: item.company_id
+          ent_id: item.company_id,
+          ent_name: item.company_name
         })
         if (code === 0) {
           this.$toast('认领成功')
@@ -204,8 +188,8 @@ export default {
         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
+        business_type_code: this.getFilters.nature,
+        sdequipment_code: this.getFilters.scope
       }
 
       if (query && Object.keys(query).length > 0) {

+ 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>

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

@@ -0,0 +1,324 @@
+<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"></KeyList>
+    </div>
+    <el-dialog
+      custom-class="sub-dialog nesting-dialog"
+      :visible.sync="add.dialog"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+      append-to-body
+    >
+      <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
+    }
+  },
+  mounted () {
+    console.log(this.keywordsList)
+  },
+  methods: {
+    parentGetCurEdit () {
+      const t = this.$refs.keyConfigRef.getCurEdit()
+      return t
+    },
+    // 新增关键词分类弹框
+    addClassifyFn () {
+      const t = this.parentGetCurEdit()
+      console.log(t)
+      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()
+    },
+    // 获取所有分类名
+    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{
+  padding: 0 30px;
+  .key-title{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding-bottom: 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: '';
+        }
+      }
+      .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>
+<style lang="scss">
+.sub-dialog{
+  .el-dialog__header,
+  .sub-dialog .el-dialog__body{
+    padding: 0;
+  }
+  .selector-card,
+  .selector-card.s-card{
+    // width: 800px;
+    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;
+  &.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;
+  }
+}
+</style>

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

@@ -0,0 +1,306 @@
+<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"
+      :visible.sync="showEmail"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      title="设置邮箱地址"
+      width="380px"
+      append-to-body
+    >
+      <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))
+    }
+  },
+  watch: {},
+  created () {},
+  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.rateModel
+      }
+      this.$emit('update', data)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.push-config{
+  padding-bottom: 80px;
+  .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>
+<style lang="scss">
+.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;
+      }
+    }
+  }
+  .el-input.is-active .el-input__inner,
+  .el-input__inner:focus {
+    border-color: $color-text--highlight;
+  }
+}
+</style>

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

@@ -0,0 +1,405 @@
+<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="projectMatch"
+            :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="keyMatchWay" @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"
+      :visible.sync="dialog.area"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+      append-to-body
+    >
+      <AreaSelect
+        :initCityMap="areaData"
+        @onCancel="dialog.area = false"
+        @onConfirm="saveAreaData"
+      ></AreaSelect>
+    </el-dialog>
+    <!-- 采购单位行业dialog -->
+    <el-dialog
+      custom-class="sub-dialog nesting-dialog"
+      :visible.sync="dialog.buyClass"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+      append-to-body
+    >
+      <BuyClassSelect
+        :initCate="buyerClassData"
+        :otherMatch="otherBuyerClass"
+        @onCancel="dialog.buyClass = false"
+        @onConfirm="saveBuyClassData"
+        @onOtherConfirm="saveOtherBuyClass">
+        <div slot="header">选择采购单位类型</div>
+      </BuyClassSelect>
+    </el-dialog>
+    <!-- 信息类型dialog -->
+    <el-dialog
+      custom-class="sub-dialog nesting-dialog"
+      :visible.sync="dialog.infoType"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+      append-to-body
+    >
+      <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: {
+    datas: {
+      projectMatch: Number,
+      otherBuyerClass: Number,
+      matchWay: Number,
+      area: Object,
+      buyerClass: Array,
+      infoType: Array
+    }
+  },
+  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: '',
+      projectMatch: this.datas.projectMatch,
+      keyMatchWay: this.datas.matchWay,
+      otherBuyerClass: this.datas.otherBuyerClass,
+      areaData: this.datas.area,
+      buyerClassData: this.datas.buyerClass,
+      infoTypeData: this.datas.infoType,
+      dialog: {
+        area: false,
+        buyClass: false,
+        infoType: false
+      }
+    }
+  },
+  mounted () {
+    this.initData()
+  },
+  methods: {
+    initData () {
+      this.formatterArea(this.datas.area)
+      this.formatterBuyerClass(this.datas.buyerClass)
+      this.formatterInfoType(this.datas.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.datas.otherBuyerClass ? '、匹配未分类行业' : '')
+      } else {
+        this.buyClassStr = '全部' + (this.datas.otherBuyerClass ? '、匹配未分类类型' : '')
+      }
+    },
+    formatterInfoType (data = []) {
+      if (data && data.length > 0) {
+        this.infoTypeStr = data.join('、')
+      } else {
+        this.infoTypeStr = '全部类型'
+      }
+    },
+    // 切换项目匹配按钮
+    switchProjectMatch (event) {
+      this.projectMatch = event
+      this.saveCommonData()
+    },
+    // 关键词匹配方式
+    chooseMatchWay (event) {
+      this.keyMatchWay = 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.otherBuyerClass = data.selected ? 1 : 0
+      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.projectMatch,
+        matchway: this.keyMatchWay,
+        otherbuyerclass: this.otherBuyerClass
+      })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.subscribe-config{
+  .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>
+<style lang="scss">
+.sub-dialog{
+  &.nesting-dialog{
+    position: absolute;
+    right: 70px;
+  }
+}
+.sub-dialog .el-dialog__header,
+.sub-dialog .el-dialog__body{
+  padding: 0;
+}
+</style>

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

@@ -0,0 +1,596 @@
+<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">
+        <div class="item-label">匹配模式:</div>
+        <div class="item-value">
+          <el-radio-group v-model="cur.matchway" @change="chooseMatchWay($event)">
+            <el-radio 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"
+      title="新增关键词组"
+      :visible.sync="dialog.repeat"
+      width="380px"
+      center
+      :show-close="false"
+      append-to-body
+      >
+      <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 () {
+      // console.log(this.classIndex, this.keyIndex, this.datas)
+      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
+      }
+      console.log(params, 'params')
+      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
+        })
+      })
+    },
+    // 判断有无更改
+    // getIsChange () {
+    //   const oldKey = JSON.stringify(this.keywords.key)
+    //   const oldAppendKey = JSON.stringify(this.keywords.appendkey ? this.keywords.appendkey : null)
+    //   const oldMatchWay = this.keywords.matchway
+    //   const oldNotKey = JSON.stringify(this.keywords.notkey ? this.keywords.notkey : null)
+    //   const newNotKey = JSON.stringify(this.cur.notkey ? this.cur.notkey.split(' ') : null)
+    //   const newMatchWay = this.cur.matchway
+    //   const newKeyArr = this.cur.key.split(' ')
+    //   const newKey = JSON.stringify(newKeyArr[0].split(','))
+    //   const temp = newKeyArr.slice(1, newKeyArr.length)
+    //   const newAppendKey = JSON.stringify(temp && temp.length > 0 ? temp : null)
+    //   return oldKey === newKey && oldAppendKey === newAppendKey && oldNotKey === newNotKey && oldMatchWay === newMatchWay
+    // },
+    // 判断附加词排除词输入框是否有空
+    inputIsEmpty (arr) {
+      return arr.some((v) => !v)
+    },
+    // 获取所有关键词的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: '';
+      }
+    }
+    .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;
+  }
+  .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;
+  }
+  .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;
+  }
+}
+// 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>

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

@@ -0,0 +1,841 @@
+<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="item.showForm = false"
+            @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 nesting-dialog"
+      :visible.sync="dialog.editClass"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+      append-to-body
+    >
+      <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"
+      :visible.sync="dialog.delClass"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      top="30vh"
+      width="380px"
+      append-to-body
+    >
+      <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"
+      :visible.sync="dialog.delKey"
+      :close-on-click-modal="false"
+      :show-close="false"
+      top="30vh"
+      center
+      width="380px"
+      append-to-body
+    >
+      <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"
+      title="删除关键词分类"
+      :visible.sync="dialog.notDelClass"
+      width="380px"
+      center
+      :show-close="false"
+      append-to-body
+      >
+      <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"
+      title="新增关键词组"
+      :visible.sync="dialog.limit"
+      width="380px"
+      center
+      :show-close="false"
+      append-to-body
+      >
+      <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'
+import $bus from '@/utils/bus'
+import { setUserInfoVip } from '@/api/modules'
+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 // 将父组件传来的数据赋值给该变量,
+    }
+  },
+  computed: {},
+  watch: {
+    // list (newVal) {
+    //   console.log(newVal, '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
+      console.log(data)
+      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) {
+                  s = data
+                }
+              })
+            } 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
+      })
+    },
+    // 修改设置接口
+    setInfoApi (data, callback) {
+      setUserInfoVip(data).then((res) => {
+        if (res.flag) {
+          $bus.$emit('updateKey')
+          callback && callback()
+        } else {
+          this.$message(res.msg)
+        }
+      })
+    },
+    // 处理数据 添加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
+        }
+      })
+      this.$nextTick(() => {
+        this.dialog.editClass = false
+        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
+    },
+    // 打开删除关键词分类弹框
+    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 () {
+      console.log(this.props.classIndex, '分类下标')
+      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()
+      }
+    },
+    // 查询有无正在编辑的表单
+    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>
+// 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: 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;
+//   }
+//   .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;
+//           }
+//         }
+//       }
+//     }
+//   }
+// }
+// .sub-dialog.small-dialog::v-deep.el-dialog{
+//   border-radius: 8px!important;
+// }
+.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: '';
+    }
+  }
+  .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: 12px;
+      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>

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

@@ -27,5 +27,11 @@ export default [
     path: '/medical/medical_portrait',
     name: 'medical_portrait',
     component: () => import('@/views/medical-field/MedicalPortrait.vue')
+  },
+  // 临时页面-----
+  {
+    path: '/medical/demo',
+    name: 'medical_demo',
+    component: () => import('@/views/medical-field/Demo.vue')
   }
 ]

+ 136 - 0
src/views/medical-field/Demo.vue

@@ -0,0 +1,136 @@
+<template>
+  <div class="demo">
+    <button class="button-default" @click="onSubscribe">订阅管理</button>
+    <DrawerCard customClass="drawer-class" :with-header="false" percent="600px" v-model="showDrawer" @close="onCloseDrawer" @saveData="onSaveDrawer">
+      <SubConfig :datas="setData" @update="onUpdateSub"></SubConfig>
+      <KeyConfig
+        :list="setData.keyList"
+        :max-count="setData.maxCount">
+      </KeyConfig>
+      <PushConfig
+        :rateModel="setData.rateModel"
+        :mailPush="setData.mailPush"
+        :appPush="setData.appPush"
+        :mail="setData.mail"
+        @update="onUpdatePush"
+      >
+      </PushConfig>
+    </DrawerCard>
+  </div>
+</template>
+
+<script>
+import DrawerCard from '@/components/drawer/Drawer'
+import SubConfig from '@/components/subscribe-manager/SubConfig'
+import KeyConfig from '@/components/subscribe-manager/KeyConfig'
+import PushConfig from '@/components/subscribe-manager/PushConfig'
+import { getSVIPBuyInfo, getPushSetDetail } from '@/api/modules/'
+export default {
+  name: 'demoComp',
+  components: {
+    DrawerCard,
+    SubConfig,
+    KeyConfig,
+    PushConfig
+  },
+  data () {
+    return {
+      showDrawer: false,
+      setData: {
+        // 区域对象
+        area: {},
+        // 采购单位行业
+        buyerClass: [],
+        // 信息类型
+        infoType: [],
+        // 关键词
+        keyList: [],
+        matchWay: null,
+        projectMatch: null,
+        otherBuyerClass: null,
+        rateModel: 1,
+        mailPush: false,
+        appPush: false,
+        mail: '',
+        maxCount: 0
+      }
+    }
+  },
+  created () {
+    this.getSubInfo()
+    this.getPushSetApi()
+  },
+  methods: {
+    onSubscribe () {
+      this.showDrawer = true
+    },
+    onCloseDrawer (data) {
+      this.showDrawer = data
+    },
+    onSaveDrawer () {
+      this.$nextTick(() => {
+        this.$refs.scopeRef.saveSetting()
+        this.showDrawer = false
+      })
+    },
+    // 获取超级订阅订阅设置信息
+    async getSubInfo () {
+      const { data, errMsg } = await getSVIPBuyInfo()
+      if (data) {
+        const { area, industry, infotype, items, matchway, projectmatch, otherbuyerclass, key_max_length: maxLength } = data
+        this.setData.area = area
+        this.setData.buyerClass = industry
+        this.setData.infoType = infotype
+        items.forEach((v, index) => {
+          if (v) {
+            v.showForm = false
+            if (!v.updatetime) {
+              v.updatetime = 0
+            }
+            if (v.a_key && v.a_key.length > 0) {
+              v.a_key.forEach((s, j) => {
+                s.showForm = false
+                if (!s.updatetime) {
+                  s.updatetime = 0
+                }
+              })
+            }
+          }
+        })
+        this.setData.keyList = items
+        this.setData.matchWay = matchway
+        this.setData.projectMatch = projectmatch
+        this.setData.otherBuyerClass = otherbuyerclass
+        this.setData.maxCount = maxLength
+      } else {
+        this.$toast(errMsg)
+      }
+    },
+    // 获取超级订阅推送设置信息
+    async getPushSetApi () {
+      const { data, errMsg } = await getPushSetDetail({
+        item: 'super_subscribe'
+      })
+      if (data) {
+        const { mail, ratemode, apppush, mailpush } = data
+        this.setData.mail = mail || ''
+        this.setData.rateModel = ratemode
+        this.setData.appPush = apppush
+        this.setData.mailPush = mailpush
+      } else {
+        this.$toast(errMsg)
+      }
+    },
+    onUpdateSub (data) {
+      console.log(data, '冒泡来的订阅数据')
+    },
+    onUpdatePush (data) {
+      console.log(data, '冒泡来的推送设置数据')
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.drawer-class{}
+</style>

+ 37 - 50
src/views/medical/FindMedical.vue

@@ -1,44 +1,40 @@
 <template>
   <div class="find-medical">
-    <forLayOut :search="false">
-      <template v-slot:main>
-        <div class="filter-container">
-          <CommonTab title='筛选条件'>
-            <div slot="right" class="tab-slot-right" @click="onTabRight">
-              <i class="iconfont icon-hui6"></i>
-              <span>业务范围</span>
-            </div>
-          </CommonTab>
-          <div class="input-Selector">
-            <div class="filter-label filter-input-label">医疗机构名称:</div>
-            <BaseInput placeholder="请输入医疗机构名称" v-model="filters.name"></BaseInput>
-          </div>
-          <AreaSelector  @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 class="filter-container">
+      <CommonTab title='筛选条件'>
+        <div slot="right" class="tab-slot-right" @click="onTabRight">
+          <i class="iconfont icon-hui6"></i>
+          <span>业务范围</span>
         </div>
-        <div class="medical-list-container">
-          <MedicalList :filters="filters" ref="pushList" @goDetail="goDetail"></MedicalList>
-        </div>
-      </template>
-    </forLayOut>
-    <DrawerCard customClass="drawer-class" v-model="showDrawer" @close="onCloseDrawer" @saveData="onSaveDrawer">
+      </CommonTab>
+      <div class="input-Selector">
+        <div class="filter-label filter-input-label">医疗机构名称:</div>
+        <BaseInput placeholder="请输入医疗机构名称" v-model="filters.name"></BaseInput>
+      </div>
+      <AreaSelector  @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" :with-header="false" v-model="showDrawer" @close="onCloseDrawer" @saveData="onSaveDrawer">
       <ScopeContent ref="scopeRef"></ScopeContent>
     </DrawerCard>
   </div>
@@ -47,7 +43,6 @@
 <script>
 import '@/assets/style/iconfont.css'
 import { Drawer } from 'element-ui'
-import forLayOut from '@/components/forecast/ForLayout.vue'
 import BaseInput from '@/components/input/BaseInput'
 import CommonTab from '@/components/medical/CommonTab.vue'
 import AreaSelector from '@/components/selector/AreaSelector.vue'
@@ -64,7 +59,6 @@ export default {
   name: 'findMedical',
   components: {
     [Drawer.name]: Drawer,
-    forLayOut,
     CommonTab,
     BaseInput,
     AreaSelector,
@@ -128,7 +122,7 @@ export default {
       setHospitalType: 'medical/setHospitalType'
     }),
     changeSubmitInfo () {
-      this.$refs.pushList.doQuery(this.filters)
+      this.$refs.pushList.doQuery()
     },
     resetFilter () {
       this.filters.name = ''
@@ -226,7 +220,7 @@ export default {
     },
     goDetail (item) {
       const routeUrl = this.$router.resolve({
-        path: `/ent_portrait/${item.company_id}`
+        path: `/medical/medical_portrait/${item.company_id}`
       })
       window.open(routeUrl.href, '_blank')
     }
@@ -236,6 +230,7 @@ export default {
 
 <style lang="scss" scoped>
 .find-medical{
+  padding: 24px;
   .filter-container{
     background: #fff;
     .tab-slot-right{
@@ -329,11 +324,3 @@ export default {
   }
 }
 </style>
-<style lang="scss">
-.drawer-class {
-  .el-drawer__header{
-    margin-bottom: 0!important;
-    padding: 0!important;
-  }
-}
-</style>