فهرست منبع

Merge branch 'dev/v1.0.53_tsz' of jianyu/web into feature/v1.0.53

汤世哲 1 سال پیش
والد
کامیت
316a53fabc

+ 177 - 88
apps/bigmember_pc/src/views/subscribe/SubPush.vue

@@ -55,19 +55,14 @@
           @onChange="changeTime"
           selectorType="line"
         >
-          <div 
-            class="filter-label"
-            :class="{ 'label-width': showVipTag }"
-            slot="header"
-          >
-            选择时间:
-            <span
+          <div class="filter-label" slot="header">
+            选择时间:<span
               class="el-icon-jy-vip visibility-hidden"
               v-if="showVipTag"
             ></span>
           </div>
         </TimeSelector>
-        <AreaSelector
+        <!-- <AreaSelector
           v-if="autoVt === 'f'"
           @onChange="changeArea"
           :beforeTabClick="beforeSelected"
@@ -152,8 +147,8 @@
           >
             信息类型:<span class="el-icon-jy-vip" v-if="showVipTag"></span>
           </div>
-        </InfoTypeSelector>
-        <PopSelector
+        </InfoTypeSelector> -->
+        <!-- <PopSelector
           @onChange="changeKeys"
           ref="keySelector"
           :beforeTabClick="beforeSelected"
@@ -166,8 +161,8 @@
           >
             关键词:<span class="el-icon-jy-vip" v-if="showVipTag"></span>
           </div>
-        </PopSelector>
-        <selectorCard cardType="line">
+        </PopSelector> -->
+        <!-- <selectorCard cardType="line">
           <div
             slot="header"
             class="s-header filter-label"
@@ -183,8 +178,8 @@
             :beforeChange="beforeSelected"
             @onChange="onFileStateChange"
           />
-        </selectorCard>
-        <selectorCard class="more-selector" cardType="line">
+        </selectorCard> -->
+        <!-- <selectorCard class="more-selector" cardType="line">
           <div
             slot="header"
             class="s-header filter-label"
@@ -199,13 +194,26 @@
             :beforeChange="beforeSelected"
             @getSelectStatus="getSelectStatus"
             @addUserPerson="addUserPerson"
+            @onKeywordChange="onKeywordChange"
             :person="showPerson"
+            :key-list="keyList"
             :show-vip-tag="showVipTag"
             :vt="vt"
             :info-source="infoSource"
             :view-status-list="viewStatusList"
           ></selectGroup>
-        </selectorCard>
+        </selectorCard> -->
+        <!--  更多筛选  -->
+        <search-schema-filter
+          v-model="filters"
+          :schema="moduleList"
+          :show-label="false"
+          :show-row-label="true"
+          style-type="row"
+          @change="doChangeFilter"
+        >
+          <span slot="row-label-text">更多筛选:</span>
+        </search-schema-filter>
       </div>
       <!-- 超前项目推荐&&市场分析报告 -->
       <div id="jyChartCom">
@@ -430,7 +438,6 @@
           </button>
         </template>
       </CustomDialog>
-      <CheckUserDialog />
       <FollowOfficialAccountDialog
         :visible.sync="dialog.toFollowOfficialAccount"
       ></FollowOfficialAccountDialog>
@@ -467,6 +474,8 @@
 
 <script>
 import { Button, Dialog, Drawer } from 'element-ui'
+import SearchSchemaFilter from '@/views/search/components/search-schema-filter.vue'
+import { subscribeMoreSchemaModule } from '@/views/subscribe/constant/search-filters'
 import CustomDialog from '@/components/dialog/Dialog.vue'
 import CollapseTransition from 'element-ui/lib/transitions/collapse-transition'
 import DrawerCard from '@/components/drawer/Drawer.vue'
@@ -484,7 +493,6 @@ import RadioGroup from '@/components/selector/RadioGroup.vue'
 // import CustomReport from '@/components/custom-report/customReport.vue'
 import FollowOfficialAccountDialog from '@/components/dialog/FollowOfficialAccountDialog.vue'
 import ConfigContent from '@/components/subscribe-manager/index'
