Browse Source

Merge branch 'main' into feature/v1.1.15

yuelujie 6 days ago
parent
commit
407614b704

+ 10 - 0
apps/bigmember_pc/src/api/modules/public.js

@@ -16,6 +16,16 @@ export function getFreeUserPushInfo() {
   })
 }
 
+// 获取帮免费用户订阅信息列表
+export function getHelpFreeUserKeyList(data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/publicapply/subscribe/freePositionList',
+    method: 'POST',
+    data
+  })
+}
+
 // 获取免费订阅信息接口
 export function getFreeUserSubscribeList() {
   return request({

+ 35 - 33
apps/bigmember_pc/src/api/modules/subscribe.js

@@ -1,10 +1,10 @@
-import request from '@/api'
 import qs from 'qs'
+import request from '@/api'
 
 // 获取大会员信息(订阅设置)
 export function getBigMemberInfo() {
   return request({
-    url: '/bigmember/use/info?t=' + Date.now(),
+    url: `/bigmember/use/info?t=${Date.now()}`,
     method: 'get'
   })
 }
@@ -12,7 +12,7 @@ export function getBigMemberInfo() {
 // 获取订阅关键词(市场分析报告
 export function getAnalysisrKeyInfo() {
   return request({
-    url: '/bigmember/marketAnalysis/analysisKeyWord?t=' + Date.now(),
+    url: `/bigmember/marketAnalysis/analysisKeyWord?t=${Date.now()}`,
     method: 'get'
   })
 }
@@ -23,7 +23,7 @@ export function updateKey(data) {
   return request({
     url: '/bigmember/subscribe/key/update',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -32,7 +32,7 @@ export function updateArea(data) {
   return request({
     url: '/bigmember/subscribe/area/update',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -41,7 +41,7 @@ export function updateInfoType(data) {
   return request({
     url: '/bigmember/subscribe/infotype/update',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -50,7 +50,7 @@ export function updateBuyClass(data) {
   return request({
     url: '/bigmember/subscribe/buyerclass/update',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -60,7 +60,7 @@ export function updateProjectMatch(data) {
   return request({
     url: '/bigmember/subscribe/projectmatch/update',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -70,7 +70,7 @@ export function updateOtherBuyClass(data) {
   return request({
     url: '/bigmember/subscribe/otherbuyclass/update',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -80,29 +80,29 @@ export function updateMatchType(data) {
   return request({
     url: '/bigmember/subscribe/matchtype/update',
     method: 'post',
-    data: data
+    data
   })
 }
 
 // 订阅地区、采购单位行业修改
 export function setAreaBuyer(data) {
-  data.vSwitch = 'm'
+  data.vSwitch = data.vSwitch || 'm'
   data = qs.stringify(data)
   return request({
     url: '/publicapply/subscribe/update',
     method: 'post',
-    data: data
+    data
   })
 }
 
 // 订阅修改(换成超级订阅)
 export function setUserInfoVip(data) {
-  data.vSwitch = 'm'
+  data.vSwitch = data.vSwitch || 'm'
   data = qs.stringify(data)
   return request({
     url: '/publicapply/subscribe/setUserInfo',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -112,18 +112,20 @@ export function getRecommend(data) {
   return request({
     url: '/member/getRecomKWs',
     method: 'post',
-    data: data
+    data
   })
 }
 
 // 关键词近30天推送数量查询
 export function getPushCount(data) {
-  data.vt = 'm'
+  if (data && !data.vt) {
+    data.vt = 'm'
+  }
   data = qs.stringify(data)
   return request({
     url: '/publicapply/subscribe/keyset/getPushCount',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -133,7 +135,7 @@ export function getPushSetDetail(data) {
   return request({
     url: '/bigmember/pushset/detail',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -142,7 +144,7 @@ export function updateVipSubscribe(data) {
   return request({
     url: '/jyapi/jybx/subscribe/vType/update',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -158,7 +160,7 @@ export function getViewStatus(vt, data) {
   return request({
     url: `/jyapi/jybx/subscribe/${vtMap[vt]}/viewStatus`,
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -167,7 +169,7 @@ export function getDistributor(vt, data) {
   return request({
     url: `/jyapi/jybx/subscribe/${vtMap[vt]}/distributor`,
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -176,7 +178,7 @@ export function getMsgDistributor(data) {
   return request({
     url: '/jyapi/jybx/subscribe/msgDistributor',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -185,7 +187,7 @@ export function ajaxSetDidDistributor(data) {
   return request({
     url: '/jyapi/jybx/subscribe/bidDistributor',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -194,7 +196,7 @@ export function getUserBindInfo(data) {
   return request({
     url: '/jyapi/jybx/subscribe/getUser',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -203,16 +205,16 @@ export function setUserBindInfo(data) {
   return request({
     url: '/jyapi/jybx/subscribe/setUser',
     method: 'post',
-    data: data
+    data
   })
 }
 
 // 用户推送设置信息获取
 export function getUserPushInfo(data) {
   return request({
-    url: '/jyapi/jybx/subscribe/getPushSet?t=' + Date.now(),
+    url: `/jyapi/jybx/subscribe/getPushSet?t=${Date.now()}`,
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -221,7 +223,7 @@ export function setUserPushInfo(data) {
   return request({
     url: '/jyapi/jybx/subscribe/setPushSet',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -230,7 +232,7 @@ export function getStaffSubscribeList(data) {
   return request({
     url: '/jyapi/jybx/subscribe/getStaffSubscribe',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -239,7 +241,7 @@ export function getStaffSubscribeDetail(data) {
   return request({
     url: '/jyapi/jybx/subscribe/getStaffSubscribeDetail',
     method: 'post',
-    data: data
+    data
   })
 }
 // 采购单位详情未登录右侧数据
@@ -247,7 +249,7 @@ export function relatesInformation(data) {
   return request({
     url: '/jyapi/jybx/buyer/relates/information',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -257,7 +259,7 @@ export function getRecommenListData(data) {
     baseURL: '/jyapi/jybx',
     url: '/subscribe/getRecList',
     method: 'post',
-    data: data
+    data
   })
 }
 
@@ -266,6 +268,6 @@ export function updateSubscribe(vType, data) {
   return request({
     url: `/jyapi/jybx/subscribe/${vType}/update`,
     method: 'post',
-    data: data
+    data
   })
 }

+ 75 - 0
apps/bigmember_pc/src/assets/js/selector.js

@@ -241,6 +241,73 @@ export const infoTypeListExp = (function () {
   return arr
 })()
 
+
+export const infoTypeListExpOfOneLevel = (function() {
+  return [
+    {
+      name: '全部',
+      value: '',
+      level: 0,
+      children: []
+    },
+    {
+      name: '拟建项目',
+      value: '拟建',
+      showHelp: true,
+      level: 1,
+      children: []
+    },
+    {
+      name: '采购意向',
+      value: '采购意向',
+      showHelp: true,
+      level: 1,
+      children: []
+    },
+    {
+      name: '招标预告',
+      value: '预告',
+      level: 1,
+      children: []
+    },
+    {
+      name: '招标公告',
+      value: '招标',
+      level: 1,
+      children: []
+    },
+    {
+      name: '招标结果',
+      value: '结果',
+      level: 1,
+      children: []
+    },
+    {
+      name: '其他信息',
+      value: '其它',
+      level: 1,
+      children: []
+    },
+  ]
+})()
+
+export function getOneLevelNamesByValues(valueArr) {
+  // 统一转成数组
+  const values = Array.isArray(valueArr) ? valueArr : [valueArr];
+
+  // 建立 value -> name 的映射表(只关心 level === 1 的项)
+  const valueToNameMap = infoTypeListExpOfOneLevel.reduce((map, item) => {
+    if (item.level === 1) {
+      map[item.value] = item.name;
+    }
+    return map;
+  }, {});
+
+  // 按顺序返回对应的 name
+  return values.map(v => valueToNameMap[v] || '').filter(v => v);
+}
+
+
 export const infoTypeListMapExp = {
   拟建项目: ['拟建项目'],
   采购意向: ['采购意向'],
@@ -723,6 +790,14 @@ export const pushFunctionData = [
 ]
 
 // 关键词匹配方式
+export const keywordMatchTypeListOfFree = [
+  {
+    value: 'title',
+    label: '标题',
+    auth: false
+  }
+]
+
 export const keywordMatchTypeList = [
   {
     value: 'title',

+ 20 - 2
apps/bigmember_pc/src/components/selector-cascader/AreaCityCountryCascader.vue

@@ -71,7 +71,7 @@
                 </ul>
               </div>
             </div>
-            <div class="module-container city-container">
+            <div class="module-container city-container" v-if="showCity">
               <header class="module-header">
                 <span :class="{ 'icon-have-vip': !vip }">城市</span>
               </header>
@@ -101,7 +101,10 @@
                 </ul>
               </div>
             </div>
-            <div class="module-container country-container">
+            <div
+              class="module-container country-container"
+              v-if="showCity && showCountry"
+            >
               <header class="module-header">
                 <span :class="{ 'icon-have-vip': !vip }">区县</span>
               </header>
@@ -191,6 +194,16 @@ export default {
       type: Boolean,
       default: false
     },
+    // 是否显示城市
+    showCity: {
+      type: Boolean,
+      default: true
+    },
+    // 是否显示城市下区县
+    showCountry: {
+      type: Boolean,
+      default: true
+    },
     // 是否展示选择标签tag
     showTags: {
       type: Boolean,
@@ -298,6 +311,11 @@ export default {
     this.setState(this.initMap)
   },
   methods: {
+    doReset() {
+      // 先重置之前的数据
+      this.initAreaMap()
+      this.setState(this.initMap)
+    },
     // select内置下拉方法
     toggleSelect() {
       this.$refs.selectRegion.toggleMenu()

+ 3 - 2
apps/bigmember_pc/src/components/selector/InfoTypeSelectorContent.vue

@@ -133,7 +133,7 @@
 
 <script>
 import { Popover } from 'element-ui'
-import { infoTypeListExp, infoTypeNotAdvancedList, infoTypeAdvancedList } from '@/assets/js/selector.js'
+import { infoTypeListExp, infoTypeNotAdvancedList, infoTypeAdvancedList, infoTypeListExpOfOneLevel } from '@/assets/js/selector.js'
 
 export default {
   name: 'info-type-selector-content',
@@ -179,7 +179,8 @@ export default {
       dataMap: {
         all: infoTypeListExp,
         base: infoTypeNotAdvancedList,
-        advance:  infoTypeAdvancedList
+        advance:  infoTypeAdvancedList,
+        oneLevel: infoTypeListExpOfOneLevel
       }
     }
   },

+ 9 - 0
apps/bigmember_pc/src/router/routers.js

@@ -128,6 +128,15 @@ export default [
     name: 'config',
     component: () => import('@/views/subscribe/Config.vue')
   },
+  {
+    path: '/free/help/set_subscribe',
+    name: 'config',
+    meta: {
+      title: '帮用户订阅(免费订阅)',
+      vt: 'f'
+    },
+    component: () => import('@/views/subscribe/FreeConfig.vue')
+  },
   // 采购单位画像-通用路由-seo
   {
     path: '/dw/:id.html', // seo数字id

+ 593 - 0
apps/bigmember_pc/src/views/subscribe/FreeConfig.vue

@@ -0,0 +1,593 @@
+<template>
+  <div class="free-config-setting-container">
+    <div class="config-title flex flex-(row items-center)">
+      <h1>帮用户订阅(免费订阅)</h1>
+      <div class="flex flex-(row items-center)">
+        <span>选择用户身份:</span>
+        <div class="select-selector-fix-height">
+          <select-selector
+            type="select"
+            :options="userKeyList"
+            v-model="checkUserKey"
+            @change="doChangeUserKey"
+            placeholder="请选择用户身份"
+          ></select-selector>
+        </div>
+      </div>
+    </div>
+
+    <div v-loading="pageLoading">
+      <!-- 订阅设置 -->
+      <free-sub-config
+        :datas="setData"
+        :can-select="setData.canSelectArea"
+        :setStatus="setStatus"
+        @update="getUpdate"
+      ></free-sub-config>
+      <!-- 关键词设置 -->
+      <free-key-config
+        id="setkey"
+        :datas="setData"
+        :setStatus="setStatus"
+        @update="getUpdate"
+      ></free-key-config>
+      <!-- 关键词列表 -->
+      <key-list
+        v-if="setData.keyList.length > 0"
+        ref="keyConfigRef"
+        matchTimeText="15天"
+        forceVt="f"
+        noSpaceWhenClickRecommend
+        :datas="setData"
+        :setStatus="setStatus"
+      ></key-list>
+    </div>
+
+    <el-dialog
+      custom-class="small-dialog"
+      title="关键词升级提示"
+      :visible.sync="dialogUpdate"
+      width="380px"
+      center
+      :show-close="false"
+    >
+      <p class="dialog-update-black">
+        “附加词”已整合至“关键词”中,添加多个关键词用空格隔开即可,并可以灵活选择匹配模式(精准或模糊)
+      </p>
+      <p class="dialog-update-gray">
+        示例说明:<br />
+        调整前:关键词:软件 <br />
+        <em style="opacity: 0">占位符:</em>附加词:系统
+        <br />调整后:关键词:软件 系统 <br />
+        <em style="opacity: 0">占位符:</em
+        >匹配模式:精准(同时包含所有关键词才推送)
+      </p>
+      <span slot="footer" class="dialog-footer">
+        <el-button class="know-btn" type="primary" @click="updateTipFn"
+          >我知道了</el-button
+        >
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { Dialog, Button } from 'element-ui'
+import FreeSubConfig from './components/key/FreeSubConfig'
+import FreeKeyConfig from './components/key/FreeKeyConfig'
+import KeyList from './components/key/List'
+import {
+  getFreeUserSubscribeList,
+  setUserInfoVip,
+  updateKey,
+  getHelpFreeUserKeyList
+} from '@/api/modules'
+import $bus from '@/utils/bus'
+import { mixinNoOpenSetMessage } from '@/utils/mixins/subscribe-setting-prompt.js'
+import { getOneLevelNamesByValues } from '@/assets/js/selector'
+import SelectSelector from '@/components/filter-items/SelectSelector.vue'
+export default {
+  name: 'free-config',
+  mixins: [mixinNoOpenSetMessage],
+  components: {
+    SelectSelector,
+    [Dialog.name]: Dialog,
+    [Button.name]: Button,
+    KeyList,
+    FreeSubConfig,
+    FreeKeyConfig
+  },
+  data() {
+    return {
+      pageLoading: true,
+      checkUserKey: '',
+      userKeyList: [],
+      getBigInfoCount: 0,
+      setData: {
+        title: '关键词设置',
+        // 区域字符串 渲染输入框
+        areaStr: '',
+        // 采购单位行业字符串 渲染输入框
+        buyClassStr: '',
+        // 信息类型字符串
+        infoTypeStr: '',
+        projectmatch: null,
+        buyclassmatch: null,
+        mathway: null, // 老字段(保留不用)
+        // 区域对象
+        areaObj: {},
+        areaCount: -1, // 大会员权限有几个省(大会员单省版使用) -1表示全国
+        // 采购单位行业数组
+        buyClassArr: [],
+        // 信息类型数组
+        infoTypeArr: [],
+        // 关键词
+        keyList: [],
+        maxCount: 0,
+        // 关键词匹配方式(新)
+        matchType: [],
+        amount: '',
+        canSelectArea: true
+      },
+      // 子账号业务范围
+      scope: [],
+      dialogUpdate: false,
+      // 订阅设置开关状态
+      setStatus: true
+    }
+  },
+  computed: {
+    canSubmanager() {
+      return this.$route.query.phone
+      // return !this.$store.state.user.info?.isSubCount
+    }
+  },
+  created() {
+    if (!this.canSubmanager) {
+      this.$alert('页面参数异常', '', {
+        confirmButtonText: '点击关闭页面',
+        confirmButtonColor: '#2ABDD1',
+        showClose: false,
+        center: true,
+        callback: (action) => {
+          history.back()
+        }
+      })
+    }
+  },
+  mounted() {
+    this.getHelpUserList()
+    $bus.$on('updateKey', (data) => {
+      // this.updateKeyWordsApi(data)
+      this.getBigInfo()
+    })
+    setTimeout(this.scrollToId, 100)
+  },
+  methods: {
+    getHelpUserList() {
+      getHelpFreeUserKeyList({
+        phone: this.$route.query.phone
+      })
+        .then((res) => {
+          if (res.data && Array.isArray(res.data) && res.data.length > 0) {
+            this.userKeyList = res.data.map((item) => {
+              if (item.checked) {
+                this.doChangeUserKey(item.token)
+              }
+              return {
+                label: item.name,
+                value: item.token
+              }
+            })
+            if (!this.checkUserKey) {
+              this.doChangeUserKey(this.userKeyList[0].value || '')
+            }
+          } else {
+            this.$alert(res.error_msg || '获取用户身份信息异常', '', {
+              confirmButtonText: '点击关闭页面',
+              confirmButtonColor: '#2ABDD1',
+              showClose: false,
+              center: true,
+              callback: (action) => {
+                history.back()
+              }
+            })
+          }
+        })
+        .catch((e) => {
+          this.$alert('获取用户身份信息异常', '', {
+            confirmButtonText: '点击关闭页面',
+            confirmButtonColor: '#2ABDD1',
+            showClose: false,
+            center: true,
+            callback: (action) => {
+              history.back()
+            }
+          })
+        })
+    },
+    doChangeUserKey(key = '') {
+      this.checkUserKey = key
+      this.$cookies.set('usrDewkYanV3', key)
+      this.getBigInfo()
+    },
+    async setSwitchCallback() {
+      // 调用api开启/关闭
+      const { flag } = await setUserInfoVip({
+        pageType: 'i_switch',
+        matchtype: this.setStatus ? 0 : 1 // 取反
+      })
+      if (flag) {
+        this.setStatus = !this.setStatus
+        this.$forceUpdate()
+      } else {
+        this.$message({
+          message: '修改失败',
+          type: 'error'
+        })
+      }
+    },
+    // 订阅设置-开关
+    onSetChange() {
+      if (this.setStatus) {
+        this.closeSetPrompt()
+      } else {
+        this.setSwitchCallback()
+      }
+    },
+    scrollToId() {
+      const { scroll } = this.$route.query
+      if (scroll === 'setkey') {
+        const headerH = $('#public-nav').height() || 0
+        const offset = $('#setkey').offset()
+        if (offset) {
+          $('body,html').animate({ scrollTop: offset.top + headerH })
+        }
+      }
+    },
+    parentGetCurEdit() {
+      const t = this.$refs.keyConfigRef.getCurEdit()
+      return t
+    },
+    // 排序
+    sortData(arr) {
+      return arr.sort((a, b) => {
+        return b.updatetime - a.updatetime
+      })
+    },
+    async getBigInfo() {
+      this.pageLoading = true
+      this.getBigInfoCount += 1
+      // const provinceArr = []
+      // let areaArr = []
+      const data = await getFreeUserSubscribeList()
+      this.pageLoading = false
+      const res = data.data
+      if (res) {
+        const editKeyList = [
+          {
+            a_key: res.keys || [],
+            s_item: '未分类'
+          }
+        ]
+        editKeyList.forEach((v, index) => {
+          if (v) {
+            v.groupIndex = index
+            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.keyindex = j
+                s.showForm = false
+                if (!s.updatetime) {
+                  s.updatetime = 0
+                }
+              })
+            }
+          }
+        })
+
+        this.setData.keyList = editKeyList
+        this.setData.areaStr = '请选择区域'
+        this.setData.areaObj = false
+        this.setData.infoTypeStr = '全部类型'
+        this.setData.matchType = ['title']
+
+        const areaInfo = {
+          list: [],
+          regionMap: {}
+        }
+
+        function getMaxSelectCount() {
+          var baseNum = res.basenum
+          var provincenum = res.provincenum
+          if (provincenum === -1) {
+            return provincenum
+          } else {
+            return provincenum + baseNum
+          }
+        }
+
+        if (res.area) {
+          for (const areaInfoKey in res.area) {
+            areaInfo.list.push(areaInfoKey)
+            areaInfo.regionMap[areaInfoKey] = res.area[areaInfoKey]
+          }
+
+          this.setData.areaStr = areaInfo.list.join('、')
+          this.setData.areaObj = areaInfo.regionMap
+        }
+
+        const maxSelectCount = getMaxSelectCount()
+        this.setData.areaCount = maxSelectCount
+        this.setData.canSelectArea =
+          maxSelectCount === -1 ? true : maxSelectCount > 0
+
+        if (res.infotype && res.infotype.length) {
+          // 免费用户格式不一样,需要转换
+          this.setData.infoTypeArr = res.infotype
+          this.setData.infoTypeStr = getOneLevelNamesByValues(
+            res.infotype
+          ).join('、')
+        }
+
+        this.setData.maxCount = 10
+
+        if (!Array.isArray(res.keys) || res.keys.length === 0) {
+          // setUserInfoVip({
+          //   pageType: 'keyWords',
+          //   actionType: 'SC',
+          //   classify_name: '未分类',
+          //   vSwitch: 'f'
+          // }).then((res) => {
+          //   this.getBigInfo()
+          // })
+        }
+      } else {
+        console.log('状态值为:' + res.status)
+      }
+    },
+    // 子组件通知父组件更新关键词接口(大会员为全量提交,关键词修改使用超级订阅关键词接口提交)
+    updateKeyWordsApi(data) {
+      updateKey({
+        a_items: data
+      }).then((res) => {
+        if (res.data.status === 1) {
+          this.$message({
+            type: 'success',
+            message: '操作成功'
+          })
+          this.getBigInfo()
+        } else {
+          this.$message({
+            type: 'error',
+            message: res.error_msg || '操作失败'
+          })
+          this.getBigInfo()
+        }
+      })
+    },
+    // 除地区、采购单位以外设置修改接口
+    setUserInfoFn(data) {
+      setUserInfoVip({
+        pageType: data
+      }).then((res) => {
+        if (res.data.status === 1) {
+          this.$message({
+            type: 'success',
+            message: '操作成功'
+          })
+          this.getBigInfo()
+        } else {
+          this.$message({
+            type: 'error',
+            message: res.error_msg || '操作失败'
+          })
+          this.getBigInfo()
+        }
+      })
+    },
+    getUpdate() {
+      this.getBigInfo()
+    },
+    // 关闭升级提示按钮
+    updateTipFn() {
+      setUserInfoVip({
+        pageType: 'keytip'
+      }).then((res) => {
+        this.dialogUpdate = false
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.buyer-dialog {
+  border-radius: 8px !important;
+}
+.sub-dialog {
+  border-radius: 5px;
+}
+.small-dialog {
+  border-radius: 8px !important;
+  .el-dialog__title {
+    color: #1d1d1d;
+  }
+  .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;
+  }
+}
+.free-config-setting-container {
+  width: 1080px;
+  margin: 32px auto 56px;
+  padding: 32px 30px;
+  background: #fff;
+  border-radius: 5px;
+  box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.08);
+
+  .select-selector-fix-height {
+    min-width: 200px;
+    .filter-layout.filter-input-layout,
+    .filter-layout.filter-input-layout .el-popper,
+    .el-dropdown {
+      width: 100%;
+    }
+    .filter-layout.filter-input-layout .el-popper {
+      margin-top: 12px;
+    }
+  }
+
+  .config-title {
+    padding-bottom: 12px;
+    font-size: 18px;
+    color: #1d1d1d;
+    line-height: 28px;
+    border-bottom: 1px solid #ececec;
+
+    span {
+      margin-left: 16px;
+      font-size: 14px;
+    }
+  }
+
+  &-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 {
+    display: inline-block;
+    margin-left: 22px;
+    font-size: 14px;
+    color: #2abed1;
+    line-height: 22px;
+  }
+  .reset-tips {
+    width: 100%;
+    height: 32px;
+    line-height: 32px;
+    color: #ff9f40;
+    font-size: 13px;
+    background: rgb(255, 159, 64, 0.1);
+    text-align: center;
+  }
+  .save-btn {
+    display: block;
+    width: 180px;
+    height: 46px;
+    margin: 20px auto;
+    line-height: 46px;
+    color: #fff;
+    background: #2cb7ca;
+    border-radius: 6px;
+    text-align: center;
+    cursor: pointer;
+    border: 0;
+  }
+  .sub-dialog .el-dialog__header,
+  .sub-dialog .el-dialog__body {
+    padding: 0;
+  }
+  .buyer-dialog {
+    .el-dialog__header {
+      padding: 20px 20px 10px;
+    }
+    .el-dialog__body {
+      padding: 25px 25px 30px;
+    }
+  }
+  .prevent-loading {
+    opacity: 0;
+  }
+  .reset-disabled-switch {
+    margin-left: 9px;
+    &.is-disabled {
+      opacity: 1;
+      .el-switch__core,
+      .el-switch__label {
+        cursor: pointer;
+      }
+    }
+  }
+  .el-switch {
+    position: relative;
+  }
+
+  .el-switch__label--left {
+    position: absolute;
+    left: 6px;
+    top: 0;
+    color: #fff !important;
+    z-index: 9;
+    font-size: 12px;
+  }
+
+  .el-switch__label--right {
+    position: absolute;
+    right: 6px;
+    top: 0;
+    color: #fff !important;
+    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;
+  }
+
+  .classify-title {
+    .icon-edit,
+    .icon-delete {
+      display: none;
+    }
+  }
+
+  .edit-form {
+    .is-matchway-item {
+      display: none;
+    }
+  }
+}
+</style>

+ 44 - 10
apps/bigmember_pc/src/views/subscribe/components/key/Edit.vue

@@ -9,7 +9,12 @@
             type="textarea"
             autosize
             resize="none"
-            placeholder="请输入关键词,多个关键词用空格隔开,例如:税务局 软件"
+            :placeholder="
+              '请输入关键词' +
+              (isFreeSubCustom
+                ? ''
+                : ',多个关键词用空格隔开,例如:税务局 软件')
+            "
             debounce="600"
             maxlength="200"
             @input="keywordsInput"
@@ -46,7 +51,7 @@
           </div>
         </div>
       </div>
-      <div class="item">
+      <div class="item is-matchway-item">
         <div class="item-label">匹配模式:</div>
         <div class="item-value">
           <el-radio-group
@@ -99,7 +104,7 @@
           @click="submitKeywords"
         >
           保存<span style="font-size: 12px">
-            (近3个月内共匹配{{ pushCount }}条信息)</span
+            (近{{ matchTimeText }}内共匹配{{ pushCount }}条信息)</span
           >
         </button>
         <button type="button" class="cancle-btn" @click="cancelEdit">
@@ -158,6 +163,15 @@ export default {
     className: '',
     classIndex: Number,
     keyIndex: Number,
+    forceVt: String,
+    matchTimeText: {
+      type: String,
+      default: '3个月'
+    },
+    noSpaceWhenClickRecommend: {
+      type: Boolean,
+      default: false
+    },
     // 订阅设置开关
     setStatus: {
       type: Boolean,
@@ -197,6 +211,16 @@ export default {
     // 匹配信息过少提示
     littleTip() {
       return this.pushCount < 30 && this.cur.key !== ''
+    },
+    addSplitter() {
+      if (this.noSpaceWhenClickRecommend) {
+        return ''
+      } else {
+        return ' '
+      }
+    },
+    isFreeSubCustom() {
+      return this.$route.meta?.vt === 'f'
     }
   },
   watch: {
@@ -240,6 +264,9 @@ export default {
     },
     // 关键词推荐数量查询
     keywordsInput() {
+      if (this.isFreeSubCustom) {
+        this.cur.key = this.cur.key.replace(/\s/g, '')
+      }
       if (this.cur.key) {
         setTimeout(() => {
           this.getRecommendFn()
@@ -251,6 +278,9 @@ export default {
     },
     // 关键词输入框失去焦点 查询推送数量
     keywordsBlur() {
+      if (this.isFreeSubCustom) {
+        this.cur.key = this.cur.key.replace(/\s/g, '')
+      }
       if (this.cur.key) {
         this.getPushCountFn()
       }
@@ -264,11 +294,15 @@ export default {
     // 关键词近3个月推送数量查询
     getPushCountFn: debounce(function () {
       if (!this.cur.key) return
-      getPushCount({
+      const params = {
         key: this.cur.key,
         notkey: this.cur.notkey,
         matchway: this.cur.matchway
-      }).then((res) => {
+      }
+      if (this.forceVt) {
+        params.vt = this.forceVt
+      }
+      getPushCount(params).then((res) => {
         this.pushCount = res.count
       })
     }, 500),
@@ -302,13 +336,13 @@ export default {
     // 添加订阅推荐到输入框
     addSameItem(e, item) {
       item.selected = !item.selected
-      const m = this.cur.key.split(' ')
+      const m = this.cur.key.split(this.addSplitter)
       if (item.selected) {
         m.push(item.word)
-        this.cur.key = m.join(' ')
+        this.cur.key = m.join(this.addSplitter)
       } else {
         m.remove(item.word)
-        this.cur.key = m.join(' ')
+        this.cur.key = m.join(this.addSplitter)
       }
       setTimeout(() => {
         this.getPushCountFn()
@@ -340,8 +374,8 @@ export default {
         kws_index: keyIndex, // 关键词索引
         kws_name: this.cur.key, // 关键词名称
         not_kws: this.getNotKeyArr(), // 排除词
-        match_way: this.cur.matchway, // 关键词匹配
-        vSwitch: 'm'
+        match_way: this.isFreeSubCustom ? 0 : this.cur.matchway, // 关键词匹配
+        vSwitch: this.$route.meta?.vt || 'm'
       }
       // 用jq axios数组参数无法序列化
       $.ajax({

+ 314 - 0
apps/bigmember_pc/src/views/subscribe/components/key/FreeKeyConfig.vue

@@ -0,0 +1,314 @@
+<template>
+  <div class="free-keywords-container">
+    <div class="key-title">
+      <div>
+        <span>{{ datas.title }}</span>
+        <span style="font-size: 14px"
+          ><em style="color: #2cb7ca"> {{ keyCounts }}</em
+          >/{{ datas.maxCount }}</span
+        >
+        <span style="font-size: 14px; color: #2cb7ca; margin-left: 16px"
+          >注:任意1组关键词组匹配成功即推送相关信息</span
+        >
+        <img
+          @click="showSetKeyDialog = true"
+          src="@/assets/images/icon/help.png"
+          class="help-img"
+        />
+      </div>
+<!--      <div class="add-classfily" @click="addClassfilyFn()" v-loading="!setStatus" element-loading-custom-class="prevent-loading">-->
+<!--        <i class="el-icon-plus"></i> 新增分类-->
+<!--      </div>-->
+    </div>
+    <common-dialog
+      width="500px"
+      :visible="showSetKeyDialog"
+      :show-footer="false"
+    >
+      <img
+        @click="showSetKeyDialog = false"
+        class="setkey-img"
+        src="@/assets/images/setkey-dialog.png"
+        alt=""
+      />
+    </common-dialog>
+    <div class="key-content">
+      <!-- <div v-if="datas.keyList.length === 0">
+        <div class="classify-title">
+          <span class="title-text">{{defaultClass}}</span>
+        </div>
+        <Edit
+          title="新增"
+          :datas="datas.keyList"
+          :className="defaultClass"
+          :classIndex="0"
+          :keyIndex="0"
+        ></Edit>
+      </div> -->
+    </div>
+    <!-- 修改分类dialog -->
+    <el-dialog
+      custom-class="sub-dialog"
+      :visible.sync="add.dialog"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="800px"
+      v-if="add.dialog"
+    >
+      <KeyCard @onCancel="add.dialog = false" @onConfirm="confirmEditClassFn">
+        <div slot="header">新增关键词分类</div>
+        <div class="class-edit-content">
+          <div class="item">
+            <div class="item-label">关键词分类:</div>
+            <div class="item-value">
+              <el-input
+                class="custom-long-input"
+                v-model.trim="add.className"
+                maxlength="20"
+                placeholder="请输入关键词分类"
+              ></el-input>
+            </div>
+          </div>
+        </div>
+      </KeyCard>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { Input, Button, Dialog, RadioGroup, Radio } from 'element-ui'
+import KeyCard from '@/components/selector/SelectorCard'
+import { setUserInfoVip } from '@/api/modules'
+import commonDialog from '@/components/dialog/Dialog'
+export default {
+  name: 'key-config',
+  props: {
+    datas: {
+      title: String,
+      keyList: Array,
+      maxCount: Number
+    },
+    // 订阅设置开关
+    setStatus: {
+      type: Boolean,
+      default: false
+    }
+  },
+  components: {
+    [Input.name]: Input,
+    [Dialog.name]: Dialog,
+    [Button.name]: Button,
+    [RadioGroup.name]: RadioGroup,
+    [Radio.name]: Radio,
+    KeyCard,
+    commonDialog
+    // Edit
+  },
+  data() {
+    return {
+      add: {
+        dialog: false,
+        className: ''
+      },
+      showSetKeyDialog: false
+    }
+  },
+  computed: {
+    keyCounts() {
+      let count = 0
+      this.datas.keyList.forEach((v) => {
+        if (v && v.a_key) {
+          count += v.a_key.length
+        }
+      })
+      return count
+    }
+  },
+  mounted() {},
+  methods: {
+    // 新增关键词分类弹框
+    addClassfilyFn() {
+      const t = this.$parent.parentGetCurEdit()
+      console.log(t)
+      if (t) return this.$toast('请先保存或取消正在操作的关键词组')
+      this.add.className = ''
+      this.add.dialog = true
+    },
+    // 确认添加分类
+    confirmEditClassFn() {
+      const classArr = this.getClassArray()
+      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 (
+        this.datas.keyList.length === 1 &&
+        this.datas.keyList[0].s_item === '未分类'
+      ) {
+        setUserInfoVip({
+          pageType: 'keyWords',
+          classify_index: 0,
+          classify_name: this.add.className,
+          actionType: 'SC'
+        }).then((res) => {
+          if (res.flag) {
+            this.$emit('update')
+            this.add.dialog = false
+          }
+        })
+        return
+      }
+      setUserInfoVip({
+        pageType: 'keyWords',
+        actionType: 'SC',
+        classify_name: this.add.className
+      }).then((res) => {
+        if (res.flag) {
+          this.$emit('update')
+        } else {
+          this.$message({
+            message: '新增分类失败',
+            type: 'error'
+          })
+        }
+      })
+      this.add.dialog = false
+    },
+    // 获取所有分类名
+    getClassArray() {
+      const data = JSON.parse(JSON.stringify(this.datas.keyList))
+      const classArr = []
+      data.forEach((v) => {
+        if (v.s_item) {
+          classArr.push(v.s_item)
+        }
+      })
+      return classArr
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.free-keywords-container {
+  .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;
+    .help-img {
+      display: inline-block;
+      width: 18px;
+      height: 18px;
+      margin: 0 7px 0 8px;
+      cursor: pointer;
+    }
+    .add-classfily {
+      width: 110px;
+      height: 38px;
+      line-height: 38px;
+      border: 1px solid #2cb7ca;
+      border-radius: 6px;
+      text-align: center;
+      color: #2cb7ca;
+      font-size: 14px;
+      cursor: pointer;
+    }
+  }
+  .key-content {
+    padding: 20px 0;
+    .classify-title {
+      display: flex;
+      align-items: center;
+      .title-text {
+        font-size: 16px;
+        color: #1d1d1d;
+      }
+      .icon-edit,
+      .icon-delete {
+        display: inline-block;
+        width: 16px;
+        height: 16px;
+        background-repeat: no-repeat;
+        background-position: center center;
+        cursor: pointer;
+        &::before {
+          content: '' !important;
+        }
+      }
+      .icon-edit {
+        margin: 0 10px;
+        background-image: url('~@/assets/images/icon-edit.png');
+        background-size: contain;
+      }
+      .icon-delete {
+        background-image: url('~@/assets/images/icon-delete.png');
+        background-size: contain;
+      }
+    }
+  }
+  .selector-card {
+    width: 800px;
+    height: auto;
+  }
+  .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;
+    }
+  }
+  ::v-deep {
+    .el-dialog__header {
+      display: none;
+    }
+    .el-dialog__body {
+      padding: 0;
+    }
+  }
+}
+.setkey-img {
+  position: absolute;
+  cursor: pointer;
+  top: calc(100% + 100px);
+}
+</style>

+ 735 - 0
apps/bigmember_pc/src/views/subscribe/components/key/FreeSubConfig.vue

@@ -0,0 +1,735 @@
+<template>
+  <div class="free-subscribe-container">
+    <div class="sub-content">
+      <div class="item">
+        <div class="item-label item-label-required">区域:</div>
+        <div
+          class="item-value"
+          v-loading="!setStatus"
+          element-loading-custom-class="prevent-loading"
+        >
+          <AreaCityCountryCascader
+            ref="areaCityCountry"
+            showSelected
+            showCount
+            stopDropdown
+            :showCity="false"
+            :showCountry="false"
+            :areaCount="maxSelectAreaCount"
+            :initMap="areaData"
+            @hideSelect="onAreaHideSelect"
+            @onClick="onAreaSelectClick"
+            @change="onAreaCityCountryChange"
+            @exceed="onExceedChange"
+          >
+            <div slot="header"></div>
+          </AreaCityCountryCascader>
+        </div>
+      </div>
+
+      <div class="item">
+        <div class="item-label item-label-required">信息类型:</div>
+        <div
+          class="item-value"
+          @click="dialog.infoType = true"
+          v-loading="!setStatus"
+          element-loading-custom-class="prevent-loading"
+        >
+          <el-input
+            class="custom-long-input"
+            v-model="datas.infoTypeStr"
+            readonly
+            unselectable="on"
+          ></el-input>
+        </div>
+      </div>
+
+      <div class="item">
+        <div class="item-label">关键词匹配方式:</div>
+        <div
+          class="item-value"
+          v-loading="!setStatus"
+          element-loading-custom-class="prevent-loading"
+        >
+          <CheckboxGroupSelector
+            :sourceList="keywordMatchTypeList"
+            keepOne
+            @change="onMatchTypeChange"
+            v-model="datas.matchType"
+          ></CheckboxGroupSelector>
+        </div>
+      </div>
+    </div>
+    <!-- 区域选择dialog -->
+    <el-dialog
+      custom-class="sub-dialog"
+      :visible.sync="dialog.area"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+    >
+      <AreaSelect
+        :initCityMap="datas.areaObj"
+        @onCancel="dialog.area = false"
+        @onConfirm="saveAreaData"
+        :showSelectResult="true"
+        :showSelectedDetail="false"
+        :areaCount="maxSelectAreaCount"
+      >
+        <template #update-tips
+          >超出已购买省份数量,请联系客户成功经理进行升级</template
+        >
+      </AreaSelect>
+    </el-dialog>
+    <!-- 采购单位行业dialog -->
+    <el-dialog
+      custom-class="sub-dialog"
+      :visible.sync="dialog.buyClass"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+    >
+      <BuyClassSelect
+        :initCate="datas.buyClassArr"
+        :otherMatch="datas.buyclassmatch"
+        @onCancel="dialog.buyClass = false"
+        @onConfirm="saveBuyClassData"
+        @onOtherConfirm="saveOtherBuyClass"
+      >
+        <div slot="header">选择采购单位类型</div>
+      </BuyClassSelect>
+    </el-dialog>
+    <!-- 信息类型dialog -->
+    <el-dialog
+      custom-class="sub-dialog info-type-dialog"
+      :visible.sync="dialog.infoType"
+      :close-on-click-modal="false"
+      :show-close="false"
+      center
+      width="460px"
+    >
+      <InfoTypeSelect
+        :initInfoType="datas.infoTypeArr"
+        showDataType="oneLevel"
+        @onCancel="dialog.infoType = false"
+        @onConfirm="saveInfoTypeData"
+      >
+        <div slot="header">选择信息类型</div>
+      </InfoTypeSelect>
+    </el-dialog>
+    <el-dialog
+      :visible.sync="dialog.areaMap"
+      title="超出可选省份数量"
+      :show-close="false"
+      class="tip-dialog"
+      width="380px"
+      top="30vh"
+      center
+    >
+      <div>
+        可选:<span class="highlight-text">{{ maxSelectAreaCount }}个省</span
+        >,如需增加省份数量,您可联系客服升级大会员。
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click.stop="goConcatCustomer"
+          >联系客服</el-button
+        >
+        <el-button @click="dialog.areaMap = false">取 消</el-button>
+      </div>
+    </el-dialog>
+    <popupBox
+      ref="popupBox"
+      text="如需修改区域,请联系企业管理员修改分发设置区域。"
+      title="不支持修改区域"
+    ></popupBox>
+  </div>
+</template>
+<script>
+import {
+  Switch,
+  Input,
+  RadioGroup,
+  Radio,
+  Button,
+  Dialog,
+  Checkbox
+} from 'element-ui'
+import AreaSelect from '@/components/selector/AreaSelector'
+import BuyClassSelect from '@/components/selector/BuyerclassSelector'
+import InfoTypeSelect from '@/components/selector/InfoTypeSelector'
+import popupBox from '@/components/common/popupBox'
+import AreaCityCountryCascader from '@/components/selector-cascader/AreaCityCountryCascader'
+import CheckboxGroupSelector from '@/components/selector/CheckboxGroupSelector'
+import { keywordMatchTypeListOfFree } from '@/assets/js/selector.js'
+import AmountRangeSelector from '@/components/selector/AmountRangeSelector.vue'
+import {
+  setAreaBuyer,
+  setUserInfoVip,
+  userRule,
+  entBaseInfo
+} from '@/api/modules'
+export default {
+  name: 'free-sub-config',
+  props: {
+    datas: {
+      areaStr: String,
+      buyClassStr: String,
+      infoTypeStr: String,
+      projectmatch: Number,
+      buyclassmatch: Number,
+      mathway: Number,
+      areaObj: Object,
+      areaCount: Number, // 用户购买了几个省份(单省版本)
+      buyClassArr: Array,
+      infoTypeArr: Array,
+      matchType: Array,
+      amount: String
+    },
+    // 订阅设置开关
+    setStatus: {
+      type: Boolean,
+      default: false
+    },
+    canSelect: {
+      type: Boolean,
+      default: true
+    }
+  },
+  components: {
+    [Switch.name]: Switch,
+    [Input.name]: Input,
+    [RadioGroup.name]: RadioGroup,
+    [Radio.name]: Radio,
+    [Button.name]: Button,
+    [Dialog.name]: Dialog,
+    AreaSelect,
+    BuyClassSelect,
+    InfoTypeSelect,
+    popupBox,
+    AreaCityCountryCascader,
+    CheckboxGroupSelector,
+    AmountRangeSelector
+  },
+  data() {
+    return {
+      projectSwitch: {
+        loading: false,
+        disabled: false
+      },
+      unitSwitch: {
+        loading: false,
+        disabled: false
+      },
+      dialog: {
+        area: false,
+        buyClass: false,
+        infoType: false,
+        areaMap: false
+      },
+      areaData: {},
+      buyerData: [],
+      timeout: null,
+      isSetArea: false,
+      keywordMatchTypeList: keywordMatchTypeListOfFree,
+      switchMode: this.setStatus
+    }
+  },
+  computed: {
+    // -1表示可选范围为全国
+    maxSelectAreaCount() {
+      const { areaCount } = this.datas
+      return areaCount || -1
+    },
+    formatAreaMap() {
+      const original = this.areaData
+      // if (!original) return ''
+      // 原二级数据结构为{福建:['福州', '厦门', '宁德']}
+      // 现数据结构为三级 需进行处理
+      if (Object.keys(original).length > 0) {
+        const obj = {}
+        for (const province in original) {
+          let cityMap = {}
+          if (Array.isArray(original[province])) {
+            original[province].forEach((city) => {
+              cityMap[city] = []
+            })
+          } else {
+            cityMap = original[province]
+          }
+          obj[province] = cityMap
+        }
+        return obj
+      } else {
+        return original
+      }
+    }
+  },
+  watch: {
+    'datas.areaObj'(newVal) {
+      this.areaData = newVal
+      try {
+        this.$refs.areaCityCountry.setState('')
+        this.$refs.areaCityCountry.setState(newVal)
+      } catch (error) {}
+    },
+    'datas.buyClassArr'(newVal) {
+      this.buyerData = newVal
+    }
+  },
+  mounted() {},
+  methods: {
+    onAmountRangeChange(amount) {
+      const { value } = amount
+      this.commonSetApi({
+        pageType: 'amount',
+        matchtype: value
+      })
+    },
+    // 关键词匹配方式选择结果回调(新)
+    onMatchTypeChange(data) {
+      this.commonSetApi({
+        pageType: 'matchmode',
+        matchtype: data.toString()
+      })
+    },
+    debounce(fn, delay) {
+      if (this.timeout) {
+        clearTimeout(this.timeout)
+      }
+      this.timeout = setTimeout(() => {
+        fn()
+      }, delay)
+    },
+    clickErea() {
+      this.debounce(() => {
+        entBaseInfo({}).then((res) => {
+          if (
+            res.data.vip_power === 1 ||
+            res.data.member_power === 1 ||
+            res.data.user_power === 1
+          ) {
+            // 企业授权
+            userRule().then((res_) => {
+              if (res_.data) {
+                // 设置了区域
+                this.$refs.popupBox.isshow = true
+              } else {
+                this.dialog.area = true
+              }
+            })
+          } else {
+            // 非企业授权
+            this.dialog.area = true
+          }
+        })
+      }, 200)
+    },
+    // 切换项目匹配按钮
+    switchProjectMatch(event) {
+      this.projectSwitch.loading = true
+      this.projectSwitch.disabled = true
+      this.commonSetApi(
+        {
+          pageType: 'projectMatch',
+          pmindex: event
+        },
+        () => {
+          this.projectSwitch.loading = false
+          this.projectSwitch.disabled = false
+          this.$message({
+            message: event === 1 ? '项目匹配已开启' : '项目匹配已关闭',
+            type: 'success'
+          })
+        }
+      )
+    },
+    // 原关键词匹配方式(弃用)
+    async chooseMathWay(state) {
+      this.commonSetApi({
+        pageType: 'saveSeniorset',
+        matchtype: state
+      })
+    },
+    // 保存区域修改数据(已停用)
+    saveAreaData(data) {
+      setAreaBuyer({
+        vSwitch: 'm',
+        area: JSON.stringify(data),
+        industry: this.buyerData.toString()
+      }).then((res) => {
+        if (res.data.doSuccess) {
+          this.$emit('update')
+          this.dialog.area = false
+        } else {
+          this.$message({
+            message: '修改失败',
+            type: 'error'
+          })
+        }
+      })
+    },
+    // 保存采购单位行业数据及匹配未分类行业数据
+    saveBuyClassData(data) {
+      const { area, district } =
+        this.$refs.areaCityCountry.formatProvinceAndCities(this.areaData)
+      setAreaBuyer({
+        vSwitch: 'm',
+        // area: JSON.stringify(this.areaData),
+        area: JSON.stringify(area),
+        district: JSON.stringify(district),
+        industry: data.toString()
+      }).then((res) => {
+        if (res.data.doSuccess) {
+          this.$emit('update')
+          this.dialog.buyClass = false
+        } else {
+          this.$message({
+            message: '修改失败',
+            type: 'error'
+          })
+        }
+      })
+    },
+    // 保存未匹配采购单位行业
+    saveOtherBuyClass(data) {
+      this.commonSetApi({
+        pageType: 'other_buyerclass',
+        other: data.selected ? 1 : 0
+      })
+    },
+    // 保存信息类型数据
+    saveInfoTypeData(data) {
+      this.commonSetApi(
+        {
+          pageType: 'infoType',
+          infoTypeArr: data.toString(),
+          vSwitch: 'f'
+        },
+        () => {
+          this.dialog.infoType = false
+        }
+      )
+    },
+    // 设置接口
+    commonSetApi(data, callback) {
+      setUserInfoVip(data).then((res) => {
+        if (res.flag) {
+          this.$emit('update')
+          callback && callback()
+        } else {
+          this.$message({
+            message: '修改失败',
+            type: 'error'
+          })
+        }
+      })
+    },
+    // 组件选择地区回调事件(场景:每次选择都需保存)
+    onAreaCityCountryChange(data) {
+      const { area, district } = data
+      console.log(area, district)
+    },
+    async onAreaSelectClick() {
+      if (!this.canSelect) {
+        return this.$message({
+          message: '修改区域次数已达上限',
+          type: 'error'
+        })
+      }
+      this.$refs.areaCityCountry.toggleSelect()
+      return false
+      const { data } = await entBaseInfo()
+      if (
+        data.vip_power === 1 ||
+        data.member_power === 1 ||
+        data.user_power === 1
+      ) {
+        // 企业授权
+        const { data: flag } = await userRule()
+        if (flag) {
+          this.$refs.popupBox.isshow = true
+        } else {
+          this.$refs.areaCityCountry.toggleSelect()
+        }
+      } else {
+        this.$refs.areaCityCountry.toggleSelect()
+      }
+    },
+    // 当下拉框隐藏时回调事件(应用场景:每次选择地区时不需要都调用接口,等所有选择完 下拉框隐藏时再调用)
+    onAreaHideSelect(data) {
+      const { area, district } = data
+      if (!data.resultText) {
+        this.$refs.areaCityCountry.doReset()
+        return false
+      }
+      setAreaBuyer({
+        vSwitch: 'f',
+        area: JSON.stringify(area),
+        industry: this.buyerData.toString(),
+        district: JSON.stringify(district)
+      }).then((res) => {
+        if (res?.data?.doSuccess) {
+          this.$emit('update')
+          this.dialog.area = false
+        } else {
+          if (res.errMsg && res.errMsg.indexOf('已用完') > -1) {
+            this.$refs.areaCityCountry.doReset()
+          }
+          this.$message({
+            message: res.errMsg || '修改失败',
+            type: 'error'
+          })
+        }
+      })
+    },
+    // 区域选择超出冒泡事件
+    onExceedChange() {
+      this.$message({
+        message: '超出可订阅省份数量',
+        type: 'error'
+      })
+      // this.dialog.areaMap = true
+    },
+    goConcatCustomer() {
+      this.contactCustomer(this)
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.free-subscribe-container {
+  .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 {
+    position: relative;
+    width: 352px;
+    .item-mask {
+      position: absolute;
+      top: 0;
+      left: 0;
+      bottom: 0;
+      right: 0;
+      z-index: 10000;
+    }
+  }
+
+  .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;
+    }
+    .select-region {
+      left: 0 !important;
+    }
+    .s-header {
+      display: none;
+    }
+    .el-select {
+      width: 100%;
+      .el-input__inner {
+        height: 40px;
+        line-height: 40px;
+      }
+      .select-prefix {
+        height: 38px;
+        line-height: 38px;
+      }
+    }
+    .tip-dialog {
+      .el-button--primary,
+      .el-button--primary:hover,
+      .el-button--primary:focus {
+        width: 132px;
+        height: 36px;
+        margin-right: 52px;
+        text-align: center;
+        background: #2cb7ca;
+        border-radius: 6px;
+        font-style: 16px;
+        color: #fff;
+        border: 0;
+      }
+      .el-dialog {
+        border-radius: 8px;
+      }
+      .el-dialog__header {
+        padding: 32px 0 0;
+      }
+      .el-dialog__body {
+        padding: 20px 32px 32px;
+        color: #686868;
+        font-size: 14px;
+        line-height: 22px;
+        text-align: center;
+      }
+      .el-dialog__body i {
+        color: #2cb7ca;
+      }
+      .el-button {
+        width: 132px;
+        height: 36px;
+        padding: 0;
+        text-align: center;
+        font-size: 16px;
+      }
+      .el-dialog__footer {
+        padding: 0 0 32px;
+      }
+    }
+    .el-select-dropdown.select-custom {
+      left: 0 !important;
+    }
+
+    .module-container.province-container {
+      width: 100%;
+      .el-icon-arrow-right {
+        display: none;
+      }
+    }
+  }
+}
+</style>

+ 34 - 8
apps/bigmember_pc/src/views/subscribe/components/key/List.vue

@@ -12,7 +12,11 @@
           @click="goThisTop(index)"
           :data-diy-sticky-mapping="'sticky-' + index"
         >
-          <div class="flex-r-c" v-loading="!setStatus" element-loading-custom-class="prevent-loading">
+          <div
+            class="flex-r-c"
+            v-loading="!setStatus"
+            element-loading-custom-class="prevent-loading"
+          >
             <span class="title-text">{{ item.s_item }}</span>
             <span
               class="icon-edit"
@@ -23,7 +27,11 @@
               @click="deleteClassFn(item, item.groupIndex)"
             ></span>
           </div>
-          <div class="flex-r-c right" v-loading="!setStatus" element-loading-custom-class="prevent-loading">
+          <div
+            class="flex-r-c right"
+            v-loading="!setStatus"
+            element-loading-custom-class="prevent-loading"
+          >
             <el-button
               type="primary"
               class="add-classfily"
@@ -54,7 +62,11 @@
         class="classify-title flex-r-c sb"
         :data-diy-sticky-origin="'sticky-' + index"
       >
-        <div class="flex-r-c" v-loading="!setStatus" element-loading-custom-class="prevent-loading">
+        <div
+          class="flex-r-c"
+          v-loading="!setStatus"
+          element-loading-custom-class="prevent-loading"
+        >
           <span class="title-text">{{ item.s_item }}</span>
           <span
             class="icon-edit"
@@ -82,7 +94,8 @@
           <div
             class="add-words-box"
             @click="addNewKeyword(item, item.groupIndex)"
-            v-loading="!setStatus" element-loading-custom-class="prevent-loading"
+            v-loading="!setStatus"
+            element-loading-custom-class="prevent-loading"
             v-if="!item.showForm"
             key="add"
           >
@@ -93,6 +106,9 @@
               :datas="newWordsList"
               :className="item.s_item"
               title="新增"
+              :matchTimeText="matchTimeText"
+              :forceVt="forceVt"
+              :noSpaceWhenClickRecommend="noSpaceWhenClickRecommend"
               :classIndex="item.groupIndex"
               :keyIndex="item.a_key.length"
               :setStatus="setStatus"
@@ -128,7 +144,11 @@
                   排除词: {{ v.notkey.join(' ') }}
                 </p>
               </div>
-              <div class="list-right" v-loading="!setStatus" element-loading-custom-class="prevent-loading">
+              <div
+                class="list-right"
+                v-loading="!setStatus"
+                element-loading-custom-class="prevent-loading"
+              >
                 <span
                   class="icon-edit"
                   @click="editKeyFn(item, v, item.groupIndex, v.keyindex)"
@@ -254,7 +274,9 @@
       center
       :show-close="false"
     >
-      <p class="dialog-text">您的关键词组数量已达{{datas.maxCount}}组,无法继续添加</p>
+      <p class="dialog-text">
+        您的关键词组数量已达{{ datas.maxCount }}组,无法继续添加
+      </p>
       <span slot="footer" class="dialog-footer">
         <el-button class="know-btn" type="primary" @click="dialog.limit = false"
           >我知道了</el-button
@@ -283,6 +305,9 @@ export default {
     [CollapseTransition.name]: CollapseTransition
   },
   props: {
+    matchTimeText: String,
+    forceVt: String,
+    noSpaceWhenClickRecommend: Boolean,
     datas: {
       keyList: Array,
       maxCount: Number
@@ -539,7 +564,8 @@ export default {
       const datas = {
         pageType: 'keyWords',
         actionType: 'DK',
-        delete_key: JSON.stringify(obj)
+        delete_key: JSON.stringify(obj),
+        vSwitch: this.$route.meta?.vt
       }
       this.setInfoApi(datas, () => {
         this.dialog.delKey = false
@@ -699,7 +725,7 @@ export default {
         .el-button--default:hover,
         .el-button--default:active {
           color: #686868;
-          border-color: #E0E0E0;
+          border-color: #e0e0e0;
           background: #fff;
         }
       }

+ 9 - 2
apps/mobile/src/views/subscribe/Guide.vue

@@ -234,6 +234,9 @@ export default {
       'restfulApiUserTypeWitchVSwitch',
       'vSwitch'
     ]),
+    isAreaVip () {
+      return this.isFree && (this.someInfo.areaCount > 1 || this.someInfo.areaCount === -1)
+    },
     provinceList() {
       return this.expand ? onlyProvinceList : onlyProvinceList.slice(0, 12)
     },
@@ -370,7 +373,11 @@ export default {
     },
     nextHandle() {
       if (!this.nextDisabled) return this.$toast('请选择关注地区')
-      const isFreeEmpty = this.isFree && this.freeSelectedProvince.length === 0
+      let isFreeEmpty = this.isFree && this.freeSelectedProvince.length === 0
+      // 省份订阅包订阅向导问题修复
+      if (this.isAreaVip && this.vipSelectedProvince) {
+        isFreeEmpty = false
+      }
       const vipFreeEmpty = !this.isFree && !this.vipSelectedProvince
       if (this.active === 0) {
         if (isFreeEmpty || vipFreeEmpty) {
@@ -522,7 +529,7 @@ export default {
         return this.$toast('请先设置主营产品')
       }
       const areParams =
-        this.vSwitch === 'f'
+        (this.vSwitch === 'f' && !this.isAreaVip)
           ? this.formatArrayToMap(this.freeSelectedProvince)
           : this.formatRegionLevel3ToLevel2(this.vipSelectedProvince)
       const { area, district } = areParams

+ 1 - 1
apps/work-bench/src/sentry.js

@@ -5,7 +5,7 @@ export function initSentry(Vue) {
     Sentry.init({
       Vue,
       dsn: 'https://30ebbe89657141879efcfc55dc59803b@jysentry.jydev.jianyu360.cn/11',
-      release: 'v1.0.0',
+      release: 'v1.1.0',
       environment: 'produce',
       sampleRate: 1,
       beforeSend: (event) => {

+ 7 - 7
packages/work-bench-frame/packages/micro-frame/qiankun/store.js

@@ -83,7 +83,7 @@ export default {
               return new Response(new Blob(), { status: 200 });
             }
             console.error('Fetch failed for:', url, err);
-            sendErrorLog(new Error(`Qiankun Fetch failed`), {
+            sendErrorLog(new Error(`Qiankun LOADING_SOURCE Fetch failed`), {
               level: 'error',
               tags: {
                 href: location.href,
@@ -99,18 +99,18 @@ export default {
       })
       addGlobalUncaughtErrorHandler((event) => {
         const isSubAppLoadError = event.type === 'error' && event.message.indexOf('died in status LOADING_SOURCE_CODE') !== -1
-        sendErrorLog(new Error(`SubApp Load ERROR`), {
-          level: 'error',
-          tags: {
-            href: location.href
-          }
-        })
         commit('toggleLoading', false)
         if (isSubAppLoadError) {
           commit('toggleAbnormal', {
             type: true,
             message: state.tips.error
           })
+          sendErrorLog(new Error(`SubApp LOADING_SOURCE_CODE ERROR`), {
+            level: 'error',
+            tags: {
+              href: location.href
+            }
+          })
         }
       })
       dispatch('syncModel', state.sharedModel)