Переглянути джерело

Merge branch 'main' into feature/v1.0.81

yuelujie 7 місяців тому
батько
коміт
bfe9bcd3ec

+ 10 - 1
apps/bigmember_pc/src/components/subscribe-manager/index.vue

@@ -16,6 +16,9 @@
         disabled
       >
       </el-switch>
+      <div class="subscribe-manage-tip">
+        注:修改完订阅条件后,预计30分钟左右生效。
+      </div>
     </div>
     <SubConfig
       :other-buyer-class="setData.otherBuyerClass"
@@ -283,7 +286,7 @@ export default {
 <style lang="scss">
 .subscribe-manage{
   &-title{
-    padding: 26px 30px;
+    padding: 13px 30px;
     font-size: 18px;
     color: #1d1d1d;
     line-height: 28px;
@@ -565,6 +568,12 @@ export default {
   .prevent-loading{
     opacity: 0;
   }
+  .subscribe-manage-tip {
+    margin-top: 4px;
+    font-size: 14px;
+    color: #2ABED1;
+    line-height: 22px;
+  }
   .reset-disabled-switch{
     &.is-disabled {
       opacity: 1;

+ 12 - 0
apps/bigmember_pc/src/views/subscribe/Config.vue

@@ -16,6 +16,9 @@
         disabled
       >
       </el-switch>
+      <div class="subscribe-manage-tip">
+        注:修改完订阅条件后,预计30分钟左右生效。
+      </div>
     </div>
     <!-- 订阅设置 -->
     <sub-config :datas="setData" :setStatus="setStatus" @update="getUpdate"></sub-config>
@@ -422,12 +425,20 @@ export default {
   border-radius: 5px;
   box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.08);
   &-title{
+    display: flex;
+    align-items: center;
     padding-bottom: 8px;
     font-size: 18px;
     color: #1d1d1d;
     line-height: 28px;
     border-bottom: 1px solid #ececec;
   }
+  .subscribe-manage-tip {
+    margin-left: 22px;
+    font-size: 14px;
+    color: #2ABED1;
+    line-height: 22px;
+  }
   .reset-tips {
     width: 100%;
     height: 32px;
@@ -466,6 +477,7 @@ export default {
     opacity: 0;
   }
   .reset-disabled-switch{
+    margin-left: 9px;
     &.is-disabled {
       opacity: 1;
       .el-switch__core,

+ 9 - 1
apps/mobile/src/api/modules/entbase.js

@@ -1,5 +1,5 @@
-import request from '@/api'
 import qs from 'qs'
+import request from '@/api'
 
 /**
  * 根据 entId 获取 企业信息
@@ -52,3 +52,11 @@ export function getMypersondept(data) {
     params: data
   })
 }
+
+// 获取用户权限
+export function getUserRule() {
+  return request({
+    url: '/entnicheNew/distribute/userRule',
+    method: 'GET'
+  })
+}

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

@@ -1,5 +1,6 @@
 import qs from 'qs'
 import request from '@/api'
+import { envs } from '@/utils/prototype/modules/platform'
 
 export function userVipSwitchState(data) {
   data = qs.stringify(data)
@@ -373,3 +374,13 @@ export function getIsSub(data) {
     data
   })
 }
+
+// 相似订阅推荐
+export function getRecommend(data) {
+  data = qs.stringify(data)
+  return request({
+    url: envs.inWX ? '/member/getRecomKWs' : '/jyapp/member/getRecomKWs',
+    method: 'post',
+    data: data
+  })
+}

+ 9 - 0
apps/mobile/src/api/modules/subscribe.js

@@ -133,3 +133,12 @@ export function setPushSet(data) {
     data
   })
 }
+
+// 保存订阅向导设置
+export function saveSubscribeGuide(type = 'fType', data) {
+  return request({
+    url: `/jyapi/jybx/subscribe/${type}/saveTSGuide`,
+    method: 'post',
+    data
+  })
+}

BIN
apps/mobile/src/assets/image/guide/subscribe-guide.png


BIN
apps/mobile/src/assets/image/icon/shuaxin.png


+ 39 - 10
apps/mobile/src/components/selector/area-three-sidebar/index.vue

@@ -123,7 +123,7 @@ export default {
       default: ''
     },
     value: {
-      type: Object,
+      type: [Object, String, Number],
       default() {
         return {}
       }
@@ -158,7 +158,14 @@ export default {
      *
      * 如果传入该函数,点击二级子项会触发,返回true会确认成功,返回false则会阻止更改
      */
-    beforeChange: Function
+    beforeChange: Function,
+    /**
+     * 是否可全不选中
+     */
+    canEmptySelected: {
+      type: Boolean,
+      default: false
+    }
   },
   model: {
     prop: 'value',
@@ -210,7 +217,8 @@ export default {
     this.init(this.provinceListMapExp)
   },
   mounted() {
-    this.setState()
+    const defaultVal = this.canEmptySelected ? -1 : {}
+    this.setState(defaultVal)
     this.setActiveTab(1)
   },
   methods: {
@@ -434,9 +442,11 @@ export default {
      */
     getStateMore() {
       // 选中状态
-      const state = {}
+      let state = {}
       // 选中数量统计
       const stateCounter = {}
+      // 是否选了全国
+      let isSelectedCountry = false
       // 所有二级市区遍历
       for (let refKey in this.secondRefNameObj) {
         const refName = this.secondRefNameObj[refKey]
@@ -448,9 +458,14 @@ export default {
           const provinceName = parent.parentName
           // 全国选中
           if (parent.level === 0 && provinceName === '全国') {
-            if (parent._selected) {
+            if (children && children[0]._selected) {
+              isSelectedCountry = true
               break
             }
+            // if (parent._selected) {
+            //   isSelectedCountry = true
+            //   break
+            // }
             continue
           }
           // 当前省份下,全部选中(保存当前省份)
@@ -496,8 +511,16 @@ export default {
           }
         }
       }
+      let resultState = state
+      if (
+        Object.keys(state).length === 0 &&
+        !isSelectedCountry &&
+        this.canEmptySelected
+      ) {
+        resultState = -1
+      }
       return {
-        state,
+        state: resultState,
         stateCounter
       }
     },
@@ -507,10 +530,16 @@ export default {
      * 全部不选中传入-1
      */
     setState(state = {}) {
+      // 所有都不选中
+      if (state === -1) {
+        this.resetSelect(false)
+        this.$emit('modelChange', -1)
+        return
+      }
       const firstSidebar = this.$refs.firstSidebar
       firstSidebar.setAllState(false)
       firstSidebar.refreshAllChildrenState(false)
-      if (!state || Object.keys(state).length === 0) {
+      if (typeof state === 'object' && Object.keys(state).length === 0) {
         // 重置第二级,并且选中全国
         this.resetSelect()
       } else {
@@ -520,7 +549,6 @@ export default {
       // 重新计算parent数据统计
       firstSidebar.refreshAllChildrenState()
       this.$emit('modelChange', state || {})
-      console.log(state, 'state')
     },
     setSidebarState(state) {
       for (const proName in state) {
@@ -573,17 +601,18 @@ export default {
       }
     },
     // 重置所有选择
-    resetSelect() {
+    resetSelect(selectedAll = true) {
       const { firstSidebar } = this.$refs
       firstSidebar.setAllState(false)
       firstSidebar.refreshAllChildrenState(false)
+      firstSidebar.setParentLevel0State(selectedAll)
       const firstRenderList = firstSidebar.renderList
       for (let item of firstRenderList) {
         const secondRef = this.$refs['secondSidebar_' + item.id]
         secondRef?.setAllState(false)
         secondRef?.refreshAllChildrenState(false)
         if (item.name === '全国') {
-          secondRef?.setParentLevel0State(true)
+          secondRef?.setParentLevel0State(selectedAll)
         }
       }
       this.sourceFirstCount = {}

+ 461 - 0
apps/mobile/src/components/subscribe/RecommendedWords.vue

@@ -0,0 +1,461 @@
+<template>
+  <div class="keywords-container">
+    <div class="keywords-card">
+      <slot name="header"></slot>
+      <div class="flex flex-(wrap items-center) keywords-list">
+        <div
+          class="flex flex-items-center selected-key-item"
+          v-for="(item, index) in keywords.list"
+          :key="index"
+        >
+          <span>{{ item }}</span>
+          <van-icon @click="onDeleteItem(item)" name="clear" />
+        </div>
+      </div>
+      <div class="flex flex-items-center keywords-input">
+        <van-field
+          class="keyword-field"
+          v-model="keywords.input"
+          format-trigger="onBlur"
+          maxlength="15"
+          show-word-limit
+          placeholder="请输入关键词"
+          clearable
+          @input="onKeywordInput"
+        ></van-field>
+        <span class="add-action" @click="onAddKeyword">添加</span>
+      </div>
+    </div>
+    <div class="rec-card" v-show="isShowRecommend">
+      <div class="flex flex-(items-center justify-between) rec-header">
+        <div class="rec-title">相似订阅推荐</div>
+        <div
+          class="rec-change"
+          @click="nextPageRec"
+          v-show="recListState.listAll.length > recListState.pageSize"
+        >
+          <!-- <van-icon :class="recListState.loading ? 'active' : ''" name="@/assets/image/icon/shuaxin.png"></van-icon> -->
+          <img
+            class="refresh-img"
+            :class="recListState.loading ? 'active' : ''"
+            width="16"
+            height="16"
+            src="@/assets/image/icon/shuaxin.png"
+          />
+          <span class="text">换一批</span>
+        </div>
+      </div>
+      <div class="rec-content">
+        <div class="rec-tags">
+          <div
+            class="ellipsis tag"
+            v-for="(item, index) in recListState.list"
+            :key="index"
+            @click="clickTag(item)"
+          >
+            {{ item }}
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Field, Icon } from 'vant'
+import { getRecommend } from '@/api/modules'
+import { bSort } from '@/utils/utils'
+import { debounce } from 'lodash'
+export default {
+  name: 'RecommendedWords',
+  components: {
+    [Field.name]: Field,
+    [Icon.name]: Icon
+  },
+  props: {
+    vSwitch: {
+      type: String,
+      default: 'f'
+    },
+    maxlength: {
+      type: Number,
+      default: 10
+    },
+    value: {
+      type: Array,
+      default: () => []
+    }
+  },
+  model: {
+    prop: 'value',
+    event: 'change'
+  },
+  data() {
+    return {
+      keywords: {
+        input: '',
+        list: []
+      },
+      recListState: {
+        loading: false, // 是否刷新中?
+        loaded: false, // 请求是否完成
+        pageNum: 1,
+        pageSize: 6,
+        total: 0, // 一共多少条数据
+        list: [],
+        listAll: [] // 后端返回的全部推荐关键词
+      }
+    }
+  },
+  watch: {
+    value() {
+      this.keywords.list = this.value || []
+      this.getKeyRecommend()
+    },
+    'keywords.input'(val) {
+      // console.log(val, 'watch input')
+    }
+  },
+  computed: {
+    isShowRecommend() {
+      return (
+        this.recListState.listAll.length > 0 &&
+        this.keywords.list.length < this.maxlength
+      )
+    }
+  },
+  mounted() {
+    this.getKeyRecommend()
+  },
+  methods: {
+    onKeywordInput: debounce(function (value) {
+      if (
+        !this.keywords.input.trim() ||
+        this.keywords.list.length >= this.maxlength
+      )
+        return
+      const formatVal =
+        this.vSwitch === 'f' || !this.vSwitch
+          ? this.keywords.input.replace(/\s+/g, '')
+          : this.keywords.input
+      const arr = [].concat(this.value, formatVal)
+      this.getKeyRecommend(arr)
+    }, 2000),
+    onAddKeyword() {
+      if (!this.keywords.input.trim()) return
+      if (this.keywords.list.includes(this.keywords.input)) {
+        return this.$toast('该关键词已存在,请勿重复添加')
+      }
+      if (this.keywords.list.length >= this.maxlength) {
+        return this.$toast('关键词已达上限')
+      }
+      const formatVal =
+        this.vSwitch === 'f' || !this.vSwitch
+          ? this.keywords.input.replace(/\s+/g, '')
+          : this.keywords.input
+      this.keywords.list.unshift(formatVal)
+      this.keywords.input = ''
+      this.$emit('change', this.keywords.list)
+    },
+    onDeleteItem(item) {
+      const index = this.keywords.list.indexOf(item)
+      if (index !== -1) {
+        this.keywords.list.splice(index, 1)
+        this.$emit('change', this.keywords.list)
+      }
+    },
+    unique(arr) {
+      if (!Array.isArray(arr)) {
+        console.log('type error!')
+        return
+      }
+      var array = []
+      for (var i = 0; i < arr.length; i++) {
+        if (array.indexOf(arr[i]) === -1) {
+          array.push(arr[i])
+        }
+      }
+      return array
+    },
+    getKeyRecommend(selected = this.value) {
+      var keysArr = []
+      selected.forEach((item) => {
+        if (item) {
+          if (item instanceof Array) {
+            keysArr.push(item.join('+'))
+          } else {
+            keysArr.push(item)
+          }
+        }
+      })
+      if (keysArr.length === 0) return
+      if (this.recListState.loading) return
+      this.recListState.loading = true
+      const params = {
+        count: 20, // 最少需要多少条数据
+        value: this.unique(keysArr).join(' ')
+      }
+      getRecommend(params).then((res) => {
+        this.recListState.loading = false
+        if (res && res instanceof Array && res.length !== 0) {
+          this.loadKeyRecommend(res)
+        }
+      })
+    },
+    loadKeyRecommend(list) {
+      // 页码初始化
+      this.recListState.pageNum = 1
+      this.recListState.total = 0
+
+      const afterFilterArr = this.filterKeyRecommend(list)
+
+      this.recListState.listAll = afterFilterArr
+      this.recListState.count = afterFilterArr.length
+      this.nextPageRec()
+    },
+    // 过滤后端返回的推荐数组
+    filterKeyRecommend(list) {
+      const arr = bSort(list, 'sim')
+      // 排序后
+      const afterSort = arr.reverse()
+      const afterTile = []
+
+      // 已订阅关键词数组
+      const allKeyArr = []
+      const allKeyArrLower = []
+
+      // 已订阅关键词的整理 -----
+      // 将所有关键词整理到一个数组中
+      this.value.forEach((item) => {
+        if (item instanceof Array) {
+          allKeyArr = allKeyArr.concat(item)
+        } else {
+          allKeyArr.push(item)
+        }
+      })
+      // 数组中英文转小写
+      allKeyArr.forEach((item) => {
+        allKeyArrLower.push(item.toLowerCase())
+      })
+
+      // 推荐数组整理
+      // 平铺,将数组中的对象去掉,使用字符串平铺内容
+      afterSort.forEach((item) => {
+        afterTile.push(item.word.toLowerCase())
+      })
+      // 去重1,当前数组中的内容去重,不精确大小写
+      let afterTileLength = afterTile.length
+      for (let i = afterTileLength - 1; i >= 0; i--) {
+        const aIndex = afterTile.indexOf(afterTile[i].toLowerCase())
+        if (aIndex !== i) {
+          afterTile.splice(i, 1)
+        }
+      }
+
+      // 去重2,找到已经订阅过的,进行删除
+      afterTileLength = afterTile.length
+      for (let j = afterTileLength - 1; j >= 0; j--) {
+        const aIndex = allKeyArrLower.indexOf(afterTile[j])
+        if (aIndex !== -1) {
+          afterTile.splice(j, 1)
+        }
+      }
+
+      return afterTile
+    },
+    getRecListTags() {
+      const listAll = this.recListState.listAll
+      this.recListState.total = listAll.length
+
+      const startIndex =
+        (this.recListState.pageNum - 1) * this.recListState.pageSize
+      const endIndex = this.recListState.pageNum * this.recListState.pageSize
+
+      return listAll.slice(startIndex, endIndex)
+    },
+    nextPageRec() {
+      if (this.recListState.loading) return
+      this.recListState.loading = true
+      this.recListState.list = this.getRecListTags()
+
+      setTimeout(() => {
+        this.recListState.loading = false
+      }, 500)
+
+      // 最后一页,则重置页码
+      if (
+        this.recListState.pageNum * this.recListState.pageSize >=
+        this.recListState.total
+      ) {
+        this.recListState.pageNum = 1
+      } else {
+        this.recListState.pageNum++
+      }
+      return this.recListState.list
+    },
+    clickTag(tag) {
+      // const key = this.keywords.input.trim()
+      // if (key) {
+      //   let keyArr = key.split(/\s+/)
+      //   keyArr.push(tag)
+      //   if (this.vSwitch === 'f') {
+      //     this.keywords.input = this.unique(keyArr).join('')
+      //   } else {
+      //     this.keywords.input = this.unique(keyArr).join(' ')
+      //   }
+      // } else {
+      //   this.keywords.input = tag
+      // }
+      // const inputDom = document.querySelector(
+      //   '.keyword-field .van-field__control'
+      // )
+      // this.changInputValue(inputDom, this.keywords.input)
+      if (this.keywords.list.includes(tag)) {
+        return this.$toast('该关键词已存在,请勿重复添加')
+      }
+      if (this.keywords.list.length >= this.maxlength) {
+        return this.$toast('关键词已达上限')
+      }
+      this.keywords.list.unshift(tag)
+      this.$emit('change', this.keywords.list)
+    },
+    // 手动赋值后 触发 input 事件(正常不会自动触发)
+    changInputValue(inputDom, newText) {
+      let lastValue = inputDom.value // 获取输入框当前的值
+      inputDom.value = newText // 设置新的值
+      let event = new Event('input', { bubbles: true }) // 创建输入事件
+      event.simulated = true
+      // 处理 React 或 Arco Design 框架的输入值追踪
+      let tracker = inputDom._valueTracker
+      if (tracker) {
+        tracker.setValue(lastValue) // 如果存在追踪器,更新值
+      }
+      inputDom.dispatchEvent(event) // 派发输入事件
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.keywords-container {
+  .keywords-card {
+    margin: 12px;
+    padding: 12px;
+    background: #fff;
+    border-radius: 8px;
+  }
+  .keywords-input {
+    margin-top: 12px;
+  }
+  .keywords-list {
+    margin-left: -6px;
+  }
+  .selected-key-item {
+    margin: 6px;
+    padding: 4px 8px;
+    background: linear-gradient(#e1fdff, #deefff00);
+    border: 1px solid rgba(0, 0, 0, 0.05);
+    border-radius: 2px;
+    span {
+      font-size: 14px;
+      line-height: 20px;
+      color: #5f5e64;
+    }
+    .van-icon-clear {
+      margin-left: 4px;
+      color: $main;
+      font-size: 16px;
+    }
+  }
+  .add-action {
+    flex-shrink: 0;
+    color: $color_main;
+    font-size: 14px;
+    line-height: 20px;
+  }
+  .rec-card {
+    margin: 12px;
+    padding: 16px;
+    background: #fff;
+    border-radius: 8px;
+    .rec-title {
+      font-size: 14px;
+      line-height: 24px;
+      color: #5f5e64;
+    }
+    .rec-content {
+      padding-top: 8px;
+    }
+    .rec-change {
+      display: flex;
+      align-items: center;
+      color: $main;
+      font-size: 14px;
+      line-height: 24px;
+      .refresh-img {
+        margin-right: 4px;
+        &.active {
+          animation: refreshLoading 0.5s linear infinite running;
+        }
+      }
+    }
+    .rec-tags {
+      display: flex;
+      width: 100%;
+      flex-wrap: wrap;
+    }
+    .tag {
+      padding: 5px 10px;
+      margin: 8px 16px 8px 0;
+      width: calc((100% - 32px) / 3);
+      font-size: 14px;
+      line-height: 20px;
+      color: #5f5e64;
+      background-color: #f5f6f7;
+      border-radius: 4px;
+      white-space: nowrap;
+      word-break: break-all;
+      text-align: center;
+      &:nth-child(3n) {
+        margin-right: 0;
+      }
+    }
+  }
+}
+::v-deep {
+  .keyword-field {
+    padding: 8px 12px;
+    margin-right: 16px;
+    background: #f5f6f7;
+    line-height: normal;
+    border: 0.5px solid rgba(0, 0, 0, 0.05);
+    box-sizing: border-box;
+    border-radius: 4px;
+    &::after {
+      border-bottom: 0;
+    }
+    .van-cell__value {
+      display: flex;
+      font-size: 14px;
+    }
+    .van-field__body {
+      flex: 1;
+    }
+    .van-field__word-limit {
+      color: #c0c4cc;
+    }
+    .van-field__word-limit {
+      margin-top: 2px;
+    }
+  }
+}
+@keyframes refreshLoading {
+  0% {
+    transform: rotateY(180deg) rotateZ(0);
+  }
+
+  100% {
+    transform: rotateY(180deg) rotateZ(1turn);
+  }
+}
+</style>

+ 37 - 0
apps/mobile/src/data/selector.js

@@ -623,3 +623,40 @@ export const lingyuDataSource = [
   }
 ]
 
+// 省份列表(前12个为产品定的热门省份)
+export const onlyProvinceList = [
+  '北京',
+  '广东',
+  '山东',
+  '河南',
+  '浙江',
+  '江苏',
+  '陕西',
+  '上海',
+  '四川',
+  '湖北',
+  '福建',
+  '河北',
+  '安徽',
+  '湖南',
+  '辽宁',
+  '江西',
+  '山西',
+  '云南',
+  '新疆',
+  '重庆',
+  '广西',
+  '吉林',
+  '贵州',
+  '天津',
+  '甘肃',
+  '黑龙江',
+  '内蒙古',
+  '宁夏',
+  '海南',
+  '青海',
+  '西藏',
+  '香港',
+  '澳门',
+  '台湾'
+]

+ 12 - 0
apps/mobile/src/router/modules/subscribe.js

@@ -0,0 +1,12 @@
+// 静态路由
+export default [
+  {
+    path: '/guide',
+    name: 'guide',
+    component: () => import('@/views/subscribe/Guide.vue'),
+    meta: {
+      header: true,
+      title: '订阅向导'
+    }
+  }
+]

+ 18 - 0
apps/mobile/src/utils/utils.js

@@ -896,3 +896,21 @@ export function getAssetsFile(url) {
 export function toWxGzhProfile () {
   window.location.href = 'https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=Mzk0MjIyMzY2Nw==&scene=110#wechat_redirect'
  }
+
+// 数组对象根据某一个值进行冒泡排序
+// arr 数组
+// value 字符串
+export function bSort (arr, value) {
+  const len = arr.length
+  for (let i = 0; i < len - 1; i++) {
+    for (let j = 0; j < len - 1 - i; j++) {
+      // 相邻元素两两对比,元素交换,大的元素交换到后面
+      if (arr[j][value] > arr[j + 1][value]) {
+        let temp = arr[j]
+        arr[j] = arr[j + 1]
+        arr[j + 1] = temp
+      }
+    }
+  }
+  return arr
+}

+ 806 - 0
apps/mobile/src/views/subscribe/Guide.vue

@@ -0,0 +1,806 @@
+<template>
+  <div class="subscribe-guide">
+    <div class="j-header">
+      <div class="step-container">
+        <van-steps :active="active">
+          <van-step v-for="(item, index) in steps" :key="index">{{
+            item.title
+          }}</van-step>
+        </van-steps>
+      </div>
+    </div>
+    <div class="j-main" v-show="active === 0">
+      <div class="region-container">
+        <div class="region-header flex flex-items-center">
+          <span class="module-title">请选择关注地区</span>
+          <span class="module-note" v-if="isFree && someInfo.areaCount === 1"
+            >(免费用户仅支持订阅1个省)</span
+          >
+        </div>
+        <div class="region-content">
+          <!-- 免费用户 -->
+          <div class="free-content" v-if="isFree && someInfo.areaCount === 1">
+            <CheckboxGroup
+              v-model="freeSelectedProvince"
+              :options="provinceList"
+              :config="checkboxConfig"
+              @change="onChangeCheckbox"
+            >
+            </CheckboxGroup>
+            <div class="text-center other-province" v-show="!expand">
+              <span @click="onExpandArea">其他地区</span>
+            </div>
+          </div>
+          <!-- 付费用户、省份订阅包用户 -->
+          <div class="vip-content" v-else>
+            <div class="selected-province">
+              <span
+                class="selected-province-item"
+                :key="aIndex"
+                v-for="(area, aIndex) in flatAreaResult"
+              >
+                {{ area }}
+              </span>
+            </div>
+            <button
+              class="j-button-cancel j-button-choose"
+              @click="onChangeArea"
+            >
+              {{ flatAreaResult.length ? '继续' : '请' }}选择地区
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="j-main" v-show="active === 1">
+      <div class="keywords-container">
+        <div class="already-selected">
+          <div class="flex flex-(justify-between items-center)">
+            <span class="module-title"
+              >您关注的地区({{ flatAreaResult.length }})</span
+            >
+            <span class="module-action" @click="onEditArea">修改</span>
+          </div>
+          <div style="position: relative">
+            <div class="selected-province no-wrap">
+              <span
+                class="selected-province-item"
+                :key="aIndex"
+                v-for="(area, aIndex) in flatAreaResult"
+              >
+                {{ area }}
+              </span>
+            </div>
+            <div class="more-placeholder">...</div>
+          </div>
+        </div>
+        <div class="keywords-content">
+          <RecommendedWords
+            v-model="someInfo.key"
+            :vSwitch="vSwitch"
+            :maxlength="keyMaxLength"
+          >
+            <div slot="header" class="flex">
+              <span class="module-title"
+                >请设置主营产品({{ someInfo.key.length }}/{{
+                  keyMaxLength
+                }})</span
+              >
+            </div>
+          </RecommendedWords>
+        </div>
+      </div>
+    </div>
+    <div class="j-footer">
+      <div class="j-button-group" :class="{ height40: active === 1 }">
+        <button
+          v-show="active === 0"
+          @click="nextHandle"
+          class="j-button-confirm"
+          :class="{ opacity50: !nextDisabled }"
+        >
+          下一步
+        </button>
+        <button
+          v-show="active === 1"
+          @click="setMore"
+          class="j-button-cancel"
+          :class="{ opacity50: !finishDisabled }"
+        >
+          设置更多条件
+        </button>
+        <button
+          v-show="active === 1"
+          @click="onFinish"
+          class="j-button-confirm"
+          :class="{ opacity50: !finishDisabled }"
+        >
+          完成,查看商机
+        </button>
+      </div>
+    </div>
+    <van-popup
+      class="fix-pop-height"
+      v-model="popup.area"
+      round
+      position="bottom"
+      :safe-area-inset-bottom="true"
+      :close-on-popstate="true"
+      get-container="body"
+      :lazy-render="false"
+      :style="{ 'max-height': '80%' }"
+    >
+      <PopupLayout title="地区选择" @closeIconClick="popup.area = false">
+        <div class="area-result">
+          <span>可选:</span>
+          <span v-html="optionalAreaText"></span>
+          <span>已选:</span>
+          <span v-html="selectedAreaText"></span>
+        </div>
+        <AreaCitySidebar
+          class="area-city-sidebar"
+          :canEmptySelected="true"
+          :tagTextOnlySelectedCount="true"
+          :disabledCitySelect="false"
+          :useProvinceCitySplit="true"
+          v-model="vipSelectedProvince"
+          :before-change="areaExceedLimit"
+          ref="areaThreeSidebar"
+        ></AreaCitySidebar>
+        <div slot="footer">
+          <div class="j-button-group height40">
+            <button class="j-button-cancel" @click="popupReset('area')">
+              重置
+            </button>
+            <button class="j-button-confirm" @click="popupConfirm('area')">
+              确定
+            </button>
+          </div>
+        </div>
+      </PopupLayout>
+    </van-popup>
+  </div>
+</template>
+
+<script>
+import { mapGetters, mapActions } from 'vuex'
+import { Sticky, Steps, Step, Popup, Field } from 'vant'
+import { CheckboxGroup } from '@/ui'
+import { onlyProvinceList } from '@/data/selector'
+import PopupLayout from '@/components/common/PopupLayout'
+import AreaCitySidebar from '@/components/selector/area-three-sidebar/index'
+import RecommendedWords from '@/components/subscribe/RecommendedWords'
+import { mixinHeader } from '@/utils/mixins/header'
+import { pushHistoryState } from '@/utils/mixins/pushState'
+import {
+  getUserSubscribeSomeInfo,
+  saveSubscribeGuide,
+  getMySelectEntInfo,
+  getUserRule
+} from '@/api/modules'
+import { openAppOrWxPage, openLinkOfOther } from '@/utils'
+import { LINKS } from '@/data'
+export default {
+  name: 'SubscribeGuide',
+  mixins: [mixinHeader, pushHistoryState],
+  components: {
+    [Sticky.name]: Sticky,
+    [Steps.name]: Steps,
+    [Step.name]: Step,
+    [Popup.name]: Popup,
+    [Field.name]: Field,
+    CheckboxGroup,
+    PopupLayout,
+    AreaCitySidebar,
+    RecommendedWords
+  },
+  data() {
+    return {
+      active: 0,
+      steps: [
+        { title: '选择关注地区' },
+        { title: '设置主营产品' },
+        { title: '查看商机' }
+      ],
+      checkboxConfig: {
+        multiple: false,
+        icon: true
+      },
+      expand: false,
+      freeSelectedProvince: [],
+      vipSelectedProvince: -1,
+      popup: {
+        area: false
+      },
+      someInfo: {
+        areaCount: 0,
+        area: {},
+        district: {},
+        key: []
+      },
+      flatAreaResult: [],
+      app_switch: false,
+      canSetArea: true,
+      pageLayoutConf: {
+        onClickLeft: this.onClickLeft
+      }
+    }
+  },
+  computed: {
+    ...mapGetters('user', [
+      'isFree',
+      'restfulApiUserTypeWitchVSwitch',
+      'vSwitch'
+    ]),
+    provinceList() {
+      return this.expand ? onlyProvinceList : onlyProvinceList.slice(0, 12)
+    },
+    nextDisabled() {
+      return (
+        this.active === 0 &&
+        (this.freeSelectedProvince.length || this.vipSelectedProvince !== -1)
+      )
+    },
+    finishDisabled() {
+      return (
+        this.active === 1 &&
+        this.someInfo.key.length > 0 &&
+        this.flatAreaResult.length > 0
+      )
+    },
+    // 可选地区
+    optionalAreaText() {
+      const { areaCount } = this.someInfo
+      if (areaCount === -1) {
+        return '全国,'
+      } else {
+        return `<i class="highlight-text">${areaCount}</i>个省,`
+      }
+    },
+    // 已选地区
+    selectedAreaText() {
+      const vipSelectedProvince = this.vipSelectedProvince
+      if (!vipSelectedProvince || vipSelectedProvince === -1) {
+        return '<i class="highlight-text">0</i>个省'
+      } else {
+        if (Object.keys(vipSelectedProvince).length === 0) {
+          return '全国'
+        } else {
+          return `<i class="highlight-text">${
+            Object.keys(vipSelectedProvince).length
+          }</i>个省`
+        }
+      }
+    },
+    keyMaxLength() {
+      // 免费和省份订阅包用户为10,超级订阅、大会员、商机管理为300
+      const vSwitch = this.vSwitch
+      if (vSwitch === 's' || vSwitch === 'm' || vSwitch === 'v') {
+        return 300
+      } else {
+        return 10
+      }
+    }
+  },
+  async created() {
+    await this.userVipSwitchState()
+    this.getUserSubscribeSomeInfo()
+    this.getAppSwitchStatus()
+    await this.isCanEditArea()
+    this.pushHistoryState()
+  },
+  methods: {
+    ...mapActions('user', ['userVipSwitchState']),
+    popStateEvent() {
+      this.$router.replace('/tabbar/home')
+    },
+    onClickLeft() {
+      this.$router.replace('/tabbar/home')
+    },
+    // 获取用户订阅信息
+    async getUserSubscribeSomeInfo() {
+      try {
+        const {
+          data = {},
+          error_code: code = 0,
+          error_msg: msg
+        } = await getUserSubscribeSomeInfo(this.restfulApiUserTypeWitchVSwitch)
+        if (code === 0 && data) {
+          const { subsetinfo: subInfo } = data
+          this.someInfo.areaCount = subInfo?.areacount
+          this.someInfo.area = subInfo?.area
+          this.someInfo.district = subInfo?.district
+          this.someInfo.key = subInfo?.key || []
+          // 免费用户且不是省份订阅包用户(areacount=1是免费;大于1是省份订阅包)
+          if (this.isFree && subInfo?.areacount === 1) {
+            this.freeSelectedProvince = subInfo?.area
+              ? Object.keys(JSON.parse(subInfo?.area))
+              : []
+            this.flatAreaResult = this.freeSelectedProvince
+          } else {
+            // 付费、省份订阅包用户
+            const area = subInfo?.area ? JSON.parse(subInfo?.area) : ''
+            const district = subInfo?.district
+              ? JSON.parse(subInfo?.district)
+              : ''
+            this.vipSelectedProvince = this.formatRegionLevel2ToLevel3(
+              area,
+              district
+            )
+            this.getFlatAreaResult(this.vipSelectedProvince)
+          }
+        } else {
+          console.error(msg)
+        }
+      } catch (error) {
+        console.error(error)
+      } finally {
+      }
+    },
+    // 企业分发是否可以修改地区
+    async isCanEditArea() {
+      const { data } = await getMySelectEntInfo()
+      const { vip_power, member_power, user_power } = data
+      if (vip_power === 1 || member_power === 1 || user_power === 1) {
+        const { data: noSet } = await getUserRule()
+        if (noSet) {
+          this.canSetArea = false
+        } else {
+          this.canSetArea = true
+        }
+      }
+    },
+    onExpandArea() {
+      this.expand = true
+    },
+    nextHandle() {
+      if (!this.nextDisabled) return this.$toast('请选择关注地区')
+      const isFreeEmpty = this.isFree && this.freeSelectedProvince.length === 0
+      const vipFreeEmpty = !this.isFree && !this.vipSelectedProvince
+      if (this.active === 0) {
+        if (isFreeEmpty || vipFreeEmpty) {
+          this.$toast('请选择关注地区')
+          return
+        }
+      }
+      this.active = 1
+      if (this.flatAreaResult.length > 0) {
+        this.calcRegionTagStyle()
+      }
+    },
+    onChangeCheckbox(val) {
+      this.flatAreaResult = val
+    },
+    getFlatAreaResult() {
+      const areaMap = this.vipSelectedProvince
+      const result = []
+      if (areaMap === -1) {
+        this.flatAreaResult = []
+      } else {
+        if (Object.keys(areaMap).length) {
+          for (const key in areaMap) {
+            if (Object.keys(areaMap[key]).length === 0) {
+              result.push(key)
+            } else {
+              const cityMap = areaMap[key]
+              for (const cKey in cityMap) {
+                if (Object.keys(cityMap[cKey]).length === 0) {
+                  result.push(cKey)
+                } else {
+                  result.push(...cityMap[cKey])
+                }
+              }
+            }
+          }
+        } else {
+          Object.keys(areaMap).length === 0 ? result.push('全国') : ''
+        }
+        this.flatAreaResult = result
+      }
+    },
+    onChangeArea() {
+      if (!this.canSetArea) {
+        return this.$dialog
+          .alert({
+            title: '不支持修改区域',
+            message: '如需修改区域,请联系企业管理员修改分发设置区域。',
+            className: 'j-confirm-dialog',
+            confirmButtonText: '我知道了'
+          })
+          .then(() => {
+            // on close
+          })
+      }
+      this.popup.area = true
+    },
+    popupReset() {
+      this.vipSelectedProvince = -1
+    },
+    popupConfirm() {
+      this.popup.area = false
+      this.getFlatAreaResult()
+    },
+    // 超出省份提示
+    areaExceedLimit(parent, child) {
+      const { areaCount } = this.someInfo
+      const selectedCount =
+        this.vipSelectedProvince !== -1
+          ? Object.keys(this.vipSelectedProvince).length
+          : 0
+      // 全国(无限制)
+      if (areaCount < 0) {
+        return true
+      } else {
+        // 非全国(省份有限制)
+        if (parent.parentName && parent.parentName === '全国') {
+          this.$toast(`超出可选省份数量,\n可选${areaCount}个省`)
+          return false
+        } else {
+          if (selectedCount >= areaCount) {
+            // 已选择过的省份列表
+            const selectedProvinceList = []
+            const sourceFirstCount =
+              this.$refs.areaThreeSidebar.sourceFirstCount || []
+            for (const key in sourceFirstCount) {
+              selectedProvinceList.push(key)
+            }
+            // 已经选中过的可勾选掉
+            if (
+              parent.parentName &&
+              selectedProvinceList.indexOf(parent.parentName) > -1
+            ) {
+              return true
+            } else {
+              this.$toast(`超出可选省份数量,\n可选${areaCount}个省`)
+              return false
+            }
+          } else {
+            return true
+          }
+        }
+      }
+    },
+    // 获取app总开关状态
+    getAppSwitchStatus() {
+      if (this.$env.platform === 'app') {
+        if (
+          JyObj.checkNoticePermission() === 1 ||
+          JyObj.checkNoticePermission() === '1'
+        ) {
+          this.app_switch = true
+        }
+      }
+    },
+    onEditArea() {
+      this.active = 0
+    },
+    // 设置更多条件
+    setMore() {
+      this.saveSubscribeGuide(() => {
+        if (this.vSwitch === 's') {
+          openLinkOfOther(
+            '/page_entniche_new/page/subsetting/sub_entrance.html',
+            { type: 'replace' }
+          )
+        } else if (this.vSwitch === 'v' || this.vSwitch === 'm') {
+          // 超级订阅/大会员
+          openAppOrWxPage(LINKS.订阅管理页面, {
+            query: {
+              vSwitch: this.vSwitch
+            },
+            type: 'replace'
+          })
+        } else {
+          openAppOrWxPage(LINKS.订阅管理页面, { type: 'replace' })
+        }
+      })
+    },
+    // 完成
+    onFinish() {
+      this.saveSubscribeGuide(() => {
+        this.$router.replace('/tabbar/subscribe')
+      })
+    },
+    // 完成订阅
+    async saveSubscribeGuide(callback) {
+      if (!this.finishDisabled) {
+        return this.$toast('请先设置主营产品')
+      }
+      const areParams =
+        this.vSwitch === 'f'
+          ? this.formatArrayToMap(this.freeSelectedProvince)
+          : this.formatRegionLevel3ToLevel2(this.vipSelectedProvince)
+      const { area, district } = areParams
+      const params = {
+        area: area ? JSON.stringify(area) : '',
+        district: district ? JSON.stringify(district) : '',
+        keywords: this.someInfo.key,
+        app_switch: this.app_switch
+      }
+      const loading = this.$toast.loading({
+        message: '订阅中...',
+        forbidClick: true
+      })
+      const { error_code: code, error_msg: msg } = await saveSubscribeGuide(
+        this.restfulApiUserTypeWitchVSwitch,
+        params
+      )
+      if (code === 0) {
+        window.history.replaceState(
+          '',
+          '剑鱼标讯官网 - 全国招标采购大数据平台',
+          '/jy_mobile/tabbar/home'
+        )
+        loading.clear()
+        setTimeout(() => {
+          callback && callback()
+        }, 200)
+      } else {
+        this.$toast(msg.replace(/[^\u4E00-\u9FA5]/g, ''))
+      }
+    },
+    // 动态隐藏已修改地区超出一行的元素
+    calcRegionTagStyle() {
+      const parent = document.querySelector(
+        '.already-selected .selected-province'
+      )
+      // parent.classList.add('no-wrap')
+      this.$nextTick(() => {
+        const position = document.querySelector('.more-placeholder')
+        const { left: pLeft } = position.getBoundingClientRect()
+        const allTags = parent.querySelectorAll('.selected-province-item')
+        position.classList.remove('opacity0')
+        const hiddenTags = []
+        allTags.forEach((v) => {
+          v.classList.remove('hidden')
+          const { right } = v.getBoundingClientRect()
+          if (right > pLeft) {
+            v.classList.add('hidden')
+            hiddenTags.push(true)
+          }
+        })
+        const hasHidden = hiddenTags.includes(true)
+        // 如果没用隐藏元素即为没有超出一行 则隐藏...占位元素
+        if (!hasHidden) {
+          position.classList.add('opacity0')
+        }
+      })
+    },
+    formatArrayToMap(array) {
+      if (!array || array.length === 0) return
+      const areaStr = array.toString()
+      const obj = {}
+      obj[areaStr] = []
+      return {
+        area: obj,
+        district: undefined
+      }
+    },
+    formatRegionLevel3ToLevel2(regionMap) {
+      if (regionMap === -1 || !regionMap) return
+      let area = {}
+      const district = {}
+      if (Object.keys(regionMap).length === 0) {
+        area = {}
+      } else {
+        for (const key in regionMap) {
+          if (Object.keys(regionMap[key]).length === 0) {
+            area[key] = []
+          } else {
+            const cities = regionMap[key]
+            const cityArr = []
+            for (const city in cities) {
+              cityArr.push(city)
+              area[key] = cityArr
+              if (cities[city].length > 0) {
+                district[city] = cities[city]
+              }
+            }
+          }
+        }
+      }
+      return { area, district }
+    },
+    formatRegionLevel2ToLevel3(area, district) {
+      if (!area) return -1
+      // 原二级数据结构为{福建:['福州', '厦门', '宁德']}
+      // 现数据结构为三级 需进行处理
+      if (Object.keys(area).length > 0) {
+        const obj = {}
+        for (const province in area) {
+          let cityMap = {}
+          if (Array.isArray(area[province])) {
+            area[province].forEach((city) => {
+              for (const d in district) {
+                if (d === city) {
+                  cityMap[city] = district[d]
+                } else {
+                  cityMap[city] = []
+                }
+              }
+            })
+          } else {
+            cityMap = area[province]
+          }
+          obj[province] = cityMap
+        }
+        return obj
+      } else {
+        return area
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.area-result {
+  padding: 0 16px 8px;
+  font-size: 14px;
+  line-height: 24px;
+  color: #5f5e64;
+}
+</style>
+<style lang="scss" scoped>
+.subscribe-guide {
+  .hidden {
+    display: none !important;
+  }
+  .no-wrap {
+    white-space: nowrap;
+  }
+  .opacity0 {
+    opacity: 0;
+  }
+  .step-container {
+    padding: 12px 64px;
+    background: #fff;
+  }
+  .module-title {
+    font-size: 14px;
+    line-height: 24px;
+    color: #171826;
+  }
+  .module-note {
+    font-size: 12px;
+    line-height: 24px;
+    color: #5f5e64;
+  }
+  .module-action {
+    flex-shrink: 0;
+    color: $color_main;
+    font-size: 14px;
+    line-height: 20px;
+  }
+  .region-container {
+    margin: 12px;
+    padding: 12px;
+    background: #fff;
+    border-radius: 8px;
+    .region-header {
+      padding-bottom: 6px;
+    }
+    .other-province {
+      margin-top: 6px;
+      color: $color_main;
+    }
+    .j-button-choose {
+      height: 36px;
+      margin-top: 6px;
+      color: $color_main;
+      border: 1px solid $color_main;
+      background: transparent;
+      border-radius: 4px;
+      font-size: 14px;
+    }
+
+    ::v-deep {
+      .jy-checkbox-group .jy-checkbox {
+        width: calc((100% - 24px) / 3);
+        margin-right: 12px;
+        text-align: center;
+        &:nth-child(3n) {
+          margin-right: 0;
+        }
+      }
+    }
+  }
+  .keywords-container {
+    .already-selected {
+      margin: 12px;
+      padding: 12px;
+      background: #fff;
+      border-radius: 8px;
+    }
+    .keyword-input {
+      margin-top: 12px;
+    }
+    .more-placeholder {
+      position: absolute;
+      right: 0;
+      top: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 28px;
+      height: 28px;
+      background: linear-gradient(#e1fdff, #deefff00);
+      border: 1px solid rgba(0, 0, 0, 0.05);
+      color: #5f5e64;
+      font-size: 14px;
+      line-height: 20px;
+      transform: translateY(-50%);
+    }
+  }
+  .selected-province-item {
+    display: inline-block;
+    padding: 4px 8px;
+    margin: 6px 12px 6px 0;
+    font-size: 14px;
+    line-height: 20px;
+    color: #5f5e64;
+    border-radius: 2px;
+    background: linear-gradient(#e1fdff, #deefff00);
+    border: 1px solid rgba(0, 0, 0, 0.05);
+  }
+
+  ::v-deep {
+    .van-steps {
+      overflow: unset;
+    }
+    .van-steps--horizontal {
+      padding: 5px 0;
+    }
+    .van-steps__items {
+      padding-bottom: 12px;
+    }
+    .van-step__title {
+      position: absolute;
+      top: 10px;
+      white-space: nowrap;
+      line-height: 18px;
+    }
+    .van-step__line {
+      top: 0;
+    }
+    .van-step__circle-container {
+      top: 0;
+      left: unset;
+      padding: 0 2px;
+    }
+    .van-step__circle {
+      width: 10px;
+      height: 10px;
+      background: #e6e6e6;
+    }
+    .van-step--horizontal:first-child .van-step__title {
+      left: -28px;
+    }
+    .van-step:last-child .van-step__title {
+      right: -24px;
+    }
+    .van-step__icon--active,
+    .van-step__title--active,
+    .van-step__icon--finish,
+    .van-step__title--finish,
+    .van-step--finish {
+      color: $color_main;
+    }
+    .van-step--finish .van-step__circle,
+    .van-step--finish .van-step__line {
+      background: $color_main;
+    }
+    .van-step__icon--active {
+      width: 10px;
+      height: 10px;
+      background: $color_main;
+      border-radius: 50%;
+    }
+    .van-icon-checked {
+      &::before {
+        content: '';
+      }
+    }
+  }
+}
+</style>

+ 418 - 56
apps/mobile/src/views/tabbar/Subscribe.vue

@@ -85,33 +85,74 @@
           />
         </template>
       </DropFilter>
-      <div v-if="tabSwitchShow" class="tab-switch bg-white border-line-b">
-        <van-tabs
-          v-model="listTabActive"
-          class="tab-left"
-          :ellipsis="false"
-          :before-change="beforeTabActiveChange"
-          @change="onListTabChange"
-        >
-          <van-tab
-            v-for="(tab, index) in conf.tabList"
-            :key="index"
-            :name="tab.name"
-            :title="tab.title"
-          />
-        </van-tabs>
-        <div class="tab-right">
-          <div
-            v-if="listState.count > 0"
-            class="data-export clickable"
-            @click="dataExport"
+      <div v-if="tabSwitchShow && listState.count > 0" class="tab-switch">
+        <div class="tab-switch-container">
+          <div class="flex flex-items-center tab-left">
+            <div class="tab-search-info">
+              <p v-if="listState.count > 0" class="search-total-count">
+                搜索到<span class="highlight-text">
+                  {{ calcListTotalText(listState.count) }} </span
+                >条信息
+              </p>
+            </div>
+            <div
+              v-if="listState.count > 0"
+              class="data-export clickable"
+              @click="dataExport"
+            >
+              <AppIcon name="shujudaochu_xiao1" />
+              <span class="text">导出</span>
+            </div>
+          </div>
+          <!-- <van-tabs
+            v-model="listTabActive"
+            class="tab-left"
+            :ellipsis="false"
+            :before-change="beforeTabActiveChange"
+            @change="onListTabChange"
           >
-            <AppIcon name="shujudaochu_xiao1" />
-            <span class="text">数据导出</span>
+            <van-tab
+              v-for="(tab, index) in conf.tabList"
+              :key="index"
+              :name="tab.name"
+              :title="tab.title"
+            />
+          </van-tabs> -->
+          <div class="tab-right">
+            <van-popover
+              v-model="showPopover"
+              trigger="click"
+              :actions="conf.tabList"
+              :offset="[6, 16]"
+              placement="bottom-end"
+              get-container=".tab-switch-container"
+            >
+              <div class="tab-column">
+                <div
+                  v-for="(tab, index) in conf.tabList"
+                  :key="index"
+                  class="tab-column-item"
+                  :class="{ active: listTabActive === tab.name }"
+                  :name="tab.name"
+                  @click.stop="onListTabClick(tab.name)"
+                >
+                  {{ tab.title }}
+                </div>
+              </div>
+              <template #reference>
+                <div class="selected-tab-content">
+                  <span>{{ listTabActiveText }}</span>
+                  <van-icon name="arrow-down" color="#2ABED1" />
+                </div>
+              </template>
+            </van-popover>
           </div>
         </div>
       </div>
-      <div class="">
+      <div
+        class="recommend-card-container"
+        :style="{ opacity: cardLoaded ? 'unset' : '0' }"
+      >
         <!-- 超前项目推荐 & 定制化分析报告 -->
         <RecommendCard
           v-if="recommendInfo.show"
@@ -131,6 +172,9 @@
       class="j-main subscribe-list"
       @scroll="scrollWrapFn"
     >
+      <div v-if="preloadTipText" class="text-center preload-tip">
+        {{ preloadTipText }}
+      </div>
       <van-list
         v-show="listTabActive === 'list' || listTabActive === 'detailedList'"
         ref="vanList"
@@ -143,13 +187,13 @@
         :class="{ 'calc-height-1px': !hasMorePage }"
         @load="getList"
       >
-        <div class="tab-search-info">
+        <!-- <div class="tab-search-info">
           <p v-if="listState.count > 0" class="search-total-count">
             搜索到<span class="highlight-text">
               {{ calcListTotalText(listState.count) }} </span
             >条信息
           </p>
-        </div>
+        </div> -->
         <div class="list-wrapper">
           <van-popup
             v-model="showBidStatus"
@@ -268,7 +312,8 @@
         </div>
         <AppEmpty
           v-show="listState.list.length === 0 && listState.finished"
-          state="sleep"
+          state="back"
+          style="background: transparent"
         >
           <!-- 有订阅管理权限的 -->
           <div v-if="showSubscribeManageButton">
@@ -288,7 +333,9 @@
             <!-- 有关键词未有数据 -->
             <template v-else>
               <div class="empty-text">
-                对不起,没有匹配到数据,请修改订阅设置
+                <!-- 对不起,没有匹配到数据,请修改订阅设置 -->
+                未匹配到您关注的商机(最近15天发布)<br />
+                建议您完善订阅关键词
               </div>
               <van-button
                 class="to-keyset-page-button mt-24"
@@ -296,7 +343,7 @@
                 type="primary"
                 @click="toSubManagePage"
               >
-                <span class="text">前往订阅设置</span>
+                <span class="text">去修改</span>
               </van-button>
             </template>
           </div>
@@ -462,7 +509,7 @@
         v-if="vipNoticeBarShow"
         theme="red"
         :right-text="noticeBar.vipRightText"
-        @clickLeft="noticeBar.vipShow = false"
+        @clickLeft="onVipNoticeClickLeft"
         @clickRight="toBuyVip"
       >
         {{ noticeBar.vipContentText }}
@@ -496,12 +543,50 @@
     />
     <PopupDataexport ref="popup_dataExport" @next="next_export" />
     <CheckUserDialog />
+    <!-- 推送提醒提示 -->
+    <NoticeBar
+      v-if="noticeBar.pushSetShow"
+      class="push-notice-bar"
+      right-text="去设置"
+      @clickLeft="noticeBar.pushSetShow = false"
+      @clickRight="toPushSetPage"
+    >
+      <p class="push-set-tips">
+        新增商机可通过邮件、微信、APP进行推送提醒,为保证您能及时接收信息,请进行推送设置。
+      </p>
+    </NoticeBar>
+    <!-- 订阅引导 -->
+    <van-overlay :show="showGuide" @click="showGuide = false">
+      <div class="subscribe-guide">
+        <div class="guide-tips">
+          <div v-if="$envs.inApp" class="tabbar-header-top-placeholder" />
+          <div class="tips-button">订阅管理</div>
+          <div class="tips-popover">
+            <div class="tips-popover-content">
+              <p class="tips-text">您可点击此处修改订阅条件</p>
+              <span class="know-btn" @click="onGuideKnow">我知道了</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </van-overlay>
   </div>
 </template>
 
 <script>
 import { mapActions, mapGetters, mapState } from 'vuex'
-import { Badge, Button, List, Popup, Tab, Tabs, Tag } from 'vant'
+import {
+  Badge,
+  Button,
+  List,
+  Popup,
+  Tab,
+  Tabs,
+  Tag,
+  Overlay,
+  Popover,
+  Icon
+} from 'vant'
 import dayjs from 'dayjs'
 import qs from 'qs'
 import { throttle } from 'lodash'
@@ -557,7 +642,9 @@ import {
   getUserSubscribeSomeInfo,
   selectEnt,
   setSubscribePageAreaPackTipClose,
-  setUserSubscribeListVisited
+  setUserSubscribeListVisited,
+  ajaxGetTipInfo,
+  getPushSet
 } from '@/api/modules'
 import { leadGetDate } from '@/api/modules/leadGeneration'
 
@@ -584,6 +671,9 @@ export default {
     [Tab.name]: Tab,
     [Tag.name]: Tag,
     [Popup.name]: Popup,
+    [Overlay.name]: Overlay,
+    [Popover.name]: Popover,
+    [Icon.name]: Icon,
     [AppIcon.name]: AppIcon,
     [Ad.name]: Ad,
     [AppEmpty.name]: AppEmpty,
@@ -619,12 +709,12 @@ export default {
         listTabActiveStorageKey: 'subscribe_listTabActive',
         tabList: [
           {
-            title: '精简列表',
+            title: '精简',
             cardType: 'simplify',
             name: 'list'
           },
           {
-            title: '详细列表',
+            title: '详细',
             name: 'detailedList',
             cardType: 'detailed',
             needPower: true
@@ -725,7 +815,8 @@ export default {
         settingKeywordsShow: true,
         vipShow: true,
         vipRightText: '',
-        vipContentText: ''
+        vipContentText: '',
+        pushSetShow: false
       },
       // 页面筛选器其他状态
       searchState: {
@@ -844,7 +935,18 @@ export default {
       mergedKeywords: [],
       alreadyLeave: false, // 免费用户是否已经留资. true为已留资,false为未留资或者留资信息不全
       scrollStatus: true,
-      showTutorial: false
+      showTutorial: false,
+      showGuide: false,
+      pushSwitch: {
+        wx: false,
+        mail: false,
+        app: false
+      },
+      showPopover: false,
+      // 预加载提醒文案
+      preloadTipText: '',
+      // 市场分析报告是否加载完成
+      cardLoaded: false
     }
   },
   computed: {
@@ -1076,6 +1178,19 @@ export default {
     },
     isHasKey() {
       return this.mergedKeywords.length > 0
+    },
+    listTabActiveText() {
+      // list/detailedList/table
+      const listTabActive = this.listTabActive
+      if (listTabActive === 'list') {
+        return '精简'
+      } else if (listTabActive === 'detailedList') {
+        return '详细'
+      } else if (listTabActive === 'table') {
+        return '表格'
+      } else {
+        return '精简'
+      }
     }
   },
   watch: {
@@ -1097,9 +1212,16 @@ export default {
       } else {
         this.$toast.clear()
       }
+      this.moveRecommendCard(n)
     },
     disabledFilters(newVal) {
       this.changeFiltersPower(newVal)
+    },
+    subscribeList(list) {
+      if (list && list.length && this.listTabActive === 'list') {
+        // 市场分析报告插到第一条数据后面位置
+        this.moveRecommendCard('list')
+      }
     }
   },
   async created() {
@@ -1145,8 +1267,6 @@ export default {
       }
       this.getMergedEntSubscribeKeywords()
     }
-    console.log(this.restfulApiUserTypeWitchVSwitch, 'created')
-    console.log(this.vSwitch)
   },
   mounted() {
     iosBackRefresh()
@@ -1154,7 +1274,6 @@ export default {
     this.initScrollEvents()
     this.appTabShow()
     this.getConfigurationApi()
-    console.log(this.restfulApiUserTypeWitchVSwitch, 'mounted')
   },
   methods: {
     formatMoney,
@@ -1275,6 +1394,7 @@ export default {
       this.initSubscribeType(this.subscribeTypeActive)
       await this.doSearch()
       await this.getMergedEntSubscribeKeywords()
+      await this.getPushSettingNotices()
       loading.clear()
     },
     // vSwitch身份切换
@@ -1304,6 +1424,7 @@ export default {
           this.pageState.someInfo.loadedSuccess = true
           this.calcVipNoticeBarInfo()
           this.checkGoToSubscribeGuide()
+          this.getPushSettingNotices()
         } else {
           console.error(msg)
         }
@@ -1802,6 +1923,10 @@ export default {
           } else {
             t.finished = true
           }
+
+          // 预加载提示语
+          this.preloadTipText = data?.subListTip || ''
+          this.linkToSubscribeGuide()
         } else {
           this.$toast(msg || '请求失败')
           t.loaded = true
@@ -2852,6 +2977,123 @@ export default {
       } else {
         console.warn(msg)
       }
+    },
+    // 已完成订阅条件设置(不进订阅向导)且我的订阅”APP、邮件、微信提醒都关闭时,展示此提醒
+    // 优先展示超级订阅续费提醒,点击×之后再展示推送设置提醒
+    async getPushSettingNotices() {
+      // 已完成订阅条件设置
+      const { isInTSguide } = this.pageState.someInfo
+      const { data, error_code: code, error_msg: msg } = await getPushSet()
+      if (code === 0 && data) {
+        const { o_subset } = data
+        this.pushSwitch.mail = !!o_subset?.i_mailpush
+        this.pushSwitch.app = !!o_subset?.i_apppush
+        this.pushSwitch.wx = !!o_subset?.i_wxpush
+        // console.log(this.vipNoticeBarShow, isInTSguide, this.pushSwitch)
+        // console.log(this.isPersonalSubscribe, this.subscribeTypeActive, '提醒')
+        if (
+          !this.vipNoticeBarShow &&
+          !isInTSguide &&
+          !this.pushSwitch.mail &&
+          !this.pushSwitch.app &&
+          !this.pushSwitch.wx &&
+          this.isPersonalSubscribe
+        ) {
+          this.noticeBar.pushSetShow = true
+        } else {
+          this.noticeBar.pushSetShow = false
+        }
+      } else {
+        console.warn(msg)
+      }
+    },
+    // 是否显示(设置)订阅向导
+    async getGuideInfo(know) {
+      try {
+        const params = {
+          tipName: 'subscribe_guide',
+          doType: know || ''
+        }
+        const { error_code: code, data } = await ajaxGetTipInfo(params)
+        if (code === 0) {
+          if (know && data === 1) {
+            this.showGuide = false
+          } else {
+            if (data === 0) {
+              this.showGuide = true && this.showSubscribeManageButton
+            } else {
+              this.showGuide = false
+            }
+          }
+        }
+      } catch (err) {
+        console.log(err)
+      }
+    },
+    onGuideKnow() {
+      this.getGuideInfo('know')
+      this.showGuide = false
+    },
+    toPushSetPage() {
+      this.$router.push('/push/pushsetting')
+    },
+    onVipNoticeClickLeft() {
+      this.noticeBar.vipShow = false
+      // 续费提醒关闭后再弹出订阅推送提醒
+      this.getPushSettingNotices()
+    },
+    onListTabClick(name) {
+      const isPass = this.beforeTabActiveChange(name)
+      if (isPass) {
+        this.listTabActive = name
+        this.onListTabChange(name)
+        this.showPopover = false
+      }
+    },
+    moveRecommendCard(n) {
+      if (n === 'table') {
+        // 市场分析报告插到表格头部位置
+        this.$nextTick(() => {
+          const recommend = document.querySelector('.recommend-card-container')
+          const scroll = document.querySelector('.table-container .scroll')
+          const recommendChild = document.querySelector('.recommend-bg')
+          if (recommend && scroll) {
+            const parentNode = scroll.parentNode
+            parentNode.insertBefore(recommend, scroll)
+            this.cardLoaded = true
+            if (recommendChild) {
+              recommendChild.style.margin = '0 8px'
+            }
+          }
+        })
+      } else {
+        // 市场分析报告插到第一条数据后面位置
+        this.$nextTick(() => {
+          const recommend = document.querySelector('.recommend-card-container')
+          const cellList = document.querySelectorAll('.project-cell')
+          const recommendChild = document.querySelector('.recommend-bg')
+          if (recommend && cellList[0]) {
+            const parentNode = cellList[0].parentNode
+            parentNode.insertBefore(recommend, cellList[0].nextSibling)
+            this.cardLoaded = true
+            if (recommendChild) {
+              recommendChild.style.margin = '8px 8px 0'
+            }
+          }
+        })
+      }
+    },
+    // 根据权限判断是否跳转订阅向导
+    linkToSubscribeGuide() {
+      // 进入“我的订阅(免费、超级订阅、大会员、商机管理)”:在该用户身份下,订阅列表无数据,且订阅向导未完成,则进入“订阅向导”页,否则进入“我的订阅”列表页
+      const noList = this.subscribeList.length === 0
+      const { isInTSguide } = this.pageState.someInfo
+      if (noList && isInTSguide) {
+        this.$router.push('/subscribe/guide')
+      } else {
+        // 已完成订阅向导再弹修改订阅条件弹框
+        this.getGuideInfo()
+      }
     }
   }
 }
@@ -2987,12 +3229,13 @@ export default {
 }
 
 .to-keyset-page-button {
+  width: 160px;
   margin-top: 48px;
   padding-left: 24px;
   padding-right: 24px;
 
   .text {
-    margin-left: 8px;
+    // margin-left: 8px;
   }
 
   .iconfont {
@@ -3020,8 +3263,8 @@ export default {
 .data-export {
   display: flex;
   align-items: center;
-  line-height: 30px;
-  font-size: 14px;
+  line-height: 16px;
+  font-size: 11px;
   color: $main;
 
   .text {
@@ -3031,13 +3274,22 @@ export default {
 }
 
 .tab-switch {
+  padding: 8px 11px;
+  background: #f5f4f9;
+}
+.tab-switch-container {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 2px 16px;
-  padding-left: 8px;
-
-  .tab-right {
+  padding: 8px;
+  border-radius: 8px;
+  border: 1px solid transparent;
+  background-clip: padding-box, border-box;
+  background-image: linear-gradient(#e8ffff 40%, #ffffff 100%),
+    linear-gradient(to right, #2abed1, #4de4f84c, #2abed1);
+  background-origin: padding-box, border-box;
+
+  .tab-left {
     display: flex;
     align-items: center;
   }
@@ -3056,6 +3308,38 @@ export default {
       padding-left: 0;
       padding-right: 0;
     }
+
+    .van-popover__arrow {
+      display: none;
+    }
+    .tab-column {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      width: 88px;
+      &-item {
+        width: 100%;
+        height: 44px;
+        font-size: 14px;
+        line-height: 44px;
+        text-align: center;
+        letter-spacing: 2px;
+        &:not(:last-child) {
+          border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+        }
+        &.active {
+          padding-right: 10px;
+          color: $color_main;
+          background: url(@/assets/image/icon/icon-check.png) no-repeat 58px
+            center;
+          background-size: 16px 16px;
+        }
+      }
+    }
+    .selected-tab-content {
+      font-size: 11px;
+      line-height: 16px;
+    }
   }
 }
 
@@ -3063,18 +3347,13 @@ export default {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 0 12px;
+  flex-shrink: 0;
 
   .search-total-count {
-    padding: 8px;
-    font-size: 14px;
-    color: #686868;
-    line-height: 22px;
-  }
-
-  .tab-right {
-    display: flex;
-    align-items: center;
+    margin-right: 8px;
+    font-size: 13px;
+    color: #171826;
+    line-height: 20px;
   }
 }
 
@@ -3088,7 +3367,7 @@ export default {
 
 .table {
   min-width: 1000px;
-  margin: 16px;
+  margin: 8px;
   font-size: 14px;
   border-collapse: collapse;
 
@@ -3182,4 +3461,87 @@ export default {
   background-size: contain;
   margin-top: -2px;
 }
+.preload-tip {
+  padding-bottom: 8px;
+  color: $main;
+  font-size: 12px;
+  line-height: 18px;
+}
+::v-deep.push-notice-bar {
+  .push-set-tips {
+    font-size: 11px;
+  }
+  .notice-bar-center {
+    margin: 0 8px;
+  }
+  .notice-bar-button {
+    padding: 6px 12px;
+  }
+}
+.recommend-card-container {
+  background: #f5f5f5;
+  ::v-deep {
+    .recommend-bg {
+      padding-bottom: 0;
+      border: 1px solid rgba(0, 0, 0, 0.05);
+      border-radius: 12px;
+      overflow: hidden;
+    }
+    .van-cell {
+      padding: 8px 12px;
+      background: linear-gradient(#ffffff 10%, #f5f5f5 100%);
+    }
+  }
+}
+.subscribe-guide {
+  display: flex;
+  justify-content: flex-end;
+  width: 100%;
+  height: 100%;
+  .guide-tips {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-end;
+    padding-right: 8px;
+    .tips-button {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 80px;
+      height: 44px;
+      background: #fff;
+      border-radius: 8px;
+      font-size: 14px;
+      line-height: 20px;
+    }
+    .tips-popover {
+      width: 203px;
+      height: 98px;
+      margin-top: 8px;
+      margin-right: 20px;
+      background: url(@/assets/image/guide/subscribe-guide.png) no-repeat center;
+      background-size: contain;
+      &-content {
+        padding-top: 26px;
+        text-align: center;
+        .tips-text {
+          color: #1d1d1d;
+          font-size: 12px;
+          line-height: 16px;
+        }
+        .know-btn {
+          display: inline-block;
+          width: 120px;
+          height: 32px;
+          margin-top: 10px;
+          font-size: 12px;
+          line-height: 32px;
+          background: $color_main;
+          color: #fff;
+          border-radius: 4px;
+        }
+      }
+    }
+  }
+}
 </style>