-import CheckUserDialog from '@/components/dialog/CheckUserDialog'
 import SubscribeOverview from '@/components/subscribe-overview/index'
 import {
   getFreeUserPushInfo,
@@ -497,8 +505,9 @@ import {
   dateFormatter,
   openSelfLink,
   replaceKeyword,
-  formatPrice
+  formatPrice,
 } from '@/utils/'
+import { InfoTypeTransform } from '@/utils/format/info-type-transform'
 import { mapState, mapGetters, mapActions } from 'vuex'
 import { difference } from 'lodash'
 import { tryCallHooks } from '@jianyu/easy-inject-qiankun'
@@ -519,7 +528,6 @@ export default {
     PriceSelector,
     RadioGroup,
     DrawerCard,
-    CheckUserDialog,
     [CustomDialog.name]: CustomDialog,
     // CustomReport,
     FollowOfficialAccountDialog,
@@ -532,7 +540,8 @@ export default {
     SelectGroup,
     [CollapseTransition.name]: CollapseTransition,
     SubscribeOverview,
-    AreaCityCountryCascader
+    AreaCityCountryCascader,
+    SearchSchemaFilter
   },
   data() {
     return {
@@ -548,7 +557,8 @@ export default {
         gray: true,
         table: true,
         collect: true,
-        push: true
+        push: true,
+        detail: true,
       },
       withFileList: [
         {
@@ -602,9 +612,9 @@ export default {
         area: '',
         selectTime: '',
         city: '',
-        buyerClass: '',
-        subtype: '',
-        industry: '',
+        buyerClass: {},
+        subtype: {},
+        industry: {},
         keyWords: '',
         fileExists: '',
         price: '',
@@ -644,7 +654,9 @@ export default {
       },
       // 员工订阅总览抽屉
       showOverviewDrawer: false,
-      isOpenedViewDetail: false
+      isOpenedViewDetail: false,
+      keyList: [],
+      moduleList: []
     }
   },
   computed: {
@@ -781,6 +793,7 @@ export default {
     this.getInitInfo()
     await this.getSubScribeKeyList({ vt: this.vt })
     await this.getNewSubScribeKeyList(this.vt)
+    this.moduleList = subscribeMoreSchemaModule
   },
   mounted() {
     this.initQuery()
@@ -791,22 +804,32 @@ export default {
       this.initKeyMap()
     }
   },
-  // 此方法移除,使用router-view上的:key="route.fullPath"代替
-  // async beforeRouteUpdate (to, from, next) {
-  //   // 页面路由发生变化需要重新执行
-  //   this.getParams()
-  //   this.getInitInfo()
-  //   await this.getSubScribeKeyList({ vt: this.vt })
-  //   this.$nextTick(() => {
-  //     this.initQuery()
-  //     this.initKeyMap()
-  //     // 重置所有筛选条件
-  //     this.resetAllSelector()
-  //     next()
-  //   })
-  // },
   methods: {
     ...mapActions('user', ['getEntInfo', 'getSubScribeKeyList']),
+    // 关键词数据
+    onKeywordChange(data) {
+      this.filters.keyWords = data.two_noall.join(',')
+      const resultObject = {}
+      data.oneAndtwo.forEach((item) => {
+        const [category, subItemsStr] = item.split('_')
+        if (!resultObject[category]) {
+          resultObject[category] = []
+        }
+
+        // 对于包含多个子项的字符串(如“钢结构 彩钢板 门窗”),进一步分割并添加到数组中
+        subItemsStr.split(' ').forEach((subItem) => {
+          if (subItem !== '') { // 忽略空字符串(可能因连续空格导致)
+            resultObject[category].push(subItem.trim())
+          }
+        })
+      })
+      this.filters.item = resultObject
+      this.$refs.pushList.doQuery(this.filters)
+    },
+    doChangeFilter(data) {
+      this.onAreaCityCountry(data)
+      this.doQuery()
+    },
     // 市场分析报告图表
     setChartData(newval) {
       const chartKeyArr = Object.keys(newval)
@@ -1059,11 +1082,44 @@ export default {
       }
     },
     initKeyMap() {
-      const tempKeys = {}
+      const tempKeys = []
+      tempKeys.push({
+        label: '全部',
+        value: '全部',
+        children: [
+          {
+            label: '全部',
+            value: '全部',
+            parent: '全部',
+            selected: false,
+            indeterminate: false,
+            disabled: true
+          }
+        ],
+        selected: false,
+        indeterminate: false,
+        disabled: false
+      })
+      let tempObj = {}
       if (this.vt === 'f') {
-        let tempArr = []
-        // eslint-disable-next-line no-unused-expressions
+        tempObj = {
+          label: '未分类',
+          value: '未分类',
+          children: [],
+          selected: false,
+          indeterminate: false,
+          disabled: false
+        }
         if (this.subscribeKeyList && this.subscribeKeyList.length > 0) {
+          let keyArr = []
+          keyArr.push({
+            label: '全部',
+            value: '全部',
+            parent: '未分类',
+            selected: false,
+            indeterminate: false,
+            disabled: false
+          })
           this.subscribeKeyList.forEach((s) => {
             if (s.key && s.key.length > 1) {
               if (Array.isArray(s.key)) {
@@ -1074,60 +1130,77 @@ export default {
             if (s?.appendkey && s?.key) {
               tempList = [s?.key + ' ' + s?.appendkey.join(' ')]
             }
-            const newList = []
-            if (s?.matchway === 1) {
-              // 模糊  不改变传入组件数据结构,拼接字符串作为标识
-              tempList.forEach((ele) => {
-                newList.push(ele + '_matchway_1')
-              })
-            } else {
-              tempList.forEach((ele) => {
-                newList.push(ele + '_matchway_0')
-              })
+            let keyObj = {
+              label: tempList.join(' '),
+              value: tempList.join(' '),
+              parent: '未分类',
+              selected: false,
+              indeterminate: false,
+              disabled: false,
+              matchway: s?.matchway
             }
-            tempArr = tempArr.concat(newList)
+            keyArr.push(keyObj)
+            tempObj.children = keyArr
+            tempKeys.push(tempObj)
           })
-          tempKeys['未分类'] = tempArr
         }
       } else {
         // eslint-disable-next-line no-unused-expressions
         console.log(this.subscribeKeyList)
         if (this.subscribeKeyList && this.subscribeKeyList.length > 0) {
           this.subscribeKeyList.forEach((v) => {
-            let tempArr = []
-            if (v.a_key) {
-              v.a_key.forEach((s) => {
-                // 把不同关键词设置提交的格式差异数据统一,用空格作为分隔关键词放入数组 (已知大会员,企业订阅关键词格式为['aa bb cc'])
-                if (s.key && s.key.length > 1 && this.vt === 'v') {
-                  // 超级订阅提交回显关键词处理
-                  if (Array.isArray(s.key)) {
-                    s.key = [s.key.join(' ')]
+            if (v.s_item) {
+              tempObj = {
+                label: v.s_item,
+                value: v.s_item,
+                children: [],
+                selected: false,
+                indeterminate: false,
+                disabled: false
+              }
+              if (v.a_key) {
+                let keyArr = []
+                keyArr.push({
+                  label: '全部',
+                  value: '全部',
+                  parent: v.s_item,
+                  selected: false,
+                  indeterminate: false,
+                  disabled: false
+                })
+                v.a_key.forEach((s) => {
+                  // 把不同关键词设置提交的格式差异数据统一,用空格作为分隔关键词放入数组 (已知大会员,企业订阅关键词格式为['aa bb cc'])
+                  if (s.key && s.key.length > 1 && this.vt === 'v') {
+                    // 超级订阅提交回显关键词处理
+                    if (Array.isArray(s.key)) {
+                      s.key = [s.key.join(' ')]
+                    }
                   }
-                }
-                let tempList = s?.key || []
-                if (s?.appendkey && s?.key) {
-                  tempList = [s?.key + ' ' + s?.appendkey.join(' ')] // 有时会存在appendkey数组 同样处理为空格分隔的字符串放入数组
-                }
-                const newList = []
-                if (s.matchway === 1) {
-                  // 模糊  不改变传入组件数据结构,拼接字符串作为标识
-                  tempList.forEach((ele) => {
-                    newList.push(ele + '_matchway_1')
-                  })
-                } else {
-                  // 精准
-                  tempList.forEach((ele) => {
-                    newList.push(ele + '_matchway_0')
-                  })
-                }
-                tempArr = tempArr.concat(newList)
-              })
+                  let tempList = s?.key || []
+                  if (s?.appendkey && s?.key) {
+                    tempList = [s?.key + ' ' + s?.appendkey.join(' ')] // 有时会存在appendkey数组 同样处理为空格分隔的字符串放入数组
+                  }
+                  let keyObj = {
+                    label: tempList.join(' '),
+                    value: tempList.join(' '),
+                    parent: v.s_item,
+                    selected: false,
+                    indeterminate: false,
+                    disabled: false,
+                    notkey: s.notkey?.join(' '),
+                    matchway: s?.matchway
+                  }
+                  keyArr.push(keyObj)
+                })
+                tempObj.children = keyArr
+              }
             }
-            tempKeys[v.s_item] = tempArr
+            tempKeys.push(tempObj)
           })
         }
       }
-      this.$refs.keySelector.initDataMap(tempKeys)
+      this.keyList = tempKeys
+      // this.$refs.keySelector.initDataMap(tempKeys)
     },
     initQuery() {
       this.$refs.timeSelector.setState({
@@ -1155,17 +1228,14 @@ export default {
     },
     changeIndustry(item) {
       this.filters.industry = this.formatIndustryMap(item).join(',')
-      this.doQuery()
     },
     changeBuyer(item) {
       this.filters.buyerClass = this.formatIndustryMap(item)
         .map((v) => v.split('_')[1])
         .join(',')
-      this.doQuery()
     },
     changeInfo(item) {
-      this.filters.subtype = item.join(',')
-      this.doQuery()
+      // this.filters.subtype = item.join(',')
     },
     onFileStateChange() {
       this.doQuery()
@@ -1180,8 +1250,14 @@ export default {
       this.doQuery()
     },
     doQuery() {
+      const filterBuyerClass = this.formatIndustryMap(this.filters.buyerClass).map((v) => v.split('_')[1]).join(',')
+      console.log(this.filters, 'filters')
+      const filterSubtype = InfoTypeTransform.mapToList(this.filters.subtype)
       const payload = {
         ...this.filters,
+        industry: this.formatIndustryMap(this.filters.industry).join(','),
+        buyerClass: filterBuyerClass,
+        subtype: filterSubtype.length > 0 ? filterSubtype.join(',') : '',
         vt: this.vt === 'f' ? '' : this.vt
       }
       this.$refs.pushList.doQuery(payload)
@@ -1547,6 +1623,19 @@ export default {
   background-color: #fff;
 
   ::v-deep {
+    .date-time-container{
+      background-color: transparent;
+    }
+    .selector-card:not(:last-of-type){
+      border-bottom: none;
+    }
+    .search-schema-filter-container {
+      padding: 0 32px;
+    }
+    .selector-card.s-line .selector-card-header{
+      margin-right: 12px;
+      min-width: 70px;
+    }
     .drawer-class {
       background: #f7f9fc;
     }
@@ -1978,7 +2067,7 @@ export default {
     justify-content: flex-end;
     font-size: 14px;
     white-space: nowrap;
-    min-width: 98px;
+    width: 70px;
     color: #686868;
   }
 

+ 1 - 1
apps/bigmember_pc/src/views/subscribe/constant/search-filters.js

@@ -189,4 +189,4 @@ function createSubscribeMoreSchema() {
 }
 
 const subscribeMoreSchemaModule = createSubscribeMoreSchema.apply(this)
-export { subscribeMoreSchemaModule }
+export { subscribeMoreSchemaModule }

+ 97 - 0
apps/mobile/src/components/selector/tag-sidebar/index.vue

@@ -0,0 +1,97 @@
+<template>
+  <div class="tag-sidebar-selector">
+    <div
+      class="tag-sidebar-selector__list"
+      :class="{ active: item.type }"
+      v-for="(item, index) in tagList"
+      :key="index"
+      @click="selectTags(item)"
+    >
+      {{ item.lanme }}
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'TagSidebarSelector',
+  props: {
+    tagList: {
+      type: Array,
+      default() {
+        return []
+      }
+    },
+    selectList: {
+      type: Array,
+      default() {
+        return []
+      }
+    },
+    beforeChange: Function
+  },
+  methods: {
+    setState() {
+      let arr = this.selecttaglist
+      if (arr && arr.length) {
+        this.tagList.forEach((item) => {
+          arr.forEach((data) => {
+            if (item.lid == data) {
+              item.type = true
+            }
+          })
+        })
+      } else {
+        this.tagList.forEach((item) => {
+          item.type = false
+        })
+      }
+    },
+    getState() {
+      let dataArr2 = []
+      let data2 = this.tagList.filter((value) => {
+        return value.type == true
+      })
+      data2.forEach((item) => {
+        dataArr2.push(item.lid)
+      })
+      return dataArr2
+    },
+    selectTags(item) {
+      item.type = !item.type
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.tag-sidebar-selector {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 16px;
+  .tag-sidebar-selector__list {
+    height: 36px;
+    background: #f5f6f7;
+    border-radius: 4px;
+    color: #5f5e64;
+    font-size: 14px;
+    line-height: 36px;
+    padding: 0 20px;
+    margin: 0 10px 10px 0;
+    &.active {
+      position: relative;
+      background: #e8fafd;
+      color: #2abed1;
+      &::after {
+        content: '';
+        position: absolute;
+        right: 0;
+        bottom: 0;
+        background: url(../../../assets/image/icon/icon-button-mark.png) no-repeat;
+        width: 14px;
+        height: 14px;
+        background-size: 100% 100%;
+      }
+    }
+  }
+}
+</style>

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

@@ -0,0 +1,12 @@
+// 静态路由
+export default [
+  {
+    path: '/index',
+    name: 'collection',
+    component: () => import('@/views/collection/index.vue'),
+    meta: {
+      header: true,
+      title: '标讯收藏'
+    }
+  }
+]

+ 1196 - 0
apps/mobile/src/views/collection/components/filters.vue

@@ -0,0 +1,1196 @@
+<template>
+  <van-dropdown-menu
+    ref="dropdownMenu"
+    class="dropdown-menu-arrow"
+    :close-on-click-outside="false"
+  >
+    <van-dropdown-item
+      ref="scopeDropdown"
+      :title-class="dropdownMenuHighlight('label')"
+      title="个人标签"
+      get-container="body"
+      @open="doOpen('label')"
+      @opened="openedDropDown('label')"
+    >
+      <DropdownLayout
+        maxHeight="50vh"
+        @cancel="onReset('label')"
+        @confirm="onConfirm('label')"
+      >
+        <TagSelector ref="tagSelector" :tagList="tagList"></TagSelector>
+      </DropdownLayout>
+    </van-dropdown-item>
+    <van-dropdown-item
+      ref="industryDropdown"
+      :title-class="dropdownMenuHighlight('industry')"
+      title="收藏日期"
+      get-container="body"
+      @open="doOpen('industry')"
+      @opened="openedDropDown('industry')"
+    >
+      <DropdownLayout
+        @cancel="onReset('industry')"
+        @confirm="onConfirm('industry')"
+      >
+        <!-- <IndustrySidebar
+          ref="industrySelector"
+          v-model="cacheMoreFilters.industry"
+        /> -->
+      </DropdownLayout>
+    </van-dropdown-item>
+    <van-dropdown-item
+      ref="areaDropdown"
+      :title-class="dropdownMenuHighlight('area')"
+      title="发布日期"
+      get-container="body"
+      @open="doOpen('area')"
+      @opened="openedDropDown('area')"
+    >
+      <DropdownLayout @cancel="onReset('area')" @confirm="onConfirm('area')">
+        <!-- <AreaCitySidebar
+          class="area-city-sidebar"
+          :tagTextOnlySelectedCount="true"
+          :disabledCitySelect="noLoginOrFree"
+          :useProvinceCitySplit="true"
+          v-model="cacheMoreFilters.area"
+          @onDisabledCityClick="onNoPower"
+          v-if="useAreaCity"
+          ref="areaSelector"
+        ></AreaCitySidebar> -->
+        <!-- 未登录下。确定时候,手动获取数据转换为对象。设置时候,手动将对象转为数组并手动恢复数据 -->
+        <!-- <AreaSelectorContent
+          class="area-selector"
+          v-else
+          ref="areaSelector"
+        ></AreaSelectorContent> -->
+      </DropdownLayout>
+    </van-dropdown-item>
+    <van-dropdown-item
+      ref="moreDropdown"
+      :title-class="dropdownMenuHighlight('more')"
+      title="更多筛选"
+      get-container="body"
+      @open="doOpen('more')"
+      @opened="openedDropDown('more')"
+    >
+      <DropdownLayout
+        :maxHeight="dropdownMenuMaxHeight"
+        @cancel="onReset('more')"
+        @confirm="onConfirm('more')"
+        :style="{ height: dropdownMenuMaxHeight }"
+      >
+        <div class="wrapper">
+          <JCell class="more-filter-item" title="标题正文">
+            <template #label>
+              <KeywordsInputGroup
+                v-model="cacheMoreFilters.moreKeywordsMode"
+                ref="moreKeywordsGroup"
+                wordsMode
+                class="more-keywords-component"
+              />
+            </template>
+          </JCell>
+          <JCell class="more-filter-item" title="金额">
+            <template #label>
+              <CheckboxGroup
+                :options="conf.priceOptions"
+                :config="conf.checkboxGroup"
+                v-model="cacheMoreFilters.priceCheckbox"
+              />
+              <MoneyInputGroup
+                class="price-input-group"
+                v-show="cacheMoreFilters.priceCheckbox.includes('custom')"
+                ref="priceSelector"
+              ></MoneyInputGroup>
+            </template>
+          </JCell>
+          <JCell class="more-filter-item" title="时间">
+            <template #label>
+              <DateTimeList
+                ref="dateTimeGroup"
+                @checkboxClick="checkboxGroupClick"
+                :options="getTimeOptions"
+              />
+            </template>
+          </JCell>
+          <van-cell
+            class="more-filter-item one-line-filter-item"
+            :isLink="true"
+            title="信息类型"
+            :border="false"
+            @click="showPopup('infoType')"
+            :value="valueTextWithInfoType"
+          />
+          <van-cell
+            v-show="isLogin"
+            class="more-filter-item one-line-filter-item"
+            :isLink="true"
+            title="项目地区"
+            :border="false"
+            @click="showPopup('area')"
+            :value="valueTextWithArea"
+          >
+          </van-cell>
+          <van-cell
+            v-show="isLogin"
+            class="more-filter-item one-line-filter-item"
+            :isLink="true"
+            title="行业"
+            :border="false"
+            @click="showPopup('industry')"
+            :value="valueTextWithIndustry"
+          >
+          </van-cell>
+          <van-cell
+            v-show="isLogin"
+            class="more-filter-item one-line-filter-item"
+            :isLink="true"
+            title="采购单位类型"
+            :border="false"
+            @click="showPopup('buyerClass')"
+            :value="valueTextWithBuyerClass"
+          >
+            <template #title v-if="noLoginOrFree">
+              <span class="mr-6">采购单位类型</span>
+              <van-tag plain round type="danger">开通</van-tag>
+            </template>
+          </van-cell>
+          <JCell
+            class="more-filter-item"
+            title="中标企业联系方式"
+            v-show="isLogin"
+          >
+            <template #title v-if="noLoginOrFree">
+              <span class="mr-6">中标企业联系方式</span>
+              <van-tag plain round type="danger" @click="onNoPower"
+                >开通</van-tag
+              >
+            </template>
+            <template #label>
+              <CheckboxGroup
+                :options="getConcatOptions"
+                :config="conf.checkboxGroup"
+                @item-select="checkboxGroupClick"
+                v-model="cacheMoreFilters.winnerConcat"
+              />
+            </template>
+          </JCell>
+          <JCell
+            class="more-filter-item"
+            title="采购单位联系方式"
+            v-show="isLogin"
+          >
+            <template #title v-if="noLoginOrFree">
+              <span class="mr-6">采购单位联系方式</span>
+              <van-tag plain round type="danger" @click="onNoPower"
+                >开通</van-tag
+              >
+            </template>
+            <template #label>
+              <CheckboxGroup
+                :options="getConcatOptions"
+                :config="conf.checkboxGroup"
+                @item-select="checkboxGroupClick"
+                v-model="cacheMoreFilters.buyerConcat"
+              />
+            </template>
+          </JCell>
+        </div>
+      </DropdownLayout>
+    </van-dropdown-item>
+    <van-popup
+      class="fix-pop-height"
+      v-model="popup.infoType"
+      round
+      position="bottom"
+      :safe-area-inset-bottom="true"
+      :close-on-popstate="true"
+      get-container="body"
+      :lazy-render="false"
+      :style="{ 'max-height': infoTypeSourceConf.name ? '56%' : '70%' }"
+    >
+      <PopupLayout @closeIconClick="popup.infoType = false">
+        <div slot="title">
+          <span>请选择信息类型</span>
+          <span class="popup-sub-title" v-if="infoTypeSourceConf.name">
+            &nbsp;&nbsp;/&nbsp;&nbsp;{{ infoTypeSourceConf.title }}
+          </span>
+        </div>
+        <InfoTypeOneClassify
+          ref="infoTypeSelector"
+          :sourceType="infoTypeSourceConf.name"
+          v-if="infoTypeSourceConf.name"
+        />
+        <InfoTypeSidebar
+          ref="infoTypeSelector"
+          v-else
+          :sourceType="infoTypeSourceConf.name"
+        />
+        <div slot="footer">
+          <div class="j-button-group height40">
+            <button class="j-button-cancel" @click="popupReset('infoType')">
+              重置
+            </button>
+            <button class="j-button-confirm" @click="popupConfirm('infoType')">
+              确定
+            </button>
+          </div>
+        </div>
+      </PopupLayout>
+    </van-popup>
+    <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': '70%' }"
+    >
+      <PopupLayout @closeIconClick="popup.area = false">
+        <div slot="title">
+          <span>请选择项目地区</span>
+        </div>
+        <AreaCitySidebar
+          class="area-city-sidebar"
+          :tagTextOnlySelectedCount="true"
+          :disabledCitySelect="noLoginOrFree"
+          :useProvinceCitySplit="true"
+          v-model="cacheMoreFilters.area"
+          @onDisabledCityClick="onNoPower"
+          v-if="useAreaCity"
+          ref="areaSelector"
+        ></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>
+    <van-popup
+      class="fix-pop-height"
+      v-model="popup.industry"
+      round
+      position="bottom"
+      :safe-area-inset-bottom="true"
+      :close-on-popstate="true"
+      get-container="body"
+      :lazy-render="false"
+      :style="{ 'max-height': industrySourceConf.name ? '56%' : '70%' }"
+    >
+      <PopupLayout @closeIconClick="popup.industry = false">
+        <div slot="title">
+          <span>请选择行业</span>
+          <span class="popup-sub-title" v-if="industrySourceConf.name">
+            &nbsp;&nbsp;/&nbsp;&nbsp;{{ industrySourceConf.title }}
+          </span>
+        </div>
+        <IndustrySidebar
+          ref="industrySelector"
+          v-model="cacheMoreFilters.industry"
+        />
+        <div slot="footer">
+          <div class="j-button-group height40">
+            <button class="j-button-cancel" @click="popupReset('industry')">
+              重置
+            </button>
+            <button class="j-button-confirm" @click="popupConfirm('industry')">
+              确定
+            </button>
+          </div>
+        </div>
+      </PopupLayout>
+    </van-popup>
+    <van-popup
+      class="fix-pop-height"
+      v-model="popup.buyerClass"
+      round
+      position="bottom"
+      :safe-area-inset-bottom="true"
+      :close-on-popstate="true"
+      get-container="body"
+      :lazy-render="false"
+      :style="{ 'max-height': '70%' }"
+    >
+      <PopupLayout
+        title="请选择采购单位类型"
+        @closeIconClick="popup.buyerClass = false"
+      >
+        <BuyerClassSidebar ref="buyerClassSelector" />
+        <div slot="footer">
+          <div class="j-button-group height40">
+            <button class="j-button-cancel" @click="popupReset('buyerClass')">
+              重置
+            </button>
+            <button
+              class="j-button-confirm"
+              @click="popupConfirm('buyerClass')"
+            >
+              确定
+            </button>
+          </div>
+        </div>
+      </PopupLayout>
+    </van-popup>
+  </van-dropdown-menu>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import { DropdownMenu, DropdownItem, Cell, Field, Popup, Tag } from 'vant'
+import { JCell, CheckboxGroup, AppIcon } from '@/ui'
+import PopupLayout from '@/components/common/PopupLayout'
+import DropdownLayout from '@/components/common/DropdownLayout'
+// 地区三级选择器
+import AreaCitySidebar from '@/components/selector/area-three-sidebar/index'
+import AreaSelectorContent from '@/components/selector/area'
+import TagSelector from '@/components/selector/tag-sidebar/index.vue';
+import IndustrySidebar from '@/components/selector/industry-sidebar/index'
+import InfoTypeSidebar from '@/components/selector/info-type-sidebar/index'
+import InfoTypeOneClassify from '@/components/selector/info-type-one-classify/index'
+import BuyerClassSidebar from '@/components/selector/buyer-class-sidebar/index'
+import MoneyInputGroup from '@/components/selector/money-input-group'
+import DateTimeList from '@/components/selector/date-time-list'
+import KeywordsInputGroup from '@/components/selector/keyword-input-group/index'
+import { deepCompare } from '@/utils'
+import {
+  biddingSearchScope,
+  biddingSearchConcat,
+  biddingSearchFileExists,
+  biddingSearchTime
+} from '@/data'
+
+export default {
+  name: 'BiddingFilters',
+  components: {
+    [Field.name]: Field,
+    [Tag.name]: Tag,
+    [Cell.name]: Cell,
+    [Popup.name]: Popup,
+    [DropdownMenu.name]: DropdownMenu,
+    [DropdownItem.name]: DropdownItem,
+    PopupLayout,
+    DropdownLayout,
+    IndustrySidebar,
+    AreaCitySidebar,
+    AreaSelectorContent,
+    TagSelector,
+    InfoTypeSidebar,
+    BuyerClassSidebar,
+    MoneyInputGroup,
+    CheckboxGroup,
+    DateTimeList,
+    InfoTypeOneClassify,
+    KeywordsInputGroup,
+    AppIcon,
+    JCell
+  },
+  model: {
+    prop: 'filters',
+    event: 'change'
+  },
+  props: {
+    dropdownMenuMaxHeight: [String, Number],
+    tagList: {
+      type: Array,
+      default() {
+        return []
+      }
+    },
+    bidColPower: {
+      type: Object,
+      default() {
+        return {
+          isOld: false
+        }
+      }
+    },
+    /**
+     * 控制信息类型选择器组件的数据来源
+     * 默认值为空: 表示显示所有可能的备选项
+     * infoTypeSourceConf.id: advanced: 表示仅显示超前项目备选项
+     * infoTypeSourceConf.id: notAdvanced: 表示仅显示非超前项目备选项
+     * infoTypeSourceConf.id: zbgg: 招标公告
+     * infoTypeSourceConf.id: zbyh: 招标预告
+     * infoTypeSourceConf.id: zbjg: 招标结果
+     */
+    infoTypeSourceConf: {
+      type: Object,
+      default() {
+        return {
+          id: '0',
+          title: '',
+          name: ''
+        }
+      }
+    },
+    industrySourceConf: {
+      type: Object,
+      default() {
+        return {
+          id: '0',
+          title: '',
+          name: ''
+        }
+      }
+    },
+    /**
+     * 默认值,用来reset恢复数据
+     */
+    defaultFilterState: {
+      type: Object,
+      default() {
+        return {
+          moreKeywordsMode: {},
+          label: [],
+          industry: {},
+          area: {},
+          priceCheckbox: [''],
+          price: {
+            start: '',
+            end: ''
+          },
+          dateTime: {
+            exact: 'thisyear'
+          },
+          infoType: [],
+          buyerClass: [],
+          winnerConcat: [''],
+          buyerConcat: [''],
+          notKey: [],
+          buyerList: [],
+          winnerList: [],
+          agencyList: []
+        }
+      }
+    },
+    // 提交数据
+    filters: {
+      type: Object,
+      default() {
+        return {
+          moreKeywordsMode: {},
+          label: [],
+          industry: {},
+          area: {},
+          priceCheckbox: [''],
+          price: {
+            start: '',
+            end: ''
+          },
+          dateTime: {},
+          infoType: [],
+          buyerClass: [],
+          winnerConcat: [''],
+          buyerConcat: [''],
+          notKey: [],
+          buyerList: [],
+          winnerList: [],
+          agencyList: []
+        }
+      }
+    }
+  },
+  data() {
+    return {
+      conf: {
+        dropdownTypeList: ['label', 'industry', 'area', 'more'],
+        label: biddingSearchScope,
+        dateTime: biddingSearchTime,
+        checkboxGroup: {
+          icon: false,
+          multiple: false
+        },
+        concatOptions: biddingSearchConcat,
+        priceOptions: [
+          {
+            label: '全部',
+            key: ''
+          },
+          {
+            label: '自定义',
+            key: 'custom'
+          }
+        ]
+      },
+      popup: {
+        infoType: false,
+        area: false,
+        industry: false,
+        buyerClass: false
+      },
+      // 选择器对应的数据
+      cacheMoreFilters: {
+        moreKeywordsMode: {},
+        industry: {},
+        area: {},
+        priceCheckbox: [''],
+        dateTime: {},
+        infoType: [],
+        buyerClass: [],
+        winnerConcat: [''],
+        buyerConcat: [''],
+        notKey: [],
+        buyerList: [],
+        winnerList: [],
+        agencyList: []
+      }
+    }
+  },
+  computed: {
+    ...mapGetters('user', ['isLogin', 'isFree']),
+    useAreaCity() {
+      return this.isLogin
+    },
+    noLoginOrFree() {
+      if (this.isLogin) {
+        return this.isFree
+      } else {
+        return true
+      }
+    },
+    getScopeOptions() {
+      const { label: list } = this.conf
+      list.forEach((item) => {
+        // 老用户专享
+        const oldUserExclusive =
+          this.bidColPower.isOld && item.label.includes('中标企业')
+        if (item.needPower) {
+          if (this.noLoginOrFree && !oldUserExclusive) {
+            this.$set(item, 'disabled', true)
+            this.$set(item, 'tag', '开通')
+          } else {
+            this.$set(item, 'disabled', false)
+            this.$set(item, 'tag', '')
+            if (oldUserExclusive) {
+              this.$set(item, 'labelTag', '老用户免费专享')
+            }
+          }
+        }
+      })
+      if (this.isLogin) {
+        return list
+      } else {
+        return list.filter((s) => !s.disabled)
+      }
+    },
+    getTimeOptions() {
+      const list = this.conf.dateTime
+      if (this.noLoginOrFree) {
+        list.forEach((item) => {
+          if (item.needPower) {
+            this.$set(item, 'disabled', true)
+            this.$set(item, 'tag', '开通')
+          }
+        })
+      } else {
+        list.forEach((item) => {
+          if (item.needPower) {
+            this.$set(item, 'disabled', false)
+            this.$set(item, 'tag', '')
+          }
+        })
+      }
+      if (this.isLogin) {
+        return list
+      } else {
+        return list.filter((s) => !s.disabled)
+      }
+    },
+    getConcatOptions() {
+      const list = this.conf.concatOptions
+      list.forEach((item) => {
+        this.$set(item, 'disabled', this.noLoginOrFree)
+      })
+      return list
+    },
+    valueTextWithInfoType() {
+      const { infoType } = this.cacheMoreFilters
+      if (Array.isArray(infoType) && infoType.length) {
+        return `已选:${infoType.length}个`
+      } else {
+        return '已选:全部'
+      }
+    },
+    valueTextWithArea() {
+      const { area } = this.cacheMoreFilters
+      if (Array.isArray(area) && area.length) {
+        return `已选:${area.length}个`
+      } else {
+        return '已选:全部'
+      }
+    },
+    valueTextWithIndustry() {
+      const { industry } = this.cacheMoreFilters
+      if (Array.isArray(industry) && industry.length) {
+        return `已选:${industry.length}个`
+      } else {
+        return '已选:全部'
+      }
+    },
+    valueTextWithBuyerClass() {
+      const { buyerClass } = this.cacheMoreFilters
+      if (Array.isArray(buyerClass) && buyerClass.length) {
+        return `已选:${buyerClass.length}个`
+      } else {
+        return '已选:全部'
+      }
+    }
+  },
+  beforeDestroy() {
+    this.$refs.dropdownMenu.$children.forEach((child) => {
+      const wrapper = child.$refs?.wrapper
+      try {
+        wrapper && wrapper.remove()
+        child.remove()
+        child.$destroy()
+      } catch (e) {
+        console.warn(e)
+      }
+    })
+  },
+  methods: {
+    getContainer() {
+      return this.$root.$el.querySelector('.search-result-bidding')
+    },
+    closePopup(type) {
+      if (type !== -1) {
+        this.$refs[`${type}Dropdown`]?.toggle(false)
+      } else {
+        const dropdown = this.conf.dropdownTypeList
+        dropdown.forEach((type) => {
+          this.closePopup(type)
+        })
+      }
+    },
+    confirmAll(type) {
+      if (type !== -1) {
+        this.onConfirm(type)
+      } else {
+        const dropdown = this.conf.dropdownTypeList
+        dropdown.forEach((type) => {
+          this.onConfirm(type)
+        })
+      }
+    },
+    dropdownMenuHighlight(type) {
+      const className = ['highlight']
+      let needHighlight = false
+      switch (type) {
+        case 'label': {
+          const { label } = this.filters
+          const { label: scopeDefault } = this.defaultFilterState
+          const same = deepCompare(label, scopeDefault)
+          needHighlight = !same
+          break
+        }
+        case 'industry': {
+          const { industry } = this.filters
+          const { industry: industryDefault } = this.defaultFilterState
+          const same = deepCompare(industry, industryDefault)
+          needHighlight = !same
+          break
+        }
+        case 'area': {
+          const { area } = this.filters
+          const { area: areaDefault } = this.defaultFilterState
+          const same = deepCompare(area, areaDefault)
+          needHighlight = !same
+          break
+        }
+        case 'more': {
+          const {
+            moreKeywordsMode,
+            price,
+            dateTime,
+            // priceCheckbox,
+            infoType,
+            buyerClass,
+            winnerConcat,
+            buyerConcat,
+            notKey,
+            buyerList,
+            winnerList,
+            agencyList
+          } = this.defaultFilterState
+          const sameList = []
+
+          // 更多关键词
+          sameList.push(
+            deepCompare(this.filters.moreKeywordsMode, moreKeywordsMode)
+          )
+
+          // 金额
+          sameList.push(deepCompare(this.filters.price, price))
+          // 时间
+          sameList.push(
+            deepCompare(this.filters?.dateTime?.exact, dateTime?.exact)
+          )
+
+          // 信息类型
+          sameList.push(deepCompare(this.filters.infoType, infoType))
+          // 采购单位类型
+          sameList.push(deepCompare(this.filters.buyerClass, buyerClass))
+          // 联系方式
+          sameList.push(deepCompare(this.filters.winnerConcat, winnerConcat))
+          sameList.push(deepCompare(this.filters.buyerConcat, buyerConcat))
+          sameList.push(deepCompare(this.filters.notKey, notKey))
+          sameList.push(deepCompare(this.filters.winnerConcat, winnerConcat))
+          sameList.push(deepCompare(this.filters.buyerConcat, buyerConcat))
+          sameList.push(deepCompare(this.filters.notKey, notKey))
+
+          // 采购单位
+          sameList.push(deepCompare(this.filters.buyerList, buyerList))
+          // 中标单位
+          sameList.push(deepCompare(this.filters.winnerList, winnerList))
+          // 代理机构
+          sameList.push(deepCompare(this.filters.agencyList, agencyList))
+
+          needHighlight = sameList.includes(false)
+          break
+        }
+      }
+      return needHighlight ? className.join(' ') : ''
+    },
+    doOpen(type) {
+      this.$emit('open', type)
+    },
+    // 从filters中恢复选择器状态
+    openedDropDown(type) {
+      switch (type) {
+        case 'label': {
+          const { tagSelector } = this.$refs
+          const { label } = this.filters
+          tagSelector?.setState(label)
+          break
+        }
+        case 'industry': {
+          const { industry } = this.filters
+          this.cacheMoreFilters.industry = industry
+          break
+        }
+        case 'area': {
+          const { area } = this.filters
+          if (this.useAreaCity) {
+            this.cacheMoreFilters.area = area
+          } else {
+            if (area) {
+              // 恢复数据
+              const { areaSelector } = this.$refs
+              const arr = Object.keys(area)
+              areaSelector?.setState(arr)
+            }
+          }
+          break
+        }
+        case 'more': {
+          const { priceSelector, dateTimeGroup, moreKeywordsGroup } = this.$refs
+          const {
+            moreKeywordsMode,
+            price,
+            dateTime,
+            priceCheckbox,
+            infoType,
+            buyerClass,
+            winnerConcat,
+            buyerConcat,
+            notKey,
+            buyerList,
+            winnerList,
+            agencyList
+          } = this.filters
+
+          // 更多关键词
+          this.cacheMoreFilters.moreKeywordsMode = moreKeywordsMode
+          try {
+            moreKeywordsGroup?.setState(moreKeywordsMode)
+          } catch (error) {}
+
+          // 金额
+          this.cacheMoreFilters.priceCheckbox = priceCheckbox
+          if (priceCheckbox.includes('custom')) {
+            priceSelector?.setState(price)
+          }
+
+          // 时间
+          this.cacheMoreFilters.dateTime = dateTime
+          dateTimeGroup?.setState(dateTime)
+
+          // 信息类型
+          this.cacheMoreFilters.infoType = infoType
+          // 采购单位类型
+          this.cacheMoreFilters.buyerClass = buyerClass
+          // 联系方式
+          this.cacheMoreFilters.winnerConcat = winnerConcat
+
+          this.cacheMoreFilters.buyerConcat = buyerConcat
+          this.cacheMoreFilters.notKey = notKey
+
+          this.cacheMoreFilters.buyerList = buyerList
+          this.cacheMoreFilters.winnerList = winnerList
+          this.cacheMoreFilters.agencyList = agencyList
+          break
+        }
+      }
+    },
+    // 重置选择器状态
+    resetSelectors(type) {
+      const filters = {}
+      switch (type) {
+        case 'label': {
+          const { label } = this.defaultFilterState
+          const { tagSelector } = this.$refs
+          if (Array.isArray(label)) {
+            filters.label = label
+            tagSelector?.setState(label)
+          }
+          break
+        }
+        case 'industry': {
+          const { industry } = this.defaultFilterState
+          if (industry) {
+            filters.industry = industry
+            this.cacheMoreFilters.industry = industry
+          }
+          break
+        }
+        case 'area': {
+          const { area } = this.defaultFilterState
+          if (area) {
+            this.cacheMoreFilters.area = area
+            filters.area = area
+            if (!this.useAreaCity) {
+              const { areaSelector } = this.$refs
+              areaSelector?.setState(Object.keys(area))
+            }
+          }
+          break
+        }
+        case 'more': {
+          const { priceSelector, dateTimeGroup } = this.$refs
+          const {
+            moreKeywordsMode,
+            price,
+            dateTime,
+            priceCheckbox,
+            infoType,
+            buyerClass,
+            winnerConcat,
+            buyerConcat,
+            notKey,
+            buyerList,
+            winnerList,
+            agencyList
+          } = this.defaultFilterState
+          // 更多关键词
+          if (moreKeywordsMode) {
+            this.cacheMoreFilters.moreKeywordsMode = moreKeywordsMode
+            filters.moreKeywordsMode = moreKeywordsMode
+          }
+
+          // 金额
+          if (Array.isArray(priceCheckbox)) {
+            this.cacheMoreFilters.priceCheckbox = priceCheckbox
+            filters.priceCheckbox = priceCheckbox
+            priceSelector?.setState(price)
+            filters.price = JSON.parse(JSON.stringify(price))
+          }
+
+          // 时间
+          if (dateTime) {
+            filters.dateTime = dateTime
+            dateTimeGroup?.setState(dateTime)
+          }
+
+          // 信息类型
+          if (infoType) {
+            filters.infoType = infoType
+            this.cacheMoreFilters.infoType = infoType
+          }
+          // 采购单位类型
+          if (Array.isArray(buyerClass)) {
+            filters.buyerClass = buyerClass
+            this.cacheMoreFilters.buyerClass = buyerClass
+          }
+
+          // 联系方式
+          if (Array.isArray(winnerConcat)) {
+            filters.winnerConcat = winnerConcat
+            this.cacheMoreFilters.winnerConcat = winnerConcat
+          }
+          if (Array.isArray(buyerConcat)) {
+            filters.buyerConcat = buyerConcat
+            this.cacheMoreFilters.buyerConcat = buyerConcat
+          }
+          if (Array.isArray(notKey)) {
+            this.cacheMoreFilters.notKey = notKey
+            filters.notKey = notKey
+          }
+          if (Array.isArray(buyerList)) {
+            filters.buyerList = buyerList
+            this.cacheMoreFilters.buyerList = buyerList
+          }
+          if (Array.isArray(winnerList)) {
+            filters.winnerList = winnerList
+            this.cacheMoreFilters.winnerList = winnerList
+          }
+          if (Array.isArray(agencyList)) {
+            filters.agencyList = agencyList
+            this.cacheMoreFilters.agencyList = agencyList
+          }
+          break
+        }
+      }
+      return filters
+    },
+    resetModelChange(type) {
+      const filters = this.resetSelectors(type)
+      this.onChange(filters)
+    },
+    onReset(type) {
+      this.resetModelChange(type)
+      this.$emit('reset')
+      this.$refs[`${type}Dropdown`]?.toggle(false)
+    },
+    confirmSelectors(type) {
+      const filters = {}
+      switch (type) {
+        case 'label': {
+          const { tagSelector } = this.$refs
+          const state = tagSelector?.getState()
+          filters.label = state
+          break
+        }
+        case 'industry': {
+          const { industry } = this.cacheMoreFilters
+          filters.industry = industry
+          break
+        }
+        case 'area': {
+          if (!this.useAreaCity) {
+            const { areaSelector } = this.$refs
+            const arr = areaSelector?.getState() || []
+            // 得到的数组转为对象
+            const m = {}
+            arr.forEach((i) => (m[i] = []))
+            this.cacheMoreFilters.area = m
+          }
+          const { area } = this.cacheMoreFilters
+          filters.area = area
+          break
+        }
+        case 'more': {
+          const { priceSelector, dateTimeGroup } = this.$refs
+          const {
+            moreKeywordsMode,
+            priceCheckbox,
+            infoType,
+            buyerClass,
+            winnerConcat,
+            buyerConcat,
+            notKey,
+            buyerList,
+            winnerList,
+            agencyList
+          } = this.cacheMoreFilters
+          const { price: defaultPrice } = this.defaultFilterState
+          // 更多关键词
+          filters.moreKeywordsMode = moreKeywordsMode
+
+          // 金额
+          if (priceCheckbox.includes('custom')) {
+            const priceState = priceSelector?.getState()
+            filters.price = priceState
+          } else {
+            filters.price = JSON.parse(JSON.stringify(defaultPrice))
+          }
+          filters.priceCheckbox = priceCheckbox
+
+          // 时间
+          const dateTime = dateTimeGroup.getState()
+          filters.dateTime = dateTime || {}
+
+          // 信息类型
+          filters.infoType = infoType
+          // 采购单位类型
+          filters.buyerClass = buyerClass
+          // 联系方式
+          filters.winnerConcat = winnerConcat
+          filters.buyerConcat = buyerConcat
+          filters.notKey = notKey
+          filters.buyerList = buyerList
+          filters.winnerList = winnerList
+          filters.agencyList = agencyList
+          break
+        }
+      }
+      return filters
+    },
+    confirmModelChange(type) {
+      const filters = this.confirmSelectors(type)
+      this.onChange(filters)
+    },
+    onConfirm(type) {
+      this.confirmModelChange(type)
+      this.$emit('confirm', { type })
+      this.$refs[`${type}Dropdown`]?.toggle(false)
+    },
+    onChange(value = {}) {
+      const filters = {}
+      Object.assign(filters, JSON.parse(JSON.stringify(this.filters)), value)
+      this.$emit('change', filters)
+    },
+    popupState(type, state = false) {
+      this.popup[type] = state
+    },
+    showPopup(type) {
+      if (type === 'buyerClass' && this.noLoginOrFree) {
+        return this.onNoPower()
+      }
+      this.popupState(type, true)
+      // 恢复数据
+      switch (type) {
+        case 'infoType': {
+          const { infoTypeSelector } = this.$refs
+          const { infoType } = this.cacheMoreFilters
+          infoTypeSelector?.setState(infoType)
+          break
+        }
+        case 'area': {
+          const { areaSelector } = this.$refs
+          const { area } = this.cacheMoreFilters
+          areaSelector?.setState(area)
+          break
+        }
+        case 'industry': {
+          const { industrySelector } = this.$refs
+          const { industry } = this.cacheMoreFilters
+          industrySelector?.setState(industry)
+          break
+        }
+        case 'buyerClass': {
+          const { buyerClassSelector } = this.$refs
+          const { buyerClass } = this.cacheMoreFilters
+          buyerClassSelector?.setState(buyerClass)
+          break
+        }
+      }
+    },
+    popupReset(type) {
+      switch (type) {
+        case 'infoType': {
+          const { infoTypeSelector } = this.$refs
+          const { infoType } = this.defaultFilterState
+          this.cacheMoreFilters.infoType = infoType
+          infoTypeSelector?.setState(infoType)
+          break
+        }
+        case 'buyerClass': {
+          const { buyerClassSelector } = this.$refs
+          const { buyerClass } = this.defaultFilterState
+          this.cacheMoreFilters.buyerClass = buyerClass
+          buyerClassSelector?.setState(buyerClass)
+          break
+        }
+      }
+      this.popupState(type, false)
+    },
+    popupConfirm(type) {
+      switch (type) {
+        case 'infoType': {
+          const { infoTypeSelector } = this.$refs
+          const state = infoTypeSelector?.getState()
+          this.$set(this.cacheMoreFilters, 'infoType', state)
+          break
+        }
+        case 'buyerClass': {
+          const { buyerClassSelector } = this.$refs
+          const state = buyerClassSelector?.getState()
+          this.$set(this.cacheMoreFilters, 'buyerClass', state)
+          break
+        }
+      }
+      this.popupState(type, false)
+    },
+    onNoPower() {
+      this.$emit('noPower')
+    },
+    checkboxGroupClick(item) {
+      if (item.disabled) {
+        this.onNoPower()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+  .highlight {
+    color: $main;
+  }
+  .mr-6 {
+    margin-right: 6px;
+  }
+}
+
+.fix-pop-height {
+  height: 100%;
+  ::v-deep {
+    .j-main,
+    .default-sidebar-height {
+      height: 100%;
+    }
+  }
+}
+
+.popup-sub-title {
+  font-size: 14px;
+  line-height: 20px;
+  color: #5f5e64;
+}
+
+.more-keywords-component {
+  padding: 8px 0;
+}
+.search-mode-filter {
+  .search-mode-label-icon {
+    margin-left: 8px;
+    font-size: 20px;
+    color: $main;
+  }
+  ::v-deep {
+    .cell-title {
+      display: flex;
+      align-items: center;
+    }
+  }
+}
+
+.scope-tag {
+  margin-left: 8px;
+  color: $main;
+  background-color: $color_main_background;
+}
+
+.one-line-filter-item {
+  padding-top: 15px;
+  padding-bottom: 15px;
+}
+.more-filter-item {
+  &:not(:last-of-type) {
+    border-bottom: 1px solid $border_color_3;
+  }
+}
+.price-input-group {
+  margin-top: 6px;
+}
+</style>

+ 2237 - 0
apps/mobile/src/views/collection/index.vue

@@ -0,0 +1,2237 @@
+<template>
+  <div class="j-container page-collection-index top-radius">
+    <div class="j-header linear-main-bg">
+      <!-- 筛选器 -->
+      <CollectionFilters
+        :bidColPower="pageState.bidColPower"
+        :tagList="pageState.tagLabelList"
+        class="top-radius"
+        :class="{ 'loading-mask': listState.loading }"
+        ref="searchFilters"
+        @noPower="onNoPower"
+        @confirm="onFilterConfirm"
+        @reset="doSearch"
+        @open="doOpen"
+        :defaultFilterState="defaultFilterState"
+        :dropdownMenuMaxHeight="conf.maxHeight"
+        v-model="filters"
+      />
+      <!-- 精简列表/详细列表/表格 -->
+      <div v-if="tabSwitchShow" class="tab-switch bg-white border-line-b">
+        <van-tabs
+          class="tab-left"
+          v-model="pageState.listTabActive"
+          :ellipsis="false"
+          :before-change="beforeTabActiveChange"
+          @change="onListTabChange"
+        >
+          <van-tab
+            v-for="(tab, index) in cardTypeTabList"
+            :name="tab.name"
+            :title="tab.title"
+            :key="index"
+          ></van-tab>
+        </van-tabs>
+      </div>
+      <div class="intercept-tip-container" v-show="interceptTipText">
+        <AppIcon name="tishi" /> {{ interceptTipText }}
+      </div>
+    </div>
+    <div class="j-main list-container" ref="listContainer">
+      <van-list
+        v-model="listState.loading"
+        :finished="listState.finished"
+        :offset="listState.offset"
+        :finished-text="finishedText"
+        v-show="
+          pageState.listTabActive === 'list' ||
+          pageState.listTabActive === 'detailedList'
+        "
+        @load="getList"
+        class="more-list calc-height-1px"
+        ref="vanList"
+      >
+        <div class="tab-search-info" v-show="tabSearchInfoShow">
+          <p class="search-total-count">
+            <template v-if="listState.total > 0">
+              搜索到<span class="highlight-text">
+                {{ calcListTotalText(listState.total) }} </span
+              >条信息
+            </template>
+          </p>
+          <div class="tab-right">
+            <div
+              class="data-export clickable"
+              v-if="listState.total > 0"
+              @click="dataExport"
+            >
+              <AppIcon name="shujudaochu_xiao1" />
+              <span class="text">数据导出</span>
+            </div>
+          </div>
+        </div>
+        <div
+          class="tip-toggle-search-mode-container"
+          v-show="toggleBlurModeTip.show"
+        >
+          <div>
+            如需查看更多相关信息,建议您将搜索模式切换为
+            <div class="tip-action highlight-text">
+              “模糊搜索”
+              <AppIcon
+                class="search-mode-label-icon"
+                name="help1"
+                @click="showSearchModeHelp"
+              >
+              </AppIcon>
+            </div>
+            按照当前条件共匹配到{{ toggleBlurModeTip.count }}条公告。
+          </div>
+          <button @click="doToggleSearchBlurMode">立即切换查看</button>
+        </div>
+        <div class="list-wrapper" ref="listWrapper">
+          <van-popup
+            :style="popupHeight"
+            get-container="body"
+            v-model="showBidStatus"
+            round
+            position="bottom"
+          >
+            <bid-status-node
+              @cancel-update="cancelUpdate"
+              @save-success="saveSuccess"
+              :project-cell-info="projectCellInfo"
+              @set-height="setHeight"
+            ></bid-status-node>
+          </van-popup>
+          <template v-for="(item, index) in searchList">
+            <ProjectCell
+              v-if="!item.ad"
+              class="list-item"
+              :class="item.className"
+              :cardType="calcProjectCardType(item)"
+              :time-fmt="getTimeFmt"
+              :detailList="item.detailList"
+              @click="goToDetail(item)"
+              :title="item.title"
+              :filetext_search="item.filetext_search"
+              :time="item.dateTime"
+              v-visited:content="item.id"
+              :isFile="item.isFile"
+              :keys="pageState.splitKeys"
+              :leftTopBadgeText="item.leftTopBadgeText"
+              :tags="item.tagList"
+              :key="item.vKid"
+            >
+              <template slot="icon" v-if="item.isCB.id">
+                <div
+                  class="right-event"
+                  style="margin-right: 24px"
+                  :class="{ cb_blue: item.isCB.value }"
+                  @click.stop="setCanBiaoStatus(item)"
+                >
+                  <AppIcon
+                    style="margin-right: 4px"
+                    svg
+                    :color="!item.isCB.value ? '#2ABED1' : '#FB483D'"
+                    size="20"
+                    name="canbiao"
+                  />
+                  <span class="label-icon">{{
+                    item.isCB.value ? '终止参标' : '参标'
+                  }}</span>
+                </div>
+              </template>
+              <template slot="icon">
+                <div @click.stop="doCollection(item, index)">
+                  <span
+                    class="j-icon"
+                    :class="{
+                      'icon-star-fill': item.star,
+                      'icon-star-streak': !item.star
+                    }"
+                  ></span>
+                  <span>&nbsp;{{ item.star ? '已收藏' : '收藏' }}</span>
+                </div>
+              </template>
+            </ProjectCell>
+            <div
+              v-else-if="isLogin"
+              :key="[item.id, index].join('-')"
+              v-show="item.show"
+              class="middle-list-container"
+            >
+              <AdSingle
+                :class="item.className"
+                :ad="getContentAdID"
+                :showCloseIcon="true"
+                cache
+                key="ad-item"
+                :beforeOpen="beforeOpenAd"
+                @close="onListAdClose(item)"
+                class="ad-container"
+              />
+            </div>
+          </template>
+        </div>
+        <div class="max-see-more" v-if="showMoreThanMaxCount">
+          <p class="m-s-content">
+            <span>为您展示前{{ listState.count }}条,</span>
+            <template v-if="isFree">
+              <span
+                >点击<i
+                  class="highlight-text"
+                  @click="toLeaveInfoPage('jylab_see500_plus')"
+                  >免费查看更多信息</i
+                ></span
+              >
+            </template>
+            <template v-else>
+              <span>可细化筛选条件查看更多信息</span>
+            </template>
+          </p>
+        </div>
+        <AppEmpty v-show="listState.list.length === 0 && listState.finished">
+          <div class="empty-text" v-html="emptyText"></div>
+          <van-button
+            class="feedback-button"
+            plain
+            size="small"
+            type="primary"
+            @click="feedback"
+            >意见反馈</van-button
+          >
+        </AppEmpty>
+      </van-list>
+      <div class="table-container" v-show="pageState.listTabActive === 'table'">
+        <div class="scroll" v-show="listState.list.length">
+          <table class="table">
+            <tr class="table-header">
+              <td v-if="containsNiJian">项目代码</td>
+              <td>项目名称</td>
+              <td v-if="!containsNiJian">公告类型</td>
+              <td v-if="containsNiJian">审批事项</td>
+              <td v-if="containsNiJian">审批结果</td>
+              <td v-if="containsNiJian">审批时间</td>
+              <td v-if="!containsNiJian">预算(万元)</td>
+              <td v-if="!containsNiJian">招标单位</td>
+              <td v-if="!containsNiJian">开标时间</td>
+              <td v-if="!containsNiJian">中标单位</td>
+              <td v-if="!containsNiJian">中标金额<br />(万元)</td>
+              <td>发布时间</td>
+            </tr>
+            <tr
+              v-for="item in listState.list.slice(0, 20)"
+              class="table-content-tr"
+              v-visited:content="item.id"
+              @click="goToDetail(item)"
+              :key="item.id"
+            >
+              <!-- 项目代码 -->
+              <td v-if="containsNiJian">
+                {{ item.projectCode ? item.projectCode : '-' }}
+              </td>
+              <!-- 项目名称 -->
+              <td
+                v-html="
+                  replaceKeyword(
+                    item.projectName ? item.projectName : item.title,
+                    pageState.splitKeys
+                  )
+                "
+              ></td>
+              <!-- 公告类型 -->
+              <td v-if="!containsNiJian">
+                {{ item.subtype ? `${item.subtype}公告` : '-' }}
+              </td>
+              <!-- 审批事项 -->
+              <td v-if="containsNiJian">
+                {{ item.approveContent ? item.approveContent : '-' }}
+              </td>
+              <!-- 审批结果 -->
+              <td v-if="containsNiJian">
+                {{ item.approveStatus ? item.approveStatus : '-' }}
+              </td>
+              <!-- 审批时间 -->
+              <td v-if="containsNiJian">
+                {{ item.approveTime ? item.approveTime : '-' }}
+              </td>
+              <td v-if="!containsNiJian">
+                {{ formatTypeofItemKey(item.budget, 'money-table') }}
+              </td>
+              <!-- 招标单位 -->
+              <td v-if="!containsNiJian">{{ item.buyer || '-' }}</td>
+              <!-- 开标时间 -->
+              <td v-if="!containsNiJian">
+                {{ formatTypeofItemKey(item.bidOpenTime, 'date') }}
+              </td>
+              <!-- 中标单位 -->
+              <td v-if="!containsNiJian">
+                {{
+                  Array.isArray(item.winner)
+                    ? item.winner.join(',')
+                    : item.winner || ''
+                }}
+              </td>
+              <td v-if="!containsNiJian">
+                {{ formatTypeofItemKey(item.bidAmount, 'money-table') }}
+              </td>
+              <td>
+                {{
+                  item.publishTime
+                    ? dateFormatter(item.publishTime * 1000, 'yyyy-MM-dd')
+                    : '-'
+                }}
+              </td>
+            </tr>
+          </table>
+        </div>
+        <div class="to-look-more" v-show="listState.list.length > 20">
+          <van-button plain size="small" type="primary" @click="dataExport"
+            >查看更多</van-button
+          >
+        </div>
+        <AppEmpty v-show="listState.list.length === 0 && listState.finished">
+          <div class="empty-text" v-html="emptyText"></div>
+          <van-button
+            class="feedback-button"
+            plain
+            size="small"
+            type="primary"
+            @click="feedback"
+            >意见反馈</van-button
+          >
+        </AppEmpty>
+      </div>
+    </div>
+    <SearchFilterHistoryDialog
+      v-model="pageState.saveFilterDialog"
+      :content="pageState.calcFilterParams"
+    />
+    <OneKeySubscribeDialog
+      v-model="pageState.oneKeySub.prompt"
+      :keywords="filters.keywords"
+      :areaSet="pageState.oneKeySub.areaSet"
+    />
+    <popupDataexport
+      ref="popup_dataExport"
+      @next="next_export"
+    ></popupDataexport>
+  </div>
+</template>
+
+<script>
+import qs from 'qs'
+import { mapState, mapGetters, mapActions } from 'vuex'
+import { Cell, Tab, Tabs, List, Button, Tag, Popup } from 'vant'
+import bidStatusNode from '@/components/bid-update/BidUpdate'
+import AdSingle from '@/components/ad/Ad'
+import CollectionFilters from '@/views/collection/components/filters'
+import SearchFilterHistoryDialog from '@/components/search/bidding/filterHistoryDialog'
+import OneKeySubscribeDialog from '@/components/search/bidding/oneKeySubscribeDialog'
+import RecommendCard from '@/components/recommend/'
+import popupDataexport from '@/components/dataExport/popupDataexport.vue'
+import { AppIcon, AppEmpty, ProjectCell } from '@/ui'
+import { getFreeSubscribeInfo } from '@/api/modules/public'
+import { LINKS } from '@/data'
+import { throttle } from 'lodash'
+import toLogin from '@/utils/mixins/modules/to-login'
+import { mixinPoints } from '@/utils/mixins/modules/points'
+import {
+  moneyUnit,
+  formatMoney,
+  getRandomString,
+  getRandomNumber,
+  openAppOrWxPage,
+  replaceKeyword,
+  dateFormatter,
+  calcNotExactTime,
+  openLinkOfOther,
+  InfoTypeTransform,
+  FilterHistoryViewModel2AjaxModel,
+} from '@/utils'
+import {
+  getBiddingSearchList,
+  freeSubscribeKeys,
+  searchIndexDataExport,
+  selectEnt,
+  getBiddingFilterList,
+  freeUserNeedOneKeySubscribe,
+  getBidColPower,
+  ajaxCanBiaoStatus,
+  ajaxCanBiaoAction,
+  ajaxGetKeepLabels
+} from '@/api/modules'
+
+export default {
+  name: 'SearchResultBidding',
+  mixins: [toLogin, mixinPoints],
+  components: {
+    [Button.name]: Button,
+    [Cell.name]: Cell,
+    [Tab.name]: Tab,
+    [Tabs.name]: Tabs,
+    [List.name]: List,
+    [Tag.name]: Tag,
+    [Popup.name]: Popup,
+    CollectionFilters,
+    SearchFilterHistoryDialog,
+    OneKeySubscribeDialog,
+    AppIcon,
+    AppEmpty,
+    ProjectCell,
+    AdSingle,
+    RecommendCard,
+    bidStatusNode,
+    popupDataexport
+  },
+  inject: {
+    topSearch: {
+      default: () => {}
+    }
+  },
+  data() {
+    return {
+      conf: {
+        maxHeight: 0,
+        // 已存筛选条件数量
+        savedFilterListMaxCount: 10,
+        // 顶部精简列表/详细列表切换滚动显示隐藏距离
+        tabSwitchShowOffset: 150,
+        listTabActiveStorageKey: 'bidding_listTabActive',
+        leaveSource: {
+          article: 'article_collection'
+        },
+        tabListFree: [
+          {
+            title: '列表',
+            cardType: 'simplify',
+            name: 'list'
+          },
+          {
+            title: '表格',
+            name: 'table'
+          }
+        ],
+        tabList: [
+          {
+            title: '精简列表',
+            cardType: 'simplify',
+            name: 'list'
+          },
+          {
+            title: '详细列表',
+            name: 'detailedList',
+            cardType: 'detailed',
+            needPower: true
+          },
+          {
+            title: '表格',
+            name: 'table'
+          }
+        ],
+        defaultFilterState: {
+          moreKeywordsMode: {},
+          searchMode: ['0'],
+          label: [],
+          industry: {},
+          area: {},
+          priceCheckbox: [''],
+          price: {
+            start: '',
+            end: ''
+          },
+          dateTime: {
+            exact: 'thisyear'
+          },
+          infoType: [],
+          buyerClass: [],
+          fileExists: ['0'],
+          winnerConcat: [''],
+          buyerConcat: [''],
+          notKey: [],
+          buyerList: [],
+          winnerList: [],
+          agencyList: []
+        }
+      },
+      // 发送请求整理的数据
+      filters: {
+        moreKeywordsMode: {},
+        searchMode: ['0'],
+        keywords: '',
+        label: [],
+        industry: {},
+        area: {},
+        price: {
+          start: '',
+          end: ''
+        },
+        priceCheckbox: [''],
+        dateTime: {},
+        infoType: [],
+        buyerClass: [],
+        winnerConcat: [''],
+        buyerConcat: [''],
+        fileExists: ['0'],
+        notKey: [],
+        winnerList: [],
+        buyerList: [],
+        agencyList: []
+      },
+      listState: {
+        limit: 0,
+        refreshing: false,
+        loaded: false, // 是否加载完成
+        loading: false, // 是否加载中
+        finished: false, // 分页结束
+        pageNum: 1, // 当前页码
+        pageSize: 50, // 每页数据条数
+        count: 0, // 总共要返回多少条数据(根据权限等)
+        total: 0, // 数据库总共查询到多少条数据
+        offset: 80,
+        scrollTop: 0,
+        list: []
+      },
+      pageState: {
+        listTabActive: 'list', // list/detailedList/table
+        // 留资前缓存收藏信息在列表中的索引(String),用于恢复状态
+        searchModeAutoChangedSearch: false, // 当前请求是否为自动切换搜索模式而重发的请求
+        cacheStar: null,
+        // 数据导出cache,用于判断并恢复数据导出跳转
+        cacheExport: null,
+        savedFilterList: [],
+        currentFilterParams: {},
+        currentFilterParamsKey: '',
+        calcFilterParams: {},
+        freeSubInfo: {},
+        toSubThisKeyNoticeBarShow: true, // 底部直接订阅开关
+        splitKeywords: '', // 分词原始数据(后端返回直接保存到这)
+        splitKeys: [], // 分词数组
+        interceptLimit: 35, // 限制搜索长度
+        interceptOtherWords: '', // 限制后,后端截取字符
+        saveFilterDialog: false,
+        tabSwitchShow: true,
+        oneKeySub: {
+          prompt: false, // 一键订阅弹窗
+          checkLoaded: false, // 一键订阅弹窗查询
+          areaSet: false,
+          freeArea: true
+        },
+        bidColPower: {
+          isOld: false
+        },
+        adItem: {
+          id: 0,
+          random: 4,
+          show: true,
+          ad: true
+        },
+        tagLabelList: [] // 个人标签列表
+      },
+      BIInfo: {
+        ids: []
+      },
+      showBidStatus: false,
+      popupHeight: 'height: 440px',
+      projectCellInfo: {},
+      toggleSearchBlurData: {
+        show: false,
+        count: ''
+      },
+      scrollStatus: true
+    }
+  },
+  computed: {
+    ...mapState('user', ['power', 'userInfo']),
+    ...mapGetters('user', [
+      'restfulApiUserTypeDefault',
+      'vSwitch',
+      'isLogin',
+      'isFree',
+      'isSuper',
+      'isNewSuper', // 新超级订阅
+      'isMember',
+      'bigMemberPower',
+      'isBusiness'
+    ]),
+    noLoginOrFree() {
+      if (this.isLogin) {
+        return this.isFree
+      } else {
+        return true
+      }
+    },
+    getTimeFmt() {
+      return 'yyyy-MM-dd'
+    },
+    finishedText() {
+      const { list } = this.listState
+      if (list.length && !this.showMoreThanMaxCount) {
+        return '没有更多了'
+      } else {
+        return ''
+      }
+    },
+    showMoreThanMaxCount() {
+      const { count, total, finished } = this.listState
+      return this.isLogin && finished && total > count
+    },
+    defaultFilterState() {
+      const { defaultFilterState } = this.conf
+      if (!this.isFree) {
+        defaultFilterState.dateTime.exact = 'fiveyear'
+      }
+      return defaultFilterState
+    },
+    searchList() {
+      const listAdPosition = 4
+      const { list } = this.listState
+      const { adItem } = this.pageState
+      const { random, show } = adItem
+      let listArr = []
+      if (!show) {
+        list.forEach((item) => listArr.push(item))
+      } else if (list.length < listAdPosition && list.length > 0) {
+        listArr = list.concat(adItem)
+      } else {
+        // 添加广告位
+        for (let i = 0; i < list.length; i++) {
+          listArr.push(list[i])
+          if (i + 1 === random) {
+            listArr.push(adItem)
+          }
+        }
+      }
+      return listArr.map((tag) => {
+        return Object.assign({}, tag, {
+          tagList: (tag?.tagList || []).filter((v) => v !== '免费注册即可查看')
+        })
+      })
+    },
+    cardTypeTabList() {
+      const { tabList, tabListFree } = this.conf
+      if (!this.isLogin) {
+        return tabListFree
+      }
+      return tabList
+    },
+    cellCardType() {
+      const { tabList } = this.conf
+      const { listTabActive } = this.pageState
+      const target = tabList.find((t) => t.name === listTabActive)
+      if (target) {
+        return target.cardType || ''
+      } else {
+        return ''
+      }
+    },
+    // 筛选条件中包含拟建项目
+    containsNiJian() {
+      const { infoType } = this.filters
+      return infoType.includes('拟建')
+    },
+    getContentAdID() {
+      const { inWX } = this.$envs
+      return inWX ? 'jy-wxsearch-middle' : 'jyapp-wxsearch-middle'
+    },
+    savedFilterListCount() {
+      return this.pageState.savedFilterList.length
+    },
+    tabSearchInfoShow() {
+      const { total } = this.listState
+      return total > 0
+    },
+    additionalWordsArr() {
+      const { moreKeywordsMode } = this.filters
+      let additionalWordsArr = []
+
+      if (moreKeywordsMode) {
+        if (
+          Array.isArray(moreKeywordsMode.tags) &&
+          moreKeywordsMode.tags.length
+        ) {
+          additionalWordsArr = moreKeywordsMode.tags
+        }
+      }
+      return additionalWordsArr
+    },
+    emptyText() {
+      const { dateTime, keywords } = this.filters
+      const { exact: defaultExact } = this.defaultFilterState.dateTime
+      const exact = dateTime.exact || defaultExact
+      const tipMap = {
+        fiveyear: '近5年',
+        threeyear: '近3年',
+        thisyear: '近1年',
+        'lately-7': '近7天',
+        'lately-30': '近30天'
+      }
+      const tipKey = tipMap[exact] || ''
+      if (keywords || this.additionalWordsArr.length) {
+        return `对不起,没有找到<span class="highlight-text"> ${tipKey} </span>相关匹配的信息,修改时间范围或换个搜索词试试吧`
+      } else {
+        return '请输入关键词试试吧'
+      }
+    },
+    interceptTipText() {
+      const { interceptLimit, interceptOtherWords } = this.pageState
+      if (interceptLimit && interceptOtherWords) {
+        return `“${interceptOtherWords}”及其后面的字词均被忽略,因为剑鱼标讯的查询限制在${interceptLimit}个汉字以内。`
+      } else {
+        return ''
+      }
+    },
+    tabSwitchShow() {
+      return this.pageState.tabSwitchShow
+    },
+    // 切换模糊搜索
+    toggleBlurModeTip() {
+      const isBlurMode = this.filters.searchMode.indexOf('1') !== -1
+      let canShow = isBlurMode ? false : this.toggleSearchBlurData.show
+      if (this.listState.loading) {
+        canShow = false
+      }
+      const result = {
+        show: canShow,
+        count: this.toggleSearchBlurData.count
+      }
+      return result
+    }
+  },
+  watch: {
+    'topSearch.input'(n, o) {
+      if (o && o.length === 1 && !n) {
+        this.clear()
+      }
+    },
+    'pageState.listTabActive'(n, o) {
+      if (n === 'table') {
+        // toast提示
+        if (this.listState.list.length) {
+          this.$toast({
+            duration: 5000,
+            message:
+              '推荐使用电脑浏览器访问剑鱼标讯网站\njianyu360.cn查看数据表格,体验更佳。'
+          })
+        }
+      } else {
+        this.$toast.clear()
+      }
+    }
+  },
+  async beforeRouteLeave(to, from, next) {
+    await this.closeAllPopup()
+    next()
+  },
+  created() {
+    this.restoreDefaultScope()
+    // 恢复详细列表/精简列表
+    this.restoreListTabActive()
+    // 未登录时候,初始化一些东西
+    this.onNoLoginInit()
+
+    // url取参判断当前是否强制不走缓存
+    this.checkIsDisabledCache()
+
+    const restored = this.restoreState()
+    this.restored = restored
+    if (restored) {
+      // do something
+      this.checkLeaveInfo()
+      // this.checkDataExport()
+    } else {
+      this.getBidColPower()
+      this.initDefaultFilterState()
+      this.initAdInfo()
+      if (this.isLogin) {
+        this.getFilterHistoryList()
+      }
+    }
+    this.getAjaxGetKeepLabels()
+  },
+  mounted() {
+    // 历史筛选恢复需要在初始化完成之后
+    // 历史筛选恢复会覆盖url参数
+    this.restoreStateFromRouteParams()
+    this.calcInitFilters()
+    this.calcDropdownMenuMaxHeight()
+    this.$nextTick(() => {
+      const scrollWrap = this.$refs.listContainer
+      scrollWrap.addEventListener('scroll', throttle(this.scrollWrapFn, 500))
+    })
+  },
+  methods: {
+    ...mapActions('search', ['setHistory']),
+    formatMoney,
+    dateFormatter,
+    replaceKeyword,
+    // 获取个人标签列表
+    async getAjaxGetKeepLabels () {
+      const { error_code: code, data } = await ajaxGetKeepLabels()
+      if (code === 0 && data) {
+        data.forEach((item) => {
+          item.type = false
+        })
+        this.pageState.tagLabelList = data
+      }
+    },
+    // 切换到模糊搜索
+    doToggleSearchBlurMode() {
+      this.switchSearchMode(1)
+      this.doSearch()
+    },
+    async showSearchModeHelp() {
+      return await this.$dialog.alert({
+        title: '搜索模式',
+        messageAlign: 'left',
+        className: 'j-confirm-dialog',
+        confirmButtonText: '我知道了',
+        message:
+          '<p>精准搜索: 搜索结果必须完全包含完整的关键词。如搜索"医疗设备" ,搜索结果一定完整包含“医疗设备”才能被搜索到,而“医疗的设备”或“设备医疗”的项目不会被搜索到。</p><br /><p>模糊搜索: 系统会先自动智能分词然后再进行搜索。如搜索"医疗设备" ,系统会自动分成“医疗”“设备”然后进行搜索,只要项目中出现“医疗”和“设备”都会被搜索到,前提是两个词必须一同出现在一则公告内,不分先后顺序。</p>'
+      })
+    },
+    /**
+     * 针对多类型的接口字段判断,可根据类型执行对应函数,没有则返回默认值
+     * @param params - 参数
+     * @param type  - 类型
+     * @param spare - 默认值
+     * @returns {string|string|string}
+     */
+    formatTypeofItemKey(params, type, spare = '-') {
+      let formatFn = () => spare
+      switch (type) {
+        case 'money': {
+          formatFn = (params) => this.formatMoney(Number(params))
+          break
+        }
+        case 'money-table': {
+          formatFn = (params) =>
+            this.formatMoney(Number(params), { type: 'number', level: 1 })
+          break
+        }
+        case 'date': {
+          formatFn = (params) => this.dateFormatter(Number(params) * 1000)
+          break
+        }
+        case 'date-ms': {
+          formatFn = (params) =>
+            this.dateFormatter(Number(params) * 1000, 'yyyy-MM-dd')
+          break
+        }
+      }
+      if (params) {
+        return typeof params === 'string' ? params : formatFn(params)
+      }
+      return spare
+    },
+    submit() {
+      this.doSearch()
+    },
+    clear() {
+      this.$router.back()
+    },
+    calcDropdownMenuMaxHeight() {
+      const { maxHeight } = this.conf
+      const { searchFilters } = this.$refs
+      if (!maxHeight) {
+        const h5Offset = this.$envs.inH5 ? 120 : 0
+        const filterDOM = searchFilters?.$el
+        const domTop =
+          filterDOM.getBoundingClientRect().top + filterDOM.clientHeight
+        this.conf.maxHeight = `calc(100vh - ${domTop + h5Offset}px)`
+      }
+    },
+    initDefaultFilterState() {
+      Object.assign(this.filters, JSON.parse(JSON.stringify(this.defaultFilterState)))
+    },
+    onNoLoginInit() {
+      if (this.isLogin) return
+    },
+    calcInitFilters() {
+      // 计算选择器内容
+      // 1. 计算时间
+      const { dateTime } = this.filters
+      if (!dateTime.start && !dateTime.end) {
+        // 没有计算过精确时间
+        if (dateTime && dateTime.exact !== 'exact') {
+          const exactTime = calcNotExactTime(dateTime.exact)
+          Object.assign(dateTime, exactTime)
+        }
+      }
+    },
+    initAdInfo() {
+      if (!this.isLogin) return
+      // 信息流广告位点击关闭按钮后,不再出现。浏览器关闭后恢复
+      const hasClosedAd = this.$storage.get(
+        `${this.getContentAdID}--closed`,
+        false,
+        {
+          storage: sessionStorage
+        }
+      )
+      Object.assign(this.pageState.adItem, {
+        id: getRandomString(8).toLowerCase(),
+        show: hasClosedAd !== '1',
+        random: getRandomNumber(4, 4)
+      })
+    },
+    onListAdClose(item) {
+      item.show = false
+      this.$storage.set(`${this.getContentAdID}--closed`, '1', {
+        storage: sessionStorage
+      })
+    },
+    async onNoPower(type = '') {
+      const noPowerMessageMap = {
+        default: '立享更多搜索权限\n寻找商机更精准',
+        detailList:
+          '立享列表展示更多公告关键信息,例如:采购单位、中标单位、招标代理机构等,提高公告查看效率'
+      }
+      const message = noPowerMessageMap[type] || noPowerMessageMap.default
+
+      try {
+        await this.$dialog.confirm({
+          title: '开通超级订阅',
+          message,
+          className: 'j-confirm-dialog',
+          confirmButtonText: '去开通'
+        })
+        this.saveState()
+        if (!this.isLogin) {
+          return openLinkOfOther(LINKS.APP登录页.app, {
+            query: {
+              url: '/jy_mobile/common/order/create/svip'
+            }
+          })
+        }
+        this.$router.push('/common/order/create/svip')
+      } catch (error) {}
+    },
+    async getBidColPower() {
+      const { data = {}, error_code: code = 0 } = await getBidColPower()
+      if (code === 0 && data) {
+        Object.assign(this.pageState.bidColPower, data)
+        this.restoreStateFromRouteParams()
+      }
+    },
+    closeAllPopup() {
+      return new Promise((resolve) => {
+        this.$refs.searchFilters.closePopup(-1)
+        setTimeout(resolve, 301)
+      })
+    },
+    restoreStateFromRouteParams() {
+      const { searchFilters = {} } = this.$refs
+      let { filters } = this.$route.params
+      if (!filters) {
+        // 取标讯详情页-推荐项目模块-查看更多 跳转过来带的筛选条件
+        filters = this.$storage.get('bidding-search-filters-restore', false, {
+          storage: sessionStorage,
+          login: true
+        })
+      }
+      console.log(filters)
+      if (!filters || Object.keys(filters).length === 0) return
+      const { getScopeOptions = [], getTimeOptions = [] } = searchFilters
+      this.topSearch.input = filters.keywords.join(' ')
+      // 恢复搜索范围
+      if (filters.label) {
+        // 只恢复可用的部分
+        const canUseOptions = getScopeOptions
+          .filter((item) => !item.disabled)
+          .map((item) => item.key)
+        this.filters.label = filters.label.filter((item) =>
+          canUseOptions.includes(item)
+        )
+      }
+      // 行业
+      if (filters.industry) {
+        const key = 'industry'
+        this.$set(this.filters, key, filters[key])
+      }
+      if (filters.area) {
+        // 只恢复可用的部分
+        if (this.isFree) {
+          for (const key in filters.area) {
+            filters.area[key] = []
+          }
+        }
+        this.$set(this.filters, 'area', filters.area)
+      }
+      if (filters.price) {
+        const key = 'price'
+        if (filters[key].start || filters[key].end) {
+          this.filters.priceCheckbox = ['custom']
+        }
+        Object.assign(this.filters[key], filters[key])
+      }
+      if (filters.publishTime) {
+        // 只恢复可用的部分
+        const canUseOptions = getTimeOptions
+          .filter((item) => !item.disabled)
+          .map((item) => item.key)
+        const { exact } = filters.publishTime
+        if (exact && canUseOptions.includes(exact)) {
+          this.$set(this.filters, 'dateTime', filters.publishTime)
+        }
+      }
+      // 附加词
+      const { additionalWords, wordsMode } = filters
+      const moreKeywordsModeState = {}
+      if (Array.isArray(additionalWords) && additionalWords.length) {
+        moreKeywordsModeState.tags = additionalWords
+      }
+      if (Array.isArray(wordsMode) && wordsMode.length) {
+        moreKeywordsModeState.wordsMode = wordsMode.join(',')
+      }
+      if (Object.keys(moreKeywordsModeState).length) {
+        this.$set(this.filters, 'moreKeywordsMode', moreKeywordsModeState)
+      }
+
+      // 搜索模式
+      if (Array.isArray(filters.searchMode) && filters.searchMode.length) {
+        this.filters.searchMode = filters.searchMode
+      }
+
+      // 附件
+      if (filters.fileExists) {
+        this.filters.fileExists = filters.fileExists
+      }
+
+      if (filters.notKey && !this.isFree) {
+        if (Array.isArray(filters.notKey)) {
+          this.filters.notKey = filters.notKey
+        } else {
+          this.filters.notKey = filters.notKey.split(',')
+        }
+      }
+      if (filters.buyerList && !this.isFree) {
+        if (Array.isArray(filters.buyerList)) {
+          this.filters.buyerList = filters.buyerList
+        } else {
+          this.filters.buyerList = filters.buyerList.split(',')
+        }
+      }
+      if (filters.winnerList && !this.isFree) {
+        if (Array.isArray(filters.winnerList)) {
+          this.filters.winnerList = filters.winnerList
+        } else {
+          this.filters.winnerList = filters.winnerList.split(',')
+        }
+      }
+      if (filters.agencyList && !this.isFree) {
+        if (Array.isArray(filters.agencyList)) {
+          this.filters.agencyList = filters.agencyList
+        } else {
+          this.filters.agencyList = filters.agencyList.split(',')
+        }
+      }
+
+      // 信息类型
+      if (filters.infoType) {
+        const key = 'infoType'
+        this.$set(this.filters, key, filters[key])
+      }
+      // 通用付费选项恢复
+      const usualPayFilter = ['buyerClass', 'winnerConcat', 'buyerConcat']
+      if (!this.isFree) {
+        for (const k in usualPayFilter) {
+          const key = usualPayFilter[k]
+          if (!filters[key]) {
+            continue
+          }
+          this.$set(this.filters, key, filters[key])
+        }
+      }
+    },
+    resetInterceptInfo() {
+      this.pageState.interceptLimit = 35
+      this.pageState.interceptOtherWords = ''
+    },
+    resetListState() {
+      Object.assign(this.listState, this.$options.data().listState)
+      this.resetInterceptInfo()
+    },
+    toLogin() {
+      return openLinkOfOther(LINKS.APP登录页.app, {
+        query: {
+          to: 'back'
+        }
+      })
+    },
+    doOpen(type) {
+      if (!this.isLogin) {
+        if (type !== 'label') {
+          return this.toLogin()
+        }
+      }
+    },
+    doSearch(conf = {}) {
+      const { from } = conf
+      if (from === 'searchModeAutoChangedSearch') {
+        // 当前搜索来自 自动切换搜索模式
+        // do something...
+      } else {
+        if (this.listState.loading) {
+          return
+        }
+        this.pageState.searchModeAutoChangedSearch = false
+      }
+
+      // 表格切换回列表
+      if (this.pageState.listTabActive === 'table') {
+        this.pageState.listTabActive = 'list'
+      }
+      // 重置页码
+      this.resetListState()
+      this.saveDefaultScope()
+      this.filters.keywords = this.topSearch.input
+      this.pageState.splitKeywords = ''
+      this.listState.loading = true
+      this.getList()
+    },
+    // 暂未用到
+    onRefresh: function () {
+      // 重置数据
+      this.listState.pageNum = 1
+      // 解除加载完成状态
+      this.listState.finished = false
+      // 重新加载数据
+      // 将 loading 设置为 true,表示处于加载状态
+      this.listState.loading = true
+      // 请求数据
+      this.getList()
+    },
+    async getList() {
+      const t = this.listState
+      const { area, city, district } =
+        FilterHistoryViewModel2AjaxModel.formatAreaCity(this.filters.area)
+      const infoType = InfoTypeTransform.listToMap(this.filters.infoType)
+      const infoTypeText = InfoTypeTransform.formatMapToList(infoType)
+      const { price, moreKeywordsMode } = this.filters
+      // 整理wordsMode和additionalWords
+      let wordsMode
+      let additionalWords
+
+      if (moreKeywordsMode) {
+        if (moreKeywordsMode.wordsMode) {
+          wordsMode = moreKeywordsMode.wordsMode - 0
+        }
+        if (
+          Array.isArray(moreKeywordsMode.tags) &&
+          moreKeywordsMode.tags.length
+        ) {
+          additionalWords = moreKeywordsMode.tags.join(',')
+        }
+      }
+
+      const params = {
+        pageNum: this.listState.pageNum,
+        pageSize: this.listState.pageSize,
+        reqType: '', // cache:空搜索缓存数据;lastNews:最新数据
+        keyWords: this.filters.keywords,
+        province: area,
+        city: city,
+        district: district,
+        subtype: infoTypeText.join(','), // 信息类型
+        publishTime: FilterHistoryViewModel2AjaxModel.formatTime(
+          this.filters.dateTime,
+          true,
+          '-'
+        ),
+        // publishTimeType: this.filters.dateTime?.exact,
+        searchMode: this.filters.searchMode.join('') - 0, // 搜索模式:0精准搜索(不进行系统分词) 1模糊搜索(进行系统分词)
+        wordsMode, // 搜索关键词模式;默认0:包含所有,1:包含任意
+        additionalWords,
+        label: this.filters.label.join(','), // 个人标签
+        price: FilterHistoryViewModel2AjaxModel.formatPrice(price),
+        industry: FilterHistoryViewModel2AjaxModel.formatIndustry(
+          this.filters.industry
+        ), // 行业
+        buyerClass: this.filters.buyerClass.join(','),
+        winnerTel: this.filters.winnerConcat.join(','),
+        buyerTel: this.filters.buyerConcat.join(','),
+        exclusionWords: this.filters.notKey.join(','),
+        buyer: this.filters.buyerList.join(','),
+        winner: this.filters.winnerList.join(','),
+        agency: this.filters.agencyList.join(','),
+        fileExists: this.filters.fileExists.join(','),
+        splitKeywords: this.pageState.splitKeywords
+      }
+      // if (!params.keyWords && !params.additionalWords) {
+      //   t.loading = false
+      //   t.finished = true
+      //   return
+      // }
+      console.table({
+        start: dateFormatter(this.filters.dateTime?.start),
+        end: dateFormatter(this.filters.dateTime?.end)
+      })
+      // 免费用户搜索结果查看大于11页时弹窗提示
+      // if (this.noLoginOrFree && this.listState.pageNum > 10) {
+      //   this.showFreeLimit10Dialog()
+      //   t.finished = true
+      //   return
+      // }
+
+      try {
+        const {
+          data = {},
+          error_code: code = 0,
+          error_msg: msg
+        } = await getBiddingSearchList(params, this.restfulApiUserTypeDefault)
+        const isLimited =
+          data.isLimit === 1 ||
+          data.isLimit === undefined ||
+          data.isLimit === null
+        if (code === 0 && data && isLimited) {
+          // 判断是否为刷新
+          if (t.refreshing) {
+            t.list = []
+            t.refreshing = false
+          }
+          // 请求完成后的回调
+          this.afterSearch(params, data)
+          // 列表赋值
+          const list = data.list
+          const count = data.count
+          const total = data.total
+          if (Array.isArray(list)) {
+            // 整理每一项
+            this.preSortList(list)
+            if (t.pageNum === 1) {
+              t.list = []
+            }
+            t.list = t.list.concat(list)
+            if (this.isLogin) {
+              this.getCanBiaoStatus(t.list)
+            }
+          } else {
+            // list不为数组,则直接finish
+            t.loaded = true
+            t.loading = false
+            t.finished = true
+          }
+
+          if (count && count !== -1) {
+            t.count = count
+          }
+          if (total) {
+            t.total = total
+          }
+
+          // 加载状态结束
+          t.loaded = true
+          t.loading = false
+
+          // 翻页
+          const hasNextPage = t.pageNum * t.pageSize < t.count
+          // let hasNextPage = true
+          // if (this.isFree && this.isLogin) {
+          //   // 免费用户有第11页,弹窗
+          //   hasNextPage = t.pageNum * t.pageSize <= t.count
+          // } else {
+          //   hasNextPage = t.pageNum * t.pageSize < t.count
+          // }
+
+          if (hasNextPage) {
+            t.pageNum++
+          } else {
+            t.finished = true
+          }
+        } else {
+          // 限制正文、附件查询
+          if (data) {
+            if (data.isLimit === -1) {
+              this.$toast('抱歉!由于系统繁忙暂时无法进行搜索,请1分钟后再试!')
+            } else if (data.isLimit === -2) {
+              this.$toast('抱歉!由于系统繁忙暂时无法进行搜索,请稍后再试!')
+            }
+          } else {
+            this.$toast(msg || '请求失败')
+          }
+          t.loaded = true
+          t.loading = false
+          t.finished = true
+        }
+      } catch (error) {
+        t.loaded = true
+        t.loading = false
+        t.finished = true
+        console.warn(error)
+      }
+    },
+    afterSearch(params = {}, data = {}) {
+      const {
+        list = [],
+        keyWords = '',
+        interceptOtherWords,
+        interceptLimit,
+        interceptKeyWords
+      } = data
+      this.pageState.splitKeywords = keyWords
+      if (keyWords) {
+        this.pageState.splitKeys = keyWords.split(' ')
+      } else {
+        const keywordsArr = this.filters.keywords.split(' ')
+        let additionalWordsArr = []
+        const { moreKeywordsMode } = this.filters
+        if (
+          Array.isArray(moreKeywordsMode.tags) &&
+          moreKeywordsMode.tags.length
+        ) {
+          additionalWordsArr = moreKeywordsMode.tags
+        }
+        this.pageState.splitKeys = keywordsArr.concat(additionalWordsArr)
+      }
+      // 限制数据赋值
+      this.pageState.interceptOtherWords = interceptOtherWords
+      this.pageState.interceptLimit = interceptLimit
+      // 如果超出限制字符数,则替换输入框内容
+      if (interceptKeyWords) {
+        this.topSearch.input = interceptKeyWords
+      }
+      if (Array.isArray(list) && list.length) {
+        if (this.isFree && this.isLogin) {
+          if (params.keyWords) {
+            this.checkOneKeySub()
+          }
+        }
+        if (params.pageNum === 1) {
+          if (this.pageState.searchModeAutoChangedSearch) {
+            this.$toast('精准搜索无结果,已为您自动切换到模糊搜索')
+          }
+        }
+      }
+
+      this.checkToggleSearchMode({
+        pageNum: params.pageNum,
+        count: data.total || 0,
+        blurCount: data?.bCount || 0,
+        searchMode: params.searchMode
+      })
+
+      if (params.pageNum === 1) {
+        // 保存更多关键词到历史记录中
+        this.saveAdditionalWordsToHistory()
+      }
+    },
+    /**
+     * 检查是否需要切换模糊搜索、是否展示提示
+     * 1. 精准搜索无数据 (自动切换模糊搜索)
+     * 2. 精准搜索有数据,< 50,提示手动切换搜索模式
+     */
+    checkToggleSearchMode({
+      pageNum = 1,
+      searchMode = 0,
+      count = 0,
+      blurCount = 0
+    }) {
+      if (pageNum === 1) {
+        // 重置变量
+        this.toggleSearchBlurData.show = false
+        this.toggleSearchBlurData.count = ''
+
+        if (searchMode === 0) {
+          const canAutoToggleBlurMode = count < 1 && blurCount > count
+          const canShowToggleBlurModeTip =
+            count >= 1 && count < 50 && blurCount > count
+
+          if (canShowToggleBlurModeTip) {
+            this.toggleSearchBlurData.show = true
+            this.toggleSearchBlurData.count = blurCount
+          }
+
+          if (canAutoToggleBlurMode) {
+            this.switchSearchMode(1)
+            this.pageState.searchModeAutoChangedSearch = true
+            this.doSearch({ from: 'searchModeAutoChangedSearch' })
+          }
+        }
+      }
+    },
+    // 切换搜索模式:精准搜索0/模糊搜索1
+    switchSearchMode(m) {
+      this.filters.searchMode = [m + '']
+    },
+    // 获取参标状态
+    async getCanBiaoStatus(arr) {
+      let idArr = arr.map((item) => item.id)
+      if (idArr.length > 0) {
+        idArr = idArr.join(',')
+      } else {
+        idArr = ''
+      }
+      const { data, error_code: code } = await ajaxCanBiaoStatus({ ids: idArr })
+      if (code === 0 && data) {
+        arr.forEach((v) => {
+          data.forEach((m) => {
+            if (v.id === m.id) {
+              Object.assign(v.isCB, m)
+            }
+          })
+        })
+      }
+    },
+    // 参标操作
+    async setCanBiaoStatus(item) {
+      if (!this.isLogin) {
+        return openLinkOfOther(LINKS.APP登录页.app, {
+          query: {
+            to: 'back'
+          }
+        })
+      }
+      if (item.isCB?.value) {
+        this.$toast('如需终止参标,请在详情页进行操作。')
+        return
+      }
+      try {
+        const { error_code: code, data } = await ajaxCanBiaoAction('in', {
+          bidIds: item.id
+        })
+        if (code === 0 && data) {
+          // this.$toast('已参标,请前往我的参标项目列表查看。')
+          this.projectCellInfo = item
+          item.isCB.value = true
+          this.$set(item, 'isCB', {
+            id: item.id,
+            value: true
+          })
+          this.showBidStatus = true
+          this.$forceUpdate()
+        }
+      } catch (e) {}
+    },
+    setHeight(data) {
+      if (data === 2) {
+        this.popupHeight = 'height: 546px'
+      } else {
+        this.popupHeight = 'height: 440px'
+      }
+    },
+    // 更新投标状态成功
+    saveSuccess() {
+      this.showBidStatus = false
+    },
+    // 取消更新投标状态
+    cancelUpdate() {
+      this.showBidStatus = false
+    },
+    preSortList(list = []) {
+      if (!Array.isArray(list)) return
+      list.forEach(this.preSortItem)
+    },
+    preSortItem(item) {
+      if (!item) return
+      const { area, isCollected, projectInfo } = item
+      item.star = !!isCollected
+      // 参标参数
+      item.isCB = {
+        id: '',
+        value: 0
+      }
+      // 是否有附件
+      item.isFile = item?.fileExists || false
+      item.leftTopBadgeText =
+        item.site === '剑鱼信息发布平台' ? '业主委托项目' : ''
+      // 拟建项目独有参数
+      if (projectInfo) {
+        Object.assign(item, projectInfo)
+      }
+      const buyerClass =
+        item?.buyerClass && item?.buyerClass !== '其它'
+          ? item?.buyerClass
+          : undefined
+      // 标签
+      item.tagList = [
+        area || '全国',
+        buyerClass,
+        item?.type || item?.subtype,
+        // 有中标金额取中标金额,没有取预算,预算没有置空
+        this.formatTypeofItemKey(item?.bidAmount || item?.budget, 'money', '')
+      ].filter((v) => v)
+
+      item.dateTime = item.publishTime ? item.publishTime * 1000 : ''
+
+      // 整理企业画像数据
+      let winnerList = Array.isArray(item.winnerInfo) ? item.winnerInfo : []
+      winnerList = winnerList.map((w) => {
+        return {
+          text: w.winner,
+          id: w.winnerId
+        }
+      })
+
+      item.vKid = `${item.id}--${getRandomString(8).toLowerCase()}`
+
+      // 详细列表数据
+      item.detailList = [
+        {
+          label: '采购单位',
+          splitter: ':',
+          text: Array.isArray(item.buyer)
+            ? item.buyer.join(',')
+            : item.buyer || '',
+          highlightText: true,
+          detailTextSlot: 'buyerText'
+        },
+        {
+          label: '预算金额',
+          splitter: ':',
+          text: this.formatTypeofItemKey(item?.budget, 'money', '')
+        },
+        {
+          label: '代理机构',
+          splitter: ':',
+          text: item.agency || ''
+        },
+        {
+          label: '中标单位',
+          splitter: ':',
+          text: Array.isArray(item.winnerInfo)
+            ? item.winnerInfo.map((w) => w.winner).join(',')
+            : '',
+          detailTextSlot: 'winnerText',
+          children: winnerList
+        },
+        {
+          label: '中标金额',
+          splitter: ':',
+          text: this.formatTypeofItemKey(item?.bidAmount, 'money', '')
+        },
+        {
+          label: '报名截止日期',
+          splitter: ':',
+          text: item.signEndTime
+            ? dateFormatter(item.signEndTime * 1000, 'yyyy-MM-dd')
+            : ''
+        },
+        {
+          label: '投标截止日期',
+          splitter: ':',
+          text: item.bidEndTime
+            ? dateFormatter(item.bidEndTime * 1000, 'yyyy-MM-dd')
+            : ''
+        },
+        {
+          label: '开标日期',
+          splitter: ':',
+          text: this.formatTypeofItemKey(item.bidOpenTime, 'date-ms', '')
+        }
+      ]
+    },
+    goToDetail(item) {
+      const { id, industry } = item
+      const query = {
+        keywords: this.pageState.splitKeys.join('+'),
+        ...this.fromPointTask
+      }
+      if (industry) {
+        query.industry = industry
+      }
+      const targetMap = {
+        wx: `/article/content/${id}.html?${qs.stringify(query)}`,
+        app: `/jyapp/article/content/${id}.html?${qs.stringify(query)}`
+      }
+      this.saveState()
+      // if (!this.isLogin) {
+      //   return openLinkOfOther(LINKS.APP登录页.app, {
+      //     query: {
+      //       url: this.$envs.inWX ? targetMap.wx : targetMap.app
+      //     }
+      //   })
+      // }
+      openAppOrWxPage(targetMap)
+    },
+    // 保存更多关键词到历史记录中
+    saveAdditionalWordsToHistory() {
+      const { moreKeywordsMode } = this.filters
+      const tags = moreKeywordsMode.tags
+      if (Array.isArray(tags) && tags.length) {
+        tags.forEach((t) => {
+          // 历史记录新增
+          this.setHistory({
+            type: 'bidding',
+            item: {
+              label: t
+            }
+          })
+        })
+      }
+    },
+    calcProjectCardType(item) {
+      const type = this.cellCardType
+      return type
+    },
+    calcListTotalText(total) {
+      const e1 = 100000000
+      if (total > e1) {
+        const billionUnit = moneyUnit(total, undefined, 2)
+        return `超过${billionUnit}`
+      } else {
+        return total
+      }
+    },
+    async showFreeLimit10Dialog() {
+      await this.$dialog.confirm({
+        title: '您暂无使用权限',
+        message: '免费用户开通大会员,查看更多招标\n项目,畅想商机不受限!',
+        className: 'j-confirm-dialog',
+        messageAlign: 'center',
+        confirmButtonText: '免费体验'
+      })
+      // 直接跳转留资
+      this.toLeaveInfoPage('jylab_see500_plus')
+    },
+    checkLeaveInfo() {
+      if (!this.isLogin) return false
+      const { cacheStar } = this.pageState
+      if (!cacheStar) return false
+      const options = {
+        storage: sessionStorage,
+        prefix: ''
+      }
+      // const backStatus = this.$storage.get('salesBackStatus', false, options)
+      // const backPayload = this.$storage.get('salesBackData', {}, options)
+      const backStatus = sessionStorage.getItem('salesBackStatus')
+      let backPayload = sessionStorage.getItem('salesBackData')
+      if (backPayload) {
+        backPayload = JSON.parse(backPayload)
+      }
+      const { list } = this.listState
+      const cacheStarItem = list[cacheStar]
+      if (backStatus) {
+        // 留资成功后恢复状态
+        const { leaveSource } = this.conf
+        switch (backPayload.type) {
+          case leaveSource.article: {
+            this.doCollection(cacheStarItem, cacheStar)
+            break
+          }
+        }
+      } else {
+        // 登录离开后恢复状态或者其他恢复
+        this.doCollection(cacheStarItem, cacheStar)
+      }
+
+      this.pageState.cacheStar = null
+      this.$storage.rm('salesBackStatus', options)
+      this.$storage.rm('salesBackData', options)
+      return true
+    },
+    checkDataExport() {
+      if (!this.isLogin) return false
+      const { cacheExport } = this.pageState
+      if (!cacheExport) return false
+      this.dataExport()
+      return true
+    },
+    // 收藏
+    async doCollection(item, index) {
+      if (!this.isLogin) {
+        this.pageState.cacheStar = index + ''
+        this.saveState()
+        return openLinkOfOther(LINKS.APP登录页.app, {
+          query: {
+            to: 'back'
+          }
+        })
+      }
+      this.$keep.action({
+        status: item.star,
+        id: item.id,
+        beforeRedirect: () => {
+          this.pageState.cacheStar = index + ''
+          this.saveState()
+        },
+        complete: ({ type, message }) => {
+          if (type) {
+            item.star = !item.star
+            this.$forceUpdate()
+          }
+        }
+      })
+    },
+    // 老接口通用的参数
+    getFilterParams2() {
+      const { moreKeywordsMode } = this.filters
+      const { area, city, district } =
+        FilterHistoryViewModel2AjaxModel.formatAreaCity(this.filters.area)
+      const infoType = InfoTypeTransform.listToMap(this.filters.infoType)
+      const infoTypeText = InfoTypeTransform.formatMapToList(infoType)
+      const fileExists = this.filters.fileExists.join(',')
+      // 整理wordsMode和additionalWords
+      let wordsMode
+      let additionalWords
+
+      if (moreKeywordsMode) {
+        if (moreKeywordsMode.wordsMode) {
+          wordsMode = moreKeywordsMode.wordsMode - 0
+        }
+        if (
+          Array.isArray(moreKeywordsMode.tags) &&
+          moreKeywordsMode.tags.length
+        ) {
+          additionalWords = moreKeywordsMode.tags.join(',')
+        }
+      }
+
+      const params = {
+        searchvalue: this.topSearch.input,
+        label: this.filters.label.join(','), // 搜索范围
+        scope: area, // 地区省份(数据导出接口用到)
+        area, // 地区省份(保存筛选接口用到)
+        city,
+        district,
+        industry: FilterHistoryViewModel2AjaxModel.formatIndustry(
+          this.filters.industry
+        ), // 行业
+        minprice: this.filters.price.start,
+        maxprice: this.filters.price.end,
+        publishtime: FilterHistoryViewModel2AjaxModel.formatTime(
+          this.filters.dateTime
+        ),
+        subtype: infoTypeText.join(','), // 信息类型
+        buyerclass: this.filters.buyerClass.join(','),
+        winnertel: this.filters.winnerConcat.join(','),
+        buyertel: this.filters.buyerConcat.join(','),
+        fileExists: fileExists || '0',
+        notkey: this.filters.notKey.join(','),
+        buyer: this.filters.buyerList.join(','),
+        winner: this.filters.winnerList.join(','),
+        agency: this.filters.agencyList.join(','),
+        searchMode: this.filters.searchMode.join('') - 0,
+        wordsMode,
+        additionalWords
+      }
+      return params
+    },
+    // 数据导出
+    async dataExport(type) {
+      if (!this.isLogin) {
+        this.pageState.cacheExport = true
+        this.saveState()
+        return openLinkOfOther(LINKS.APP登录页.app, {
+          query: {
+            to: 'back'
+          }
+        })
+      } else {
+        this.pageState.cacheExport = null
+      }
+      if (
+        this.listState.total > 20000 &&
+        type !== 'popupClick' &&
+        this.$refs.popup_dataExport.isPrompt
+      ) {
+        this.$refs.popup_dataExport.show = true
+        return
+      }
+      const loading = this.$toast.loading({ duration: 0 })
+      const params = this.getFilterParams2()
+      try {
+        // 判断是否选择过企业。未选择过调用 selectEnt 选择一个默认的企业
+        await selectEnt()
+        const { _id } = await searchIndexDataExport(params)
+        if (!_id) return
+        this.saveState()
+        this.$router.push({
+          path: '/common/order/create/dataexport',
+          query: {
+            source: 'd',
+            id: _id
+          }
+        })
+      } catch (error) {
+      } finally {
+        loading.clear()
+      }
+    },
+    next_export(obj) {
+      console.log(obj)
+      this.dataExport('popupClick')
+    },
+    // 获取免费用户信息
+    async getFreeSubInfo() {
+      const { data = {}, error_code: code = 0 } = await getFreeSubscribeInfo()
+      if (code === 0) {
+        Object.assign(this.pageState.freeSubInfo, data)
+      }
+      return this.pageState.freeSubInfo
+    },
+    toLeaveInfoPage(source) {
+      if (!source) return
+      this.saveState()
+      this.$leaveInfo.toLeaveInfoPage({ source })
+    },
+    toSetArea() {
+      this.saveState()
+      openAppOrWxPage(LINKS.省份订阅包设置省份)
+    },
+    // 去用户反馈页面
+    feedback() {
+      // 判断是否登陆,未登录去登陆
+      this.saveState()
+      openAppOrWxPage(LINKS.用户反馈)
+    },
+    beforeOpenAd() {
+      this.saveState()
+      return true
+    },
+    async getFilterHistoryList() {
+      try {
+        const { data, error_code: code } = await getBiddingFilterList()
+        if (code === 0) {
+          if (Array.isArray(data)) {
+            this.pageState.savedFilterList = data || []
+          }
+        }
+      } catch (error) {
+        console.warn(error)
+      }
+    },
+    // 检查一键订阅是否打开(仅免费用户检查)
+    async checkOneKeySub() {
+      const { checkLoaded } = this.pageState.oneKeySub
+      if (checkLoaded) return
+      try {
+        const { error_code: code = 0, data } =
+          await freeUserNeedOneKeySubscribe()
+        if (code === 0 && data) {
+          Object.assign(this.pageState.oneKeySub, data)
+        }
+      } catch (error) {
+      } finally {
+        this.pageState.oneKeySub.checkLoaded = true
+      }
+    },
+    saveDefaultScope() {
+      const { label: defaultScope } =
+        this.$options.data().conf.defaultFilterState
+      // 只缓存defaultScope中的项['title', 'content']
+      const { label } = this.filters
+      let cachedArr = defaultScope.filter((item) => {
+        return label.includes(item)
+      })
+      if (cachedArr.length === 0) {
+        cachedArr = defaultScope
+      }
+      // 持久存储scope
+      this.$storage.set('biddingSearchScopePrevSelected', cachedArr)
+    },
+    // url取参判断当前是否强制不走缓存
+    checkIsDisabledCache() {
+      const options = {
+        storage: sessionStorage
+      }
+      // 如果当前url有参数,nocache=1则不走缓存
+      const query = this.$route.query
+      const { nocache } = query
+      if (nocache) {
+        this.$storage.rm(this.$route.path, options)
+        this.$router.replace({
+          path: this.$route.path,
+          query: {
+            ...query,
+            nocache: undefined
+          }
+        })
+      }
+    },
+    saveState() {
+      const { listContainer } = this.$refs
+      this.listState.scrollTop = listContainer
+        ? parseInt(listContainer.scrollTop)
+        : 0
+      this.$storage.set(
+        this.$route.path,
+        {
+          cacheMoreFilters: this.cacheMoreFilters,
+          filters: this.filters,
+          listState: this.listState,
+          pageState: this.pageState,
+          BIInfo: this.BIInfo,
+          toggleSearchBlurData: this.toggleSearchBlurData,
+        },
+        { storage: sessionStorage }
+      )
+    },
+    restoreListScroll() {
+      this.$nextTick(() => {
+        const { listContainer } = this.$refs
+        listContainer.scrollTop = this.listState.scrollTop - 0
+      })
+    },
+    // 恢复以前选过的scope
+    restoreDefaultScope() {
+      const label = this.$storage.get('biddingSearchScopePrevSelected', false)
+      if (Array.isArray(label) && label.length) {
+        this.conf.defaultFilterState.label = label
+      }
+    },
+    restoreState() {
+      const options = {
+        storage: sessionStorage
+      }
+      const storage = this.$storage.get(this.$route.path, false, options)
+      if (storage) {
+        let actionCBInfo = sessionStorage.getItem('actionCBInfo')
+        if (actionCBInfo) {
+          actionCBInfo = JSON.parse(actionCBInfo)
+          storage.listState.list.forEach((v) => {
+            if (v.id === actionCBInfo.id) {
+              v.isCB = actionCBInfo
+            }
+          })
+          sessionStorage.removeItem('actionCBInfo')
+        }
+        Object.assign(this._data, storage)
+        this.$storage.rm(this.$route.path, options)
+        this.restoreListScroll()
+      }
+      return !!storage
+    },
+    // 恢复以前选过的tab
+    restoreListTabActive() {
+      const { listTabActiveStorageKey } = this.conf
+      const storage = this.$storage.get(listTabActiveStorageKey, '')
+      if (storage) {
+        const tab = this.cardTypeTabList.find((t) => t.name === storage)
+        if (tab) {
+          let active = storage
+          if (tab.needPower) {
+            if (this.noLoginOrFree) {
+              active = 'list'
+            }
+          }
+          this.pageState.listTabActive = active
+        } else {
+          this.$storage.rm(listTabActiveStorageKey)
+        }
+      }
+    },
+    onListTabChange(name) {
+      if (name === 'table') return
+      const { listTabActiveStorageKey } = this.conf
+      this.$storage.set(listTabActiveStorageKey, name)
+    },
+    onFilterConfirm() {
+      this.doSearch()
+    },
+    beforeTabActiveChange(name) {
+      if (name === 'detailedList') {
+        if (this.noLoginOrFree) {
+          // 弹窗
+          this.onNoPower('detailList')
+          return false
+        }
+      }
+      return true
+    },
+    scrollWrapFn(e) {
+      const scrollDOM = e.target
+      const scrollTop = scrollDOM.scrollTop
+
+      if (scrollTop >= this.conf.tabSwitchShowOffset) {
+        this.pageState.tabSwitchShow = false
+      } else {
+        this.pageState.tabSwitchShow = true
+      }
+      this.scrollStatus = scrollTop < 60
+    }
+  }
+}
+</script>
+<style lang="scss">
+.winner-item:active {
+  background: transparent;
+}
+#app.in-bi {
+  .hd-search-group {
+    margin-top: 0;
+    padding-left: 16px;
+    .van-icon-clear {
+      display: none;
+    }
+  }
+  .hd-search-group .back-icon {
+    display: none;
+  }
+}
+</style>
+<style lang="scss" scoped>
+.tip-toggle-search-mode-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 8px 16px;
+  margin-top: 0;
+  padding: 12px 16px;
+  background: linear-gradient(180deg, #e8ffff 0%, #ffffff 100%);
+  border: 0.5px solid #2abed1;
+  border-radius: 8px;
+  color: #5f5e64;
+  font-size: 13px;
+  line-height: 20px;
+  .tip-action {
+    display: inline-flex;
+    align-items: center;
+  }
+  button {
+    margin-top: 12px;
+    background: #2abed1;
+    padding: 5px 12px;
+    border-radius: 4px;
+    color: #f7f9fa;
+  }
+}
+.page-collection-index {
+  background: #f5f5f5;
+  ::v-deep {
+    .van-dropdown-menu {
+      .van-dropdown-menu__bar {
+        height: 40px;
+        border: none;
+      }
+    }
+  }
+}
+.linear-main-bg {
+  background: $gradient_search_header;
+}
+.data-export,
+.save-filter {
+  display: flex;
+  align-items: center;
+  line-height: 30px;
+  font-size: 14px;
+  color: $main;
+  .text {
+    margin-left: 4px;
+    white-space: nowrap;
+  }
+}
+.tab-switch {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 2px 16px;
+  padding-left: 8px;
+  background: #fff;
+  .tab-right {
+    display: flex;
+    align-items: center;
+  }
+  ::v-deep {
+    .van-tabs__wrap {
+      height: 32px;
+    }
+    .van-tabs__line {
+      width: 2em;
+      height: 2px;
+    }
+    .van-tabs__nav--line.van-tabs__nav--complete {
+      padding-left: 0;
+      padding-right: 0;
+    }
+  }
+}
+
+.tab-search-info {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 16px;
+  line-height: 30px;
+  .tab-right {
+    display: flex;
+    align-items: center;
+  }
+}
+.search-total-count {
+  font-size: 14px;
+}
+
+.intercept-tip-container {
+  padding: 8px 16px;
+  font-size: 14px;
+  color: #1d1d1d;
+  line-height: 20px;
+  ::v-deep {
+    .iconfont {
+      color: $main;
+    }
+  }
+}
+
+.list-item {
+  &:not(:first-of-type) {
+    margin-top: 8px;
+  }
+}
+.middle-list-container {
+  + .list-item {
+    margin-top: 0;
+  }
+}
+
+.ad-container {
+  margin: 12px;
+}
+.empty-text {
+  font-size: 15px;
+  color: #5f5e64;
+  text-align: center;
+  line-height: 22px;
+}
+.feedback-button {
+  width: 165px;
+  height: 40px;
+  margin-top: 48px;
+}
+.table-container {
+  flex: 1;
+  .scroll {
+    overflow: scroll;
+  }
+}
+.table {
+  min-width: 1000px;
+  margin: 16px;
+  font-size: 14px;
+  border-collapse: collapse;
+  tr {
+    td {
+      padding: 6px 12px;
+      text-align: center;
+      border: 1px solid #e5e5e5;
+      &:nth-child(1) {
+        width: 196px;
+      }
+      &:nth-child(2) {
+        width: 86px;
+      }
+      &:nth-child(3) {
+        width: 136px;
+      }
+      &:nth-child(4) {
+        width: 150px;
+      }
+      &:nth-child(5) {
+        width: 124px;
+      }
+      &:nth-child(6) {
+        width: 136px;
+      }
+      &:nth-child(7) {
+        width: 80px;
+      }
+      &:nth-child(8) {
+        width: 108px;
+      }
+    }
+  }
+  .table-header {
+    font-size: 13px;
+    background-color: $gray_1;
+  }
+  .table-content-tr {
+    &.visited {
+      color: #c0c4cc;
+    }
+    td {
+      line-height: 20px;
+      &:nth-child(1) {
+        text-align: left;
+      }
+    }
+  }
+}
+.max-see-more {
+  padding: 12px 16px;
+  font-size: 14px;
+  line-height: 22px;
+  color: #686868;
+  text-align: center;
+}
+.to-look-more {
+  position: relative;
+  bottom: 0;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+  margin-top: -100px;
+  padding-bottom: 16px;
+  width: 100%;
+  height: 120px;
+  background: linear-gradient(
+    to bottom,
+    rgba(255, 255, 255, 0),
+    rgba(255, 255, 255, 0.8),
+    rgba(255, 255, 255, 1),
+    rgba(255, 255, 255, 1)
+  );
+}
+.sub-this-keywords {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 16px;
+  height: 46px;
+  color: #fff;
+  background-color: #37c6da;
+  .left {
+    font-size: 22px;
+  }
+  .center {
+    margin: 0 4px;
+    flex: 1;
+  }
+  .text {
+    font-size: 13px;
+  }
+  .action {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0 6px;
+    font-size: 13px;
+    line-height: 26px;
+    border-radius: 16px;
+    border: 1px solid $white;
+  }
+}
+.right-event {
+  margin-left: 24px;
+  .label-icon {
+    font-size: 14px;
+    color: #5f5e64;
+  }
+  &.cb_blue {
+    color: #2abed1;
+    //.label-icon{
+    //  color: #2ABED1;
+    //}
+  }
+}
+.bi-report-inject-button {
+  display: inline-block;
+  border: 1px solid #2cb7ca;
+  color: #2cb7ca;
+  background-color: #fff;
+  padding: 0 6px;
+  height: 22px;
+  line-height: 22px;
+  border-radius: 4px;
+  font-size: 14px;
+  text-align: center;
+  cursor: pointer;
+  box-sizing: content-box;
+  min-width: 42px;
+  &[disabled] {
+    opacity: 0.5;
+  }
+}
+</style>