Browse Source

feat: 新增pc端阳光采购搜索

cuiyalong 9 months ago
parent
commit
609ab5688e
33 changed files with 7207 additions and 10 deletions
  1. 12 0
      apps/bigmember_pc/src/components/article-item/ArticleItem.vue
  2. 12 1
      apps/bigmember_pc/src/components/filter-items/ContactSelector.vue
  3. 47 2
      apps/bigmember_pc/src/components/selector/TimeSelectorContent.vue
  4. 12 6
      apps/bigmember_pc/src/router/modules/search.js
  5. 5 1
      apps/bigmember_pc/src/views/search/components/search-schema-filter.vue
  6. 128 0
      apps/bigmember_pc/src/views/search/sun/components/current-filter-text.vue
  7. 258 0
      apps/bigmember_pc/src/views/search/sun/components/history-filter-dialog.vue
  8. 610 0
      apps/bigmember_pc/src/views/search/sun/components/recommend-card.vue
  9. 223 0
      apps/bigmember_pc/src/views/search/sun/components/save-filter-dialog.vue
  10. 193 0
      apps/bigmember_pc/src/views/search/sun/components/search-bid-filter.vue
  11. 226 0
      apps/bigmember_pc/src/views/search/sun/components/search-bid-header.vue
  12. 177 0
      apps/bigmember_pc/src/views/search/sun/components/search-filter-header.vue
  13. 260 0
      apps/bigmember_pc/src/views/search/sun/components/search-list-table.vue
  14. 127 0
      apps/bigmember_pc/src/views/search/sun/composables/list-header-actions.js
  15. 17 0
      apps/bigmember_pc/src/views/search/sun/constant/index.js
  16. 264 0
      apps/bigmember_pc/src/views/search/sun/constant/search-filters-bi.js
  17. 347 0
      apps/bigmember_pc/src/views/search/sun/constant/search-filters.js
  18. 746 0
      apps/bigmember_pc/src/views/search/sun/index.vue
  19. 1625 0
      apps/bigmember_pc/src/views/search/sun/model/base.js
  20. 9 0
      apps/bigmember_pc/src/views/search/sun/model/index.js
  21. 99 0
      apps/bigmember_pc/src/views/search/sun/model/modules/before-search.js
  22. 57 0
      apps/bigmember_pc/src/views/search/sun/model/modules/data-add-actions.js
  23. 414 0
      apps/bigmember_pc/src/views/search/sun/model/modules/data-collect-actions.js
  24. 46 0
      apps/bigmember_pc/src/views/search/sun/model/modules/data-distribute-actions.js
  25. 155 0
      apps/bigmember_pc/src/views/search/sun/model/modules/data-employ-actions.js
  26. 121 0
      apps/bigmember_pc/src/views/search/sun/model/modules/data-export-actions.js
  27. 52 0
      apps/bigmember_pc/src/views/search/sun/model/modules/filter-keywords.js
  28. 244 0
      apps/bigmember_pc/src/views/search/sun/model/modules/filter.js
  29. 56 0
      apps/bigmember_pc/src/views/search/sun/model/modules/join-bid-actions.js
  30. 101 0
      apps/bigmember_pc/src/views/search/sun/model/modules/list-header-actions.js
  31. 306 0
      apps/bigmember_pc/src/views/search/sun/model/modules/recommend-card.js
  32. 178 0
      apps/bigmember_pc/src/views/search/sun/model/modules/save-filter-actions.js
  33. 80 0
      apps/bigmember_pc/src/views/search/sun/model/modules/tabs.js

+ 12 - 0
apps/bigmember_pc/src/components/article-item/ArticleItem.vue

@@ -50,6 +50,11 @@
       ></div>
       <div class="a-i-right">
         <div class="tags">
+          <span
+            class="tag tag-user"
+            v-if="config.tagUserPublishState && article.tagPublishText"
+            >{{article.tagPublishText}}</span
+          >
           <span
             class="tag tag-user"
             v-if="
@@ -100,6 +105,9 @@
           <span class="tag dpink" v-if="calcBudget && calcBudget !== '0元'">
             {{ calcBudget }}
           </span>
+          <span class="tag tag-user border" v-if="config.tagBidEnd && article.bidEndTime">
+            {{ article.bidEndTime }}
+          </span>
         </div>
 
         <div style="display: flex; align-items: center">
@@ -298,6 +306,10 @@
             <i class="l-d-item-label">开标日期:</i>
             {{ dateFromNow(article.bidOpenTime * 1000) }}
           </span>
+          <div v-if="article.bidOpenTime">
+            <i class="l-d-item-label">交付地点:</i>
+            交付地点
+          </div>
         </p>
       </div>
     </div>

+ 12 - 1
apps/bigmember_pc/src/components/filter-items/ContactSelector.vue

@@ -45,7 +45,12 @@ export default {
     },
     options: {
       type: Array,
-      default: () => []
+      default: () => [
+        // {
+        //   label: 'xx',
+        //   value: 'xx'
+        // }
+      ]
     },
     value: {
       type: [String, Object],
@@ -78,6 +83,9 @@ export default {
       }
     },
     calcOptions () {
+      if (Array.isArray(this.options) && this.options.length > 0) {
+        return this.options
+      }
       if (this.source === 'winner') {
         return winnerContactData
       } else {
@@ -85,6 +93,9 @@ export default {
       }
     },
     calcPlaceholder () {
+      if (this.placeholder) {
+        return this.placeholder
+      }
       if (this.source === 'winner') {
         return '中标企业联系方式'
       } else {

+ 47 - 2
apps/bigmember_pc/src/components/selector/TimeSelectorContent.vue

@@ -92,7 +92,7 @@ import { DatePicker } from 'element-ui'
 import { dateFormatter } from '@/utils/'
 import moment from 'moment'
 import 'moment/locale/zh-cn'
-import { uniqWith } from 'lodash'
+import { uniqWith, cloneDeep } from 'lodash'
 
 moment.locale('zh-cn')
 const timeSelectMap = {
@@ -276,6 +276,33 @@ const timeSelectMap = {
       selected: false
     }
   ],
+  bidDeadline: [
+    {
+      name: '全部',
+      value: 'all',
+      selected: true
+    },
+    {
+      name: '今天',
+      value: 'today',
+      selected: false
+    },
+    {
+      name: '明天',
+      value: 'tomorrow',
+      selected: false
+    },
+    {
+      name: '未来7天',
+      value: 'next7days',
+      selected: false
+    },
+    {
+      name: '未来30天',
+      value: 'next30days',
+      selected: false
+    }
+  ],
   bidPushTime: [
     {
       name: '全部',
@@ -504,7 +531,7 @@ export default {
       } else {
         timeSelectList = propTimeSelectList
       }
-      return timeSelectList
+      return cloneDeep(timeSelectList)
     },
     calcLastTime() {
       if (this.selectorTime === 'more') {
@@ -539,6 +566,9 @@ export default {
         switch (data.exact) {
           case 'all':
           case 'today':
+          case 'tomorrow':
+          case 'next7days':
+          case 'next30days':
           case 'yesterday':
           case 'lately3':
           case 'lately7':
@@ -653,6 +683,21 @@ export default {
           t.end = moment().endOf('day').format('x')
           break
         }
+        case 'tomorrow': {
+          t.start = moment().add(1, 'days').startOf('day').format('x')
+          t.end = moment().add(1, 'days').endOf('day').format('x')
+          break
+        }
+        case 'next7days': {
+          t.start = moment().format('x')
+          t.end = moment().add(7, 'days').endOf('day').format('x')
+          break
+        }
+        case 'next30days': {
+          t.start = moment().format('x')
+          t.end = moment().add(30, 'days').endOf('day').format('x')
+          break
+        }
         case 'yesterday': {
           t.start = moment().startOf('day').format('x') - durations.day1
           t.end = moment().endOf('day').format('x') - durations.day1

+ 12 - 6
apps/bigmember_pc/src/router/modules/search.js

@@ -12,12 +12,12 @@ export default [
     name: 'bidding-search',
     component: () => import('@/views/search/bidding/index.vue')
   },
-  {
-    path: '/search/bidding-test',
-    alias: ['/jylab/supsearch/index.html'],
-    name: 'bidding-search-test',
-    component: () => import('@/views/search/bidding/test.vue')
-  },
+  // {
+  //   path: '/search/bidding-test',
+  //   alias: ['/jylab/supsearch/index.html'],
+  //   name: 'bidding-search-test',
+  //   component: () => import('@/views/search/bidding/test.vue')
+  // },
   //  企业搜索
   {
     path: '/search/ent',
@@ -25,6 +25,12 @@ export default [
     name: 'ent-search',
     component: () => import('@/views/search/ent/index.vue')
   },
+  // 阳光采购
+  {
+    path: '/search/sun',
+    name: 'sun-search',
+    component: () => import('@/views/search/sun/index.vue')
+  },
   // 采购单位搜索
   {
     path: '/search/purchase',

+ 5 - 1
apps/bigmember_pc/src/views/search/components/search-schema-filter.vue

@@ -54,6 +54,10 @@ const getPrefix = {
   component: 'search-component-',
   slot: 'search-slot-'
 }
+
+const getLabelStyle = (item) => {
+  return item.labelStyle || {}
+}
 </script>
 
 <template>
@@ -73,7 +77,7 @@ const getPrefix = {
         'flex-items-start': showLabel && item.label
       }"
       >
-        <div class="search-schema-filter-label" v-if="showLabel && item.label" :style="{'line-height': item.labelHeight}">
+        <div class="search-schema-filter-label" v-if="showLabel && item.label" :style="{'line-height': item.labelHeight, ...getLabelStyle(item) }">
           <slot name="item-label">
             {{ item.label }}
           </slot>

+ 128 - 0
apps/bigmember_pc/src/views/search/sun/components/current-filter-text.vue

@@ -0,0 +1,128 @@
+<script setup>
+import { computed, nextTick, ref } from 'vue'
+const props = defineProps({
+  data: {
+    type: Object,
+    default: {}
+  }
+})
+
+const emit = defineEmits(['closeCallBack'])
+
+function closeCallBack(key) {
+  emit('closeCallBack', key)
+}
+
+// 计算最大宽度
+// function calcItemWidth() {
+//   const ele = document.querySelector('.current-filter-text-box')
+//   if (ele) {
+//     const width = ele.clientWidth
+//     const childrenLen = ele.children.length || 0
+//     const maxWidth = Math.floor(width / 6)
+//     const fValueNode = document.querySelectorAll(
+//       '.current-filter-text-box .f-value'
+//     )
+//     if (childrenLen > 0) {
+//       Array.from(fValueNode).forEach((fNode) => {
+//         fNode.style.maxWidth = maxWidth + 'px'
+//       })
+//     }
+//   }
+// }
+// nextTick(() => {
+//   // calcItemWidth()
+// })
+
+const textKeyObj = computed(() => {
+  return {
+    publishTime: props.data.publishTimeText,
+    selectType: props.data.scopeText,
+    subtype: props.data.infoTypeText,
+    regionMap: props.data.regionMapText,
+    industry: props.data.industryText ? `行业:${props.data.industryText}` : '',
+    fileExists: props.data.fileExistsText,
+    price: props.data.priceText,
+    mobileTag:
+      props.data.mobileTagText && props.data.mobileTagText !== 'all'
+        ? props.data.mobileTagText
+        : '',
+    buyerclass: props.data.buyerClassText
+      ? `采购单位类型:${props.data.buyerClassText}`
+      : '',
+    buyertel: props.data.buyerTelText,
+    winnertel: props.data.winnerTelText,
+    notkey: props.data.notkey ? `排除词:${props.data.notkey}` : '',
+    buyer: props.data.buyer ? `采购单位:${props.data.buyer}` : '',
+    winner: props.data.winner ? `中标企业:${props.data.winner}` : '',
+    agency: props.data.agency ? `招标代理:${props.data.agency}` : ''
+  }
+})
+</script>
+
+<template>
+  <ul class="current-filter-text-box">
+    <template v-for="(value, key) in textKeyObj">
+      <el-tooltip
+        class="f-text-tip"
+        effect="dark"
+        placement="top"
+        :content="value"
+        popper-class="f-text-tip"
+        v-if="value"
+      >
+        <li>
+          <span class="f-value">{{ value }}</span>
+          <i
+            class="iconfont icon-close"
+            @click="closeCallBack(key)"
+            v-if="key !== 'publishTime' && key !== 'selectType'"
+          ></i>
+        </li>
+      </el-tooltip>
+    </template>
+  </ul>
+</template>
+
+<style lang="scss">
+.f-text-tip {
+  max-width: 300px;
+  &.el-tooltip__popper[x-placement^='top'] .popper__arrow {
+    bottom: -5px !important;
+  }
+}
+</style>
+
+<style lang="scss" scoped>
+.current-filter-text-box {
+  max-height: 102px;
+  overflow: hidden;
+  margin-bottom: 4px;
+  li {
+    padding: 0 8px;
+    border-radius: 12px;
+    border: 1px solid #2abed1;
+    background: linear-gradient(90deg, #eaf8fa 0%, #ffffff 100%);
+    color: #2abed1;
+    font-size: 14px;
+    line-height: 22px;
+    margin: 0 12px 12px 0;
+    display: flex;
+    align-items: center;
+    max-width: 223px;
+    .f-value {
+      display: inline-block;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      text-align: justify;
+      max-width: 180px;
+    }
+  }
+  .icon-close {
+    font-size: 16px;
+    margin-left: 10px;
+    cursor: pointer;
+  }
+}
+</style>

+ 258 - 0
apps/bigmember_pc/src/views/search/sun/components/history-filter-dialog.vue

@@ -0,0 +1,258 @@
+<script setup>
+import { SearchBidModel } from '../model'
+
+const { onSelectedFilter, disposeFilterActionModel } = SearchBidModel
+
+const {
+  filterHistoryList: filterData,
+  onHasToggle, // 展开收起
+  onDeleteFilter // 删除单条已存筛选条件
+} = disposeFilterActionModel
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  }
+})
+
+// 关键词
+function formatToSpace(keywords, additionalWords, wordsModeText) {
+  let str = ''
+  if (additionalWords) {
+    if (keywords) {
+      str += ','
+    }
+    str += additionalWords.replace(/,/g, ',') + '(' + wordsModeText + ')'
+  }
+  return str
+}
+
+const emit = defineEmits(['before-close'])
+function beforeClose() {
+  emit('before-close')
+}
+</script>
+
+<template>
+  <!-- 已存筛选弹框 -->
+  <el-dialog
+    custom-class="filter-dialog has-filter-dialog"
+    title="已存筛选条件"
+    :close-on-click-modal="true"
+    :close-on-press-escape="false"
+    width="750"
+    :center="true"
+    :visible.sync="visible"
+    :before-close="beforeClose"
+  >
+    <div class="filter-data-container">
+      <div v-for="item in filterData" class="filter-data-list">
+        <div class="f-l-title">
+          <div style="display: flex; align-items: center; flex: 1">
+            <i
+              class="iconfont icon-xiala"
+              :class="{ 'is-reverse': item.open }"
+              @click="onHasToggle(item)"
+            ></i>
+            <span class="f-l-title-text" @click="onSelectedFilter(item)">
+              {{ item.keywords }}
+              <span v-if="item.additionalWords">{{
+                formatToSpace(
+                  item.keywords,
+                  item.additionalWords,
+                  item.wordsModeText
+                )
+              }}</span>
+            </span>
+          </div>
+          <i class="iconfont icon-delete" @click="onDeleteFilter(item)"></i>
+        </div>
+        <div class="has-search-model">搜索模式:{{ item.searchModeText }}</div>
+        <el-collapse-transition :collapse-transition="false">
+          <div v-show="item.open">
+            <div class="f-l-content" @click="onSelectedFilter(item)">
+              <p class="f-l-c-item">
+                搜索范围:<em class="i-value">{{ item.scopeText }}</em>
+              </p>
+              <p class="f-l-c-item" v-if="item.industry">
+                行业:<em class="i-value">{{ item.industryText }}</em>
+              </p>
+              <p class="f-l-c-item">
+                <span v-if="item.price"
+                  >金额:<em class="i-value">{{ item.priceText }}</em></span
+                >
+                <span v-if="item.publishTimeText"
+                  >发布时间:<em class="i-value">{{
+                    item.publishTimeText
+                  }}</em></span
+                >
+                <span v-if="item.fileExistsText != '全部'"
+                  >附件:<em class="i-value">{{
+                    item.fileExistsText
+                  }}</em></span
+                >
+              </p>
+              <p class="f-l-c-item" v-if="item.regionMapText">
+                地区:<em class="i-value">{{ item.regionMapText }}</em>
+              </p>
+              <p class="f-l-c-item" v-if="item.infoTypeText">
+                信息类型:<em class="i-value">{{ item.infoTypeText }}</em>
+              </p>
+              <p class="f-l-c-item" v-if="item.buyerClassText">
+                采购单位类型:<em class="i-value">{{ item.buyerClassText }}</em>
+              </p>
+              <p class="f-l-c-item">
+                <span v-if="item.buyerTel === 'y'"
+                  >采购单位联系方式:<em class="i-value">{{
+                    item.buyerTelText
+                  }}</em></span
+                >
+                <span v-if="item.winnerTel === 'y'"
+                  >中标单位联系方式:<em class="i-value">{{
+                    item.winnerTelText
+                  }}</em></span
+                >
+                <span v-if="item.notkey"
+                  >排除词:<em class="i-value">{{ item.notkey }}</em></span
+                >
+              </p>
+              <p class="f-l-c-item" v-if="item.buyer">
+                采购单位:<em class="i-value">{{ item.buyer }}</em>
+              </p>
+              <p class="f-l-c-item" v-if="item.winner">
+                中标企业:<em class="i-value">{{ item.winner }}</em>
+              </p>
+              <p class="f-l-c-item" v-if="item.agency">
+                招标代理:<em class="i-value">{{ item.agency }}</em>
+              </p>
+            </div>
+          </div>
+        </el-collapse-transition>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+::v-deep {
+  .filter-dialog {
+    padding: 32px;
+    border-radius: 8px !important;
+    & ::-webkit-scrollbar {
+      /*滚动条整体样式*/
+      width: 2px !important;
+    }
+  }
+  .el-dialog {
+    width: 750px !important;
+  }
+  .el-dialog__body {
+    padding: 0;
+  }
+}
+.filter-dialog {
+  .el-dialog__header {
+    padding: 0;
+    .el-dialog__title {
+      color: #1d1d1d;
+    }
+  }
+  .el-dialog__body {
+    width: 686px;
+    overflow: hidden;
+  }
+  .filter-data-container {
+    max-height: 400px;
+    overflow-y: scroll;
+    //width: 694px;
+
+    .f-l-title {
+      padding: 8px 0 2px;
+    }
+    .f-l-content {
+      padding: 12px 0;
+      border-top: 1px dashed #ddd;
+      font-size: 12px;
+      color: #686868;
+      cursor: pointer;
+    }
+
+    .filter-data-list {
+      margin-top: 12px;
+      padding: 0 16px;
+      background: #f5f6f7;
+      border-radius: 4px;
+      .has-search-model {
+        padding: 0 0 8px 22px;
+        font-size: 12px;
+        line-height: 18px;
+      }
+      .f-l-title {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        cursor: pointer;
+        font-size: 14px;
+        min-height: 22px;
+        color: #1d1d1d;
+        .f-l-title-text {
+          flex: 1;
+          text-align: justify;
+          font-size: 14px;
+          line-height: 22px;
+          max-width: 582px;
+        }
+      }
+      .f-l-title:hover {
+        .f-l-title-text {
+          color: #2cb7ca;
+        }
+        .icon-xiala {
+          color: #2cb7ca;
+        }
+        .icon-delete {
+          font-size: 18px;
+          color: #2cb7ca;
+        }
+      }
+      .f-l-c-item {
+        margin-top: 8px;
+        line-height: 18px;
+        span {
+          margin-right: 16px;
+        }
+        .i-value {
+          color: #1d1d1d;
+        }
+      }
+    }
+  }
+}
+.has-filter-dialog {
+  .el-dialog__headerbtn {
+    font-size: 18px;
+    top: 34px;
+    right: 32px;
+  }
+}
+
+.icon-xiala {
+  display: inline-block;
+  font-size: 18px;
+  flex-shrink: 0;
+  transform: rotate(0deg);
+  transition: transform 0.5s;
+  margin-right: 4px;
+  cursor: pointer;
+  &.is-reverse {
+    transform: rotate(180deg);
+  }
+}
+.icon-delete {
+  display: inline-block;
+  font-size: 22px;
+  color: #aeaeae;
+  cursor: pointer;
+}
+</style>

+ 610 - 0
apps/bigmember_pc/src/views/search/sun/components/recommend-card.vue

@@ -0,0 +1,610 @@
+<script setup>
+import { getCurrentInstance, watch} from 'vue'
+import MarketUserScatter from '@/views/analysisReport/components/MarketUserScatter'
+import BuyerScaleScatter from '@/views/analysisReport/components/BuyerScaleScatter'
+import { SearchBidModel } from '../model'
+const {
+  recommendCardCircleModel
+} = SearchBidModel
+
+const that = getCurrentInstance().proxy
+
+const {
+  toggleAdvancedContent,
+  advancedInfo,
+  onClickInterested,
+  goToContent,
+  getProjectTitle,
+  goToReport,
+  getShowChart,
+  getNotModuleDataStatus,
+  getNowInfo,
+  nowModuleName,
+  chartCustomData,
+  showModuleChart,
+  chart,
+  test
+} = recommendCardCircleModel
+</script>
+
+<template>
+  <!-- 超前项目推荐&&市场分析报告 -->
+  <div id="jyChartCom">
+    <div class="advanced-pro-rec" v-show="advancedInfo.show">
+      <div class="p-lr-32">
+        <div class="c-a-r-top">
+          <div class="c-a-r-title">
+            <div class="r-title-text">{{ getNowInfo.title }}</div>
+            <div class="r-title-tip">
+                  <span
+                    v-if="advancedInfo.showContent || getNotModuleDataStatus"
+                  >{{ getNowInfo.desc }}</span
+                  >
+              <span
+                v-else
+                class="total-item"
+                v-for="(item, index) in advancedInfo.briefList"
+                :key="index"
+              >
+                    {{ item.key }}:<span class="highlight-text"
+              ><em>{{ item.value }}</em
+              >条</span
+              >
+                  </span>
+            </div>
+          </div>
+          <div class="c-a-r-option">
+            <div
+              v-if="nowModuleName === '市场分析报告'"
+              class="c-view-report c-view-common"
+              @click="goToReport"
+            >
+              查看完整报告
+            </div>
+            <div
+              class="c-view-interest c-view-common"
+              @click="
+                    onClickInterested(
+                      nowModuleName === '市场分析报告' ? 'B' : 'A'
+                    )
+                  "
+            >
+              感兴趣点我
+            </div>
+            <div class="c-up-or-down" @click="toggleAdvancedContent()">
+              <el-button type="text">
+                {{ advancedInfo.showContent ? '收起' : '展开' }}
+                <i
+                  class="el-icon--right"
+                  :class="
+                        'el-icon-arrow-' +
+                        (advancedInfo.showContent ? 'up' : 'down')
+                      "
+                ></i>
+              </el-button>
+            </div>
+          </div>
+        </div>
+      </div>
+      <el-collapse-transition>
+
+        <div v-show="advancedInfo.showContent">
+          <!-- 超前项目 -->
+          <div
+            class="project-module"
+            v-if="nowModuleName === '超前项目推荐'"
+            :class="{ 'remove-bl': !getShowChart }"
+          >
+            <div class="project-item">
+              <div class="left-tag total-color">累计发布</div>
+              <div>
+                    <span
+                      class="total-item"
+                      v-for="(item, index) in advancedInfo.briefList"
+                      :key="index"
+                    >
+                      {{ item.key }}:<span class="highlight-text"
+                    ><em>{{ item.value }}</em
+                    >条</span
+                    >
+                    </span>
+              </div>
+            </div>
+            <div class="project-item">
+              <div class="left-tag new-color">最新项目</div>
+              <div class="new-group">
+                    <span
+                      class="ellipsis new-item"
+                      @click="goToContent(item)"
+                      v-for="(item, index) in advancedInfo.projectList"
+                      :key="index"
+                      v-html="getProjectTitle(item)"
+                    ></span>
+              </div>
+            </div>
+          </div>
+          <!-- 市场分析报告 -->
+          <div class="custom-report" v-if="getShowChart">
+            <div class="c-a-r-top" v-if="nowModuleName === '超前项目推荐'">
+              <div class="c-a-r-title">
+                <div class="r-title-text">市场分析报告</div>
+                <div class="r-title-tip">
+                  量身定制个性化报告,分析市场竞争格局,为企业找准市场机会!
+                </div>
+              </div>
+              <div class="c-a-r-option">
+                <div
+                  class="c-view-report c-view-common"
+                  @click="goToReport"
+                >
+                  查看完整报告
+                </div>
+                <div
+                  class="c-view-interest c-view-common"
+                  @click="onClickInterested('B')"
+                >
+                  感兴趣点我
+                </div>
+              </div>
+            </div>
+            <div class="c-a-r-chart">
+              <div
+                class="chart-common"
+                id="customerChart"
+                v-show="showModuleChart !== 'customer_scale'"
+              >
+                <div class="chart-title">客户分布:</div>
+                <div class="c-c-content">
+                  <!-- <div id="chartTreeMap"></div> -->
+                  <MarketUserScatter
+                    min-height="min-height: 211px"
+                    top="-8px"
+                    :key="chart.treeMapKey"
+                    ref="treeMap"
+                    :chartData="chart.treeMapData"
+                  />
+                </div>
+              </div>
+              <div
+                class="chart-common"
+                id="winnerChart"
+                v-show="showModuleChart !== 'winner_time_distribution'"
+              >
+                <div class="chart-title">中标规模分布:</div>
+                <div class="c-c-content chart-line">
+                  <!-- <div id="chartLineChart"></div> -->
+                  <BuyerScaleScatter
+                    :key="chart.winnerLineKey"
+                    height="211px"
+                    :chartData="chart.winnerLineData"
+                  />
+                </div>
+              </div>
+              <div
+                class="chart-common"
+                id="buyerChart"
+                v-show="showModuleChart !== 'buyer_time_distribution'"
+              >
+                <div class="chart-title">采购规模分布:</div>
+                <div class="c-c-content chart-line">
+                  <!-- <div id="chartLineChartBuyer"></div> -->
+                  <BuyerScaleScatter
+                    :key="chart.buyLineKey"
+                    height="211px"
+                    :chartData="chart.buyLineData"
+                  />
+                </div>
+              </div>
+            </div>
+          </div>
+          <!-- <custom-report :show-title="nowModuleName === '超前项目推荐'" @onReport="goToReport" @onInterest="onClickInterested('B')" v-if="getShowChart" :chartCustomData="chartCustomData"></custom-report> -->
+        </div>
+      </el-collapse-transition>
+    </div>
+    <el-dialog
+      custom-class="advanced-dialog"
+      :visible.sync="advancedInfo.showDialog"
+    >
+      <img
+        class="advanced-dialog--head"
+        src="@/assets/images/advanced/dialog-head.png"
+        alt="剑鱼标讯"
+      />
+      <img
+        class="advanced-dialog--qrcode"
+        src="@/assets/images/advanced/dialog-qrcode.png"
+        alt="扫码联系客服"
+      />
+      <div class="advanced-dialog--info">
+        <h4>扫码联系客服</h4>
+        <h4>{{ advancedInfo.dialogContent }}</h4>
+        <p>专业招投标大数据服务平台丨国家信息中心大数据战略合作商</p>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+#jyChartCom {
+  background: #fff;
+  border-radius: 8px;
+
+  .advanced-pro-rec {
+    margin-top: 16px;
+    margin-bottom: 16px;
+
+    .custom-report {
+      padding: 0 16px 0 32px;
+    }
+
+    // 超前项目模块
+    $total-color: #2abed1;
+    $new-color: #ff9f40;
+
+    .total-item {
+      line-height: 24px;
+
+      em {
+        font-weight: 700;
+        font-size: 20px;
+      }
+
+      & + .total-item {
+        margin-left: 72px;
+      }
+    }
+
+    .project-module {
+      font-weight: 400;
+      font-size: 14px;
+      line-height: 22px;
+      color: #1d1d1d;
+      padding: 24px 32px;
+      border-bottom: 1px solid #ececec;
+
+      &.remove-bl {
+        border-bottom-color: transparent;
+      }
+
+      .left-tag {
+        flex-shrink: 0;
+        display: inline-block;
+        font-size: 14px;
+        line-height: 24px;
+        color: #ffffff;
+        padding: 0 9px;
+        border-radius: 0 12px 12px 0;
+        margin-right: 24px;
+
+        &.total-color {
+          background: $total-color;
+        }
+
+        &.new-color {
+          background: $new-color;
+        }
+      }
+
+      .project-item {
+        width: 100%;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+
+        & + .project-item {
+          margin-top: 18px;
+        }
+      }
+
+      .new-group {
+        display: flex;
+        flex-direction: row;
+        width: 100%;
+      }
+
+      .new-item {
+        cursor: pointer;
+        font-size: 16px;
+        line-height: 24px;
+        max-width: calc(50% - 72px);
+
+        & + .new-item {
+          margin-left: 36px;
+        }
+
+        &::before {
+          content: '';
+          display: inline-block;
+          vertical-align: middle;
+          width: 7px;
+          height: 7px;
+          border-radius: 50%;
+          margin-right: 8px;
+          background: $new-color;
+        }
+      }
+    }
+
+    .p-lr-32 {
+      padding: 0 16px 0 32px;
+    }
+
+    .c-a-r-top {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      height: 60px;
+      border-bottom: 1px dashed #e0e0e0;
+
+      .c-a-r-title {
+        display: flex;
+        align-items: center;
+        height: 100%;
+        line-height: 21px;
+        font-style: normal;
+        font-weight: 400;
+      }
+
+      .r-title-text {
+        display: flex;
+        align-items: center;
+        width: fit-content;
+        height: 95%;
+        margin-top: 5px;
+        color: #2cb7ca;
+        font-size: 16px;
+        border-bottom: 2px solid #2cb7ca;
+      }
+
+      .r-title-tip {
+        margin-top: 5px;
+        margin-left: 32px;
+        color: #686868;
+        font-size: 14px;
+      }
+    }
+
+    .c-a-r-option {
+      display: flex;
+      align-items: center;
+    }
+
+    .c-view-common {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding: 0 17px;
+      height: 30px;
+      font-style: normal;
+      font-weight: 400;
+      font-size: 14px;
+      line-height: 22px;
+      border-radius: 4px;
+      cursor: pointer;
+    }
+
+    .c-view-report {
+      border: 1px solid #2abed1;
+      color: #2abed1;
+    }
+
+    .chart-common {
+      /* width: 590px; */
+      height: 280px;
+    }
+
+    .c-c-content {
+      width: 383px;
+      height: 211px;
+    }
+
+    .c-c-content.chart-line {
+      width: 558px;
+    }
+
+    .c-up-or-down {
+      margin-left: 32px;
+
+      .el-icon--right {
+        margin-left: 2px;
+      }
+
+      .el-button--text {
+        padding: 0;
+        font-weight: 400;
+        font-size: 14px;
+        line-height: 19px;
+        color: #686868;
+      }
+    }
+
+    .c-view-interest {
+      margin-left: 36px;
+      background: #2abed1;
+      color: #fff;
+      border: 1px solid #2abdd1;
+    }
+
+    .c-a-r-chart {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-top: 16px;
+    }
+
+    .chart-title {
+      padding: 16px 0 12px 0;
+
+      font-style: normal;
+      font-weight: 400;
+      font-size: 16px;
+      line-height: 24px;
+      color: #1d1d1d;
+    }
+  }
+}
+.custom-report {
+  padding: 0 32px;
+
+  .c-a-r-top {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 60px;
+    border-bottom: 1px dashed #e0e0e0;
+
+    .c-a-r-title {
+      display: flex;
+      align-items: center;
+      height: 100%;
+      line-height: 21px;
+
+      font-style: normal;
+      font-weight: 400;
+    }
+
+    .r-title-text {
+      display: flex;
+      align-items: center;
+      width: fit-content;
+      height: 95%;
+      margin-top: 5px;
+      color: #2cb7ca;
+      font-size: 16px;
+      border-bottom: 2px solid #2cb7ca;
+    }
+
+    .r-title-tip {
+      margin-top: 5px;
+      margin-left: 32px;
+      color: #686868;
+      font-size: 14px;
+    }
+  }
+
+  .c-a-r-option {
+    display: flex;
+    align-items: center;
+  }
+
+  .c-view-common {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0 17px;
+    height: 30px;
+
+    font-style: normal;
+    font-weight: 400;
+    font-size: 14px;
+    line-height: 22px;
+    border-radius: 4px;
+    cursor: pointer;
+  }
+
+  .c-view-report {
+    border: 1px solid #2abed1;
+    color: #2abed1;
+  }
+
+  .chart-common {
+    /* width: 590px; */
+    height: 280px;
+  }
+
+  .c-c-content {
+    width: 383px;
+    height: 211px;
+  }
+
+  .c-c-content.chart-line {
+    width: 558px;
+  }
+
+  .c-view-interest {
+    margin-left: 36px;
+    background: #2abed1;
+    color: #fff;
+    border: 1px solid #2abdd1;
+  }
+
+  .c-a-r-chart {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-top: 16px;
+  }
+
+  .chart-title {
+    padding: 16px 0 12px 0;
+
+    font-style: normal;
+    font-weight: 400;
+    font-size: 16px;
+    line-height: 24px;
+    color: #1d1d1d;
+  }
+}
+::v-deep {
+  .advanced-dialog {
+    width: 370px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    border-radius: 8px;
+
+    .el-dialog__body {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+    }
+
+    .el-dialog__close {
+      font-size: 20px;
+      font-weight: bold;
+      color: #2abdd1;
+      cursor: pointer;
+    }
+
+    .el-dialog__header,
+    .el-dialog__body {
+      padding: 0;
+    }
+
+    &--head {
+      margin-top: -58px;
+      max-width: 100%;
+    }
+
+    &--qrcode {
+      margin-top: 26px;
+      margin-bottom: 22px;
+      width: 154px;
+      height: 154px;
+    }
+
+    &--info {
+      padding: 24px 20px;
+      padding-top: 0;
+
+      h4 {
+        font-weight: 400;
+        font-size: 16px;
+        line-height: 24px;
+        text-align: center;
+        color: #1d1d1d;
+      }
+
+      p {
+        margin-top: 16px;
+        font-weight: 400;
+        font-size: 12px;
+        line-height: 18px;
+        text-align: center;
+        color: #999999;
+      }
+    }
+  }
+}
+</style>
+

+ 223 - 0
apps/bigmember_pc/src/views/search/sun/components/save-filter-dialog.vue

@@ -0,0 +1,223 @@
+<script setup>
+import { computed } from 'vue'
+import { SearchBidModel } from '../model'
+const { disposeFilterActionModel } = SearchBidModel
+const { viewFilterParams: currentFilter } = disposeFilterActionModel
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  }
+})
+// 关键词
+const additionalWordsCon = computed(() => {
+  let str = ''
+  if (currentFilter.value.additionalWords) {
+    if (currentFilter.value.keywords) {
+      str += ','
+    }
+    str +=
+      currentFilter.value.additionalWords.replace(/,/g, ',') +
+      '(' +
+      currentFilter.value.wordsModeText +
+      ')'
+  }
+  return str
+})
+
+// 处理筛选数据-英文逗号转空格
+function formatToSpace(val) {
+  if (!val) return
+  return val.replace(/,/g, ' ')
+}
+
+const emit = defineEmits(['cancel', 'confirm'])
+function cancelHandle() {
+  emit('cancel')
+}
+function confirmHandle() {
+  emit('confirm')
+}
+</script>
+
+<template>
+  <!-- 保存筛选弹框 -->
+  <el-dialog
+    custom-class="filter-dialog save-filter-dialg"
+    title="保存筛选条件"
+    :show-close="false"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    width="750"
+    :center="true"
+    :visible.sync="visible"
+  >
+    <div class="filter-save-item">
+      <div class="save-label">关键词:</div>
+      <div class="save-value">
+        {{ currentFilter.keywords }}
+        <span v-if="additionalWordsCon">{{ additionalWordsCon }}</span>
+        <div class="search_model">
+          搜索模式:{{ currentFilter.searchModeText }}
+        </div>
+      </div>
+    </div>
+    <div class="filter-save-item">
+      <div class="save-label">筛选条件:</div>
+      <div class="save-value">
+        <div class="save-value-bg">
+          <span>搜索范围:</span>{{ currentFilter.scopeText }}
+        </div>
+        <div class="save-value-bg" v-if="currentFilter.industry">
+          <span>行业:</span>{{ currentFilter.industryText }}
+        </div>
+        <div style="display: flex">
+          <div class="save-value-bg" v-if="currentFilter.priceText">
+            <span>金额:</span>{{ currentFilter.priceText }}
+          </div>
+          <div class="save-value-bg" v-if="currentFilter.publishTime">
+            <span>发布时间:</span>{{ currentFilter.publishTimeText }}
+          </div>
+          <div
+            class="save-value-bg"
+            v-if="currentFilter.fileExistsText != '全部'"
+          >
+            <span>附件:</span>{{ currentFilter.fileExistsText }}
+          </div>
+        </div>
+        <div class="save-value-bg" v-if="currentFilter.regionMapText">
+          <span>地区:</span>{{ currentFilter.regionMapText }}
+        </div>
+        <div class="save-value-bg" v-if="currentFilter.infoType">
+          <span>信息类型:</span>{{ currentFilter.infoTypeText }}
+        </div>
+        <div class="save-value-bg" v-if="currentFilter.buyerClass">
+          <span>采购单位类型:</span>{{ currentFilter.buyerClassText }}
+        </div>
+        <div style="display: flex">
+          <div class="save-value-bg" v-if="currentFilter.buyerTel">
+            <span>采购单位联系方式:</span>{{ currentFilter.buyerTelText }}
+          </div>
+          <div class="save-value-bg" v-if="currentFilter.winnerTel">
+            <span>中标单位联系方式:</span>{{ currentFilter.winnerTelText }}
+          </div>
+          <div class="save-value-bg" v-if="currentFilter.notkey">
+            <span>排除词:</span>{{ formatToSpace(currentFilter.notkey) }}
+          </div>
+        </div>
+        <div class="save-value-bg" v-if="currentFilter.buyer">
+          <span>采购单位:</span>{{ formatToSpace(currentFilter.buyer) }}
+        </div>
+        <div class="save-value-bg" v-if="currentFilter.winner">
+          <span>中标企业:</span>{{ formatToSpace(currentFilter.winner) }}
+        </div>
+        <div class="save-value-bg" v-if="currentFilter.agency">
+          <span>招标代理:</span>{{ formatToSpace(currentFilter.agency) }}
+        </div>
+      </div>
+    </div>
+    <span slot="footer" class="dialog-footer">
+      <el-button
+        type="primary"
+        class="btn-group confirm-btn"
+        @click="confirmHandle"
+        >确 定</el-button
+      >
+      <el-button class="btn-group cancel-btn" @click="cancelHandle"
+        >取 消</el-button
+      >
+    </span>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+::v-deep {
+  .filter-dialog {
+    padding: 32px;
+    border-radius: 8px !important;
+  }
+  .el-dialog {
+    width: 750px !important;
+  }
+}
+
+.filter-dialog {
+  width: 750px;
+  .el-dialog__header {
+    padding: 0;
+    .el-dialog__title {
+      color: #1d1d1d;
+    }
+  }
+  &.save-filter-dialog {
+    .el-dialog__body {
+      padding: 0 0 32px !important;
+    }
+  }
+  .btn-group {
+    width: 132px;
+    height: 36px;
+    padding: 0;
+    border-radius: 6px;
+    font-size: 16px;
+  }
+  .btn-group.confirm-btn {
+    background: #2cb7ca;
+    margin-right: 52px;
+    border: 0;
+    color: #fff;
+  }
+  .btn-group.confirm-btn:hover,
+  .btn-group.confirm-btn:focus {
+    color: #fff;
+  }
+  .btn-group.cancel-btn:hover {
+    background: unset;
+    border-color: #dcdfe6;
+    color: #606266;
+  }
+  .filter-save-item {
+    display: flex;
+    margin-top: 20px;
+    line-height: 18px;
+
+    .save-label {
+      min-width: 60px;
+      text-align: right;
+      color: #636467;
+      font-size: 12px;
+    }
+
+    .save-value {
+      margin-left: 8px;
+      flex: 1;
+      color: #1d1d1d;
+      text-align: left;
+      font-size: 12px;
+    }
+
+    .save-value-bg {
+      margin-bottom: 8px;
+      margin-right: 8px;
+      padding: 6px 8px;
+      background: #f5f6f7;
+      border-radius: 4px;
+      font-size: 12px;
+      color: #1d1d1d;
+      line-height: 18px;
+
+      & > span {
+        color: #636467;
+      }
+    }
+
+    .search_model {
+      margin-top: 4px;
+      font-size: 12px;
+      line-height: 18px;
+      color: #686868;
+    }
+  }
+}
+</style>

+ 193 - 0
apps/bigmember_pc/src/views/search/sun/components/search-bid-filter.vue

@@ -0,0 +1,193 @@
+<script setup>
+import { computed, watch } from 'vue'
+import { SearchBidModel } from '../model/index'
+import { getPropertyFilters } from '@/api/modules/bi'
+import { getCMCustomInfo } from '@/api/modules/'
+import SearchSchemaFilter from '@/views/search/components/search-schema-filter.vue'
+import SelectorWithBasePower from '@/components/filter-items/SelectorWithBasePower.vue'
+import $bus from '@/utils/bus'
+
+const {
+  isVip,
+  isLogin,
+  isInApp,
+  isInWeb,
+  inBIPropertyIframe,
+  inResourceBIIframe,
+  activeTab,
+  guideGoWorkSpace,
+  filterState,
+  updateFilterBase,
+  doQuery,
+  goLogin,
+  disposeFilterSchema,
+  onChangeTab
+} = SearchBidModel
+
+const {
+  SearchBidBaseSchema,
+  SearchBidMoreSchema,
+  searchBidMoreFreeSchema,
+  searchBidMoreVipSchema,
+  doUpdateData
+} = disposeFilterSchema()
+
+watch(activeTab, (newVal) => {
+  doUpdateData()
+})
+
+
+
+const customMoreSchema = computed(() => {
+  return {
+    vipModuleShow: true,
+    commonConf: {
+      showLabel: false,
+      styleType: 'row'
+    },
+    freeConf: {
+      showRowLabel: true,
+      rowLabelText: '更多筛选:',
+      schema: searchBidMoreFreeSchema.value
+    },
+    vipConf: {
+      schema: searchBidMoreVipSchema.value
+    }
+  }
+})
+
+function noPower() {
+  $bus.$emit('search:filter:no-power')
+}
+
+function toLogin() {
+  $bus.$emit('bidding:goLogin')
+}
+
+function doChangeFilter() {
+  doQuery()
+}
+</script>
+
+<template>
+  <div class="search-bid-filter">
+    <div class="guide-go-workspace" v-if="isInWeb">
+      最近五年数据查询以及更多筛选条件请
+      <span class="highlight-text">"</span>
+      <span class="cursor-button highlight-text" @click="guideGoWorkSpace"
+        >{{ isLogin ? '' : '登录后' }}进入工作台</span
+      >
+      <span class="highlight-text">"</span>
+      检索
+    </div>
+    <!--  标准筛选  -->
+    <search-schema-filter
+      v-model="filterState"
+      :schema="SearchBidBaseSchema"
+      @change="doChangeFilter"
+    ></search-schema-filter>
+
+    <!--  更多筛选  -->
+    <div class="more-filters-container" :class="{ 'wrap-line': !isVip }">
+      <search-schema-filter
+        v-if="isVip && isInApp"
+        v-model="filterState"
+        :schema="SearchBidMoreSchema"
+        :show-label="false"
+        showRowLabel
+        style-type="row"
+        @change="doChangeFilter"
+      >
+        <span slot="row-label-text">更多筛选:</span>
+      </search-schema-filter>
+      <template v-else>
+        <SelectorWithBasePower
+          :component="SearchSchemaFilter"
+          :commonConf="customMoreSchema.commonConf"
+          :freeConf="customMoreSchema.freeConf"
+          :vipConf="customMoreSchema.vipConf"
+          v-model="filterState"
+          vipMaskShow
+          :baseMaskShow="!isLogin"
+          :vipModuleShow="isInApp && !inBIPropertyIframe"
+          @clickVipMask="noPower"
+          @clickBaseMask="toLogin"
+          @change="doChangeFilter"
+        ></SelectorWithBasePower>
+      </template>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@media (min-width: 1456px) {
+  .in-app {
+    .search-bid-filter {
+      .wrap-line {
+        ::v-deep {
+          .vip-module {
+            margin-top: 4px;
+            margin-left: 0;
+          }
+        }
+      }
+    }
+  }
+}
+.search-bid-filter {
+  position: relative;
+  padding: 16px 32px;
+  .wrap-line {
+    ::v-deep {
+      .filter-layout .select-prefix {
+        background: transparent;
+      }
+      .vip-module {
+        margin-top: 4px;
+        margin-left: 82px;
+      }
+    }
+  }
+  .guide-go-workspace {
+    left: 345px;
+    position: absolute;
+    border-radius: 4px;
+    background: linear-gradient(
+      270deg,
+      rgba(42, 190, 209, 0.24) 1.5%,
+      rgba(42, 190, 209, 0.12) 97.45%
+    );
+    padding: 2px 8px;
+    color: #1d1d1d;
+    font-size: 16px;
+    font-style: normal;
+    font-weight: 400;
+    line-height: 22px;
+
+    .highlight-text {
+      color: #2abed1;
+      font-weight: 700;
+      font-family:
+        -apple-system,
+        system-ui,
+        BlinkMacSystemFont,
+        Helvetica Neue,
+        PingFang SC,
+        Hiragino Sans GB,
+        Microsoft YaHei,
+        Arial,
+        sans-serif;
+    }
+    .cursor-button {
+      text-decoration-line: underline;
+      background: transparent;
+      padding: 0;
+      cursor: pointer;
+    }
+  }
+  .more-filters-container {
+    margin-top: 6px;
+    margin-right: 38px;
+  }
+}
+</style>

+ 226 - 0
apps/bigmember_pc/src/views/search/sun/components/search-bid-header.vue

@@ -0,0 +1,226 @@
+<script setup>
+import { getCurrentInstance, computed } from 'vue'
+import SearchHeaderCard from '@/views/search/components/search-header-card.vue'
+import KeywordTagsPc from '@/views/search/components/keyword-tags.vue'
+import CommonSingleChoice from '@/components/filter-items/CommonSingleChoice.vue'
+import { SearchBidModel } from '../model/index'
+import $bus from '@/utils/bus'
+
+const {
+  isInApp,
+  isLogin,
+  isInBI,
+  inResourceBIIframe,
+  inBIPropertyIframe,
+  isBidField,
+  cooperateCode,
+  inputKeywordsState,
+  doQuery,
+  onChangeTab,
+  searchModelOptions,
+  SearchTabsModel,
+  goWorkSpace,
+  clearHistoryQuery,
+  searchHistoryList,
+  toggleFilter,
+  showFilter
+} = SearchBidModel
+
+const { searchTabs } = SearchTabsModel
+
+const that = getCurrentInstance().proxy
+
+function doSearch($event) {
+  const firstSearch = !$event ? 'firstSearch' : undefined
+
+  return doQuery({}, firstSearch)
+}
+// 跳转信息发布
+function goToPublish() {
+  if (!isLogin.value) {
+    location.href = '/swordfish/frontPage/InformationDistribution/free/index'
+    return
+  }
+  // 工作台内
+  if (isInApp.value) {
+    that.$BRACE.methods.open({
+      route: {
+        link: '/swordfish/page_web_pc/issued/info',
+        appType: 'iframe'
+      }
+    })
+  } else {
+    window.open('/swordfish/page_web_pc/issued/info')
+  }
+}
+
+function checkPower($event) {
+  if (!isLogin.value) {
+    $bus.$emit('bidding:goLogin')
+    return false
+  } else {
+    return true
+  }
+}
+// 未登录--多个关键词切换处理
+function onSelectMoreKey() {
+  if (!isLogin.value) {
+    inputKeywordsState.value.selectMoreKey = false
+    $bus.$emit('bidding:goLogin')
+  }
+}
+
+const showNewPublish = computed(() => {
+  // return !isInBI.value && !isBidField
+  return false
+})
+
+function onClearSearchHistory() {
+  if (!searchHistoryList.value || searchHistoryList.value.length === 0) return
+  clearHistoryQuery({ type: 1 })
+}
+</script>
+
+<template>
+  <div class="search-bid-header">
+    <search-header-card
+      v-model="inputKeywordsState.input"
+      :tabs="searchTabs"
+      placeholder="请输入项目名称等关键词,例如:医疗设备"
+      @change-tab="onChangeTab"
+      @search="doSearch"
+      :show-wx-qr="!cooperateCode && !inBIPropertyIframe && !isBidField"
+      :show-workspace-button="isLogin && !isInApp"
+      :showTab="!inBIPropertyIframe"
+      :historyEnabled="true"
+      @goWorkSpace="goWorkSpace"
+      @clearSearch="onClearSearchHistory"
+      :immediateSearch="false"
+    >
+      <template slot="tab-other" v-if="showNewPublish">
+        <!-- 工作台内外样式不同-->
+        <div class="search-tab-right news-publish-btn">
+          <el-button
+            class="use-badge"
+            data-badge="限免"
+            :class="{ outside: !isInApp }"
+            type="primary"
+            @click="goToPublish"
+          >
+            {{ isLogin ? '信息发布' : '免费发布信息' }}
+          </el-button>
+        </div>
+      </template>
+      <div class="flex flex-(row items-center)">
+        <div class="flex flex-(row items-center)">
+          <common-single-choice
+            class="m-l-6px"
+            v-model="inputKeywordsState.searchMode"
+            placeholder="精准搜索"
+            :options="searchModelOptions"
+            @change="doSearch"
+            :beforeChange="checkPower"
+          ></common-single-choice>
+
+          <el-tooltip
+            popper-class="tooltip-help-class"
+            effect="dark"
+            placement="bottom"
+            :offset="0"
+          >
+            <i
+              class="iconfont icon-help highlight-text cursor-pointer m-l-4px"
+            ></i>
+            <template slot="content">
+              <div class="tooltip-slot-content w-360px">
+                精准搜索: 搜索结果必须完全包含完整的关键词。如搜索"医疗设备"
+                ,搜索结果一定完整包含“医疗设备”才能被搜索到,而“医疗的设备”或“设备医疗”的项目不会被搜索到。
+                <br />
+                <br />
+                模糊搜索: 系统会先自动智能分词然后再进行搜索。如搜索"医疗设备"
+                ,系统会自动分成“医疗”“设备”然后进行搜索,只要项目中出现“医疗”和“设备”都会被搜索到,前提是两个词必须一同出现在一则公告内,不分先后顺序。
+              </div>
+            </template>
+          </el-tooltip>
+        </div>
+
+        <el-checkbox
+          class="m-l-16px"
+          v-model="inputKeywordsState.selectMoreKey"
+          @input="onSelectMoreKey"
+        >
+          多个关键词
+        </el-checkbox>
+        <div class="show-filter-handle m-l-16px" @click="toggleFilter">
+          <span>筛选</span>
+          <i
+            class="iconfont"
+            :class="showFilter ? 'icon-a-zhankai1' : 'icon-shouqi1'"
+          ></i>
+        </div>
+      </div>
+    </search-header-card>
+    <keyword-tags-pc
+      class="p-l-32px p-b-16px"
+      v-if="inputKeywordsState.selectMoreKey"
+      :list="inputKeywordsState.additionalWords"
+      placeholder="请输入关键词"
+      @change="doSearch"
+    >
+      <div
+        class="m-l-16px"
+        slot="radio"
+        v-show="inputKeywordsState.additionalWords.length > 0"
+      >
+        <el-radio-group
+          class="keyword-radio-group"
+          v-model="inputKeywordsState.wordsMode"
+          @input="doSearch"
+        >
+          <el-radio :label="0">包含所有关键词</el-radio>
+          <el-radio :label="1">包含任意关键词</el-radio>
+        </el-radio-group>
+      </div>
+    </keyword-tags-pc>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.search-bid-header {
+  .show-filter-handle {
+    font-size: 14px;
+    line-height: 22px;
+    color: #1d1d1d;
+    cursor: pointer;
+    .iconfont {
+      color: #686868;
+      font-size: 16px;
+      margin-left: 4px;
+    }
+    &::before {
+      content: '';
+      display: inline-block;
+      height: 14px;
+      width: 1px;
+      background: #ececec;
+      margin-right: 16px;
+      vertical-align: middle;
+    }
+  }
+  .news-publish-btn {
+    .el-button.outside {
+      font-size: 16px;
+      line-height: 24px;
+      border-radius: 4px;
+      padding: 4px 8px;
+      margin-left: 12px;
+    }
+    .el-button {
+      border-radius: 8px;
+      font-size: 14px;
+      line-height: 22px;
+      padding: 7px 24px;
+    }
+  }
+}
+</style>

+ 177 - 0
apps/bigmember_pc/src/views/search/sun/components/search-filter-header.vue

@@ -0,0 +1,177 @@
+<script setup>
+import { ref } from 'vue'
+import SaveFilterDialog from '../components/save-filter-dialog.vue'
+import HistoryFilterDialog from '../components/history-filter-dialog.vue'
+import CurrentFilterText from '../components/current-filter-text'
+import { SearchBidModel } from '../model/index'
+const {
+  isLogin,
+  isInApp,
+  inBIPropertyIframe,
+  disposeFilterActionModel,
+  onSaveFilter,
+  onResetFilter,
+  toggleFilter,
+  fixedTop,
+  showCurrentFilterText: filterText,
+  doQuery,
+  updateFilterBase
+} = SearchBidModel
+
+const {
+  historyFilterCount, // 已存筛选条件个数
+  saveFilterDialogVisible, // 保存筛选条件弹窗
+  historyFilterDialogVisible, //已存筛选条件弹窗
+  saveFilterConfirm, // 确认保存筛选条件
+  saveFilterCancel, // 取消保存筛选条件
+  onHasFilter // 已存筛选条件操作
+} = disposeFilterActionModel
+
+function closeHistoryFilterDialog() {
+  historyFilterDialogVisible.value = false
+}
+
+// 已选筛选,删除某一项筛选项
+function resetFilterItem(key) {
+  updateFilterBase({ key }, 'resetItem')
+  doQuery({})
+}
+</script>
+
+<template>
+  <div class="searchTender">
+    <div class="filter-header-container" :class="{ 'header-fixed': fixedTop }" v-if="false">
+      <div class="filter-header" :class="{ 'fixed-top': fixedTop }">
+        <!--        已选条件-->
+        <section v-if="isLogin && isInApp && !inBIPropertyIframe">
+          <div class="f-h-current">
+            <span class="f-h-c-label">
+              <span>已选条件:</span>
+            </span>
+            <div class="f-h-c-container">
+              <current-filter-text
+                :data="filterText"
+                @closeCallBack="resetFilterItem"
+              ></current-filter-text>
+            </div>
+          </div>
+          <div class="f-h-action">
+            <span class="action-item save-item" @click="onSaveFilter">
+              <i class="icon-square-up"></i>保存
+            </span>
+            <span class="action-item reset-item" @click="onResetFilter">
+              <i class="iconfont icon-delete"></i>清空
+            </span>
+            <span class="action-item has-item" @click="onHasFilter">
+              <i class="icon-square-up"></i>已存{{ historyFilterCount || '' }}
+            </span>
+          </div>
+        </section>
+      </div>
+    </div>
+    <save-filter-dialog
+      v-if="saveFilterDialogVisible"
+      :visible="saveFilterDialogVisible"
+      @cancel="saveFilterCancel"
+      @confirm="saveFilterConfirm"
+    ></save-filter-dialog>
+    <history-filter-dialog
+      v-if="historyFilterDialogVisible"
+      :visible="historyFilterDialogVisible"
+      @before-close="closeHistoryFilterDialog"
+    >
+    </history-filter-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+$mainColor: #2abed1;
+//.in-app {
+//  .filter-header-container.header-fixed {
+//    position: fixed;
+//    top: 0;
+//    left: 0;
+//    width: 100%;
+//    padding: 0 24px;
+//    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.05);
+//    background: #ffffff;
+//    z-index: 99;
+//  }
+//  .filter-header-container.header-fixed .fixed-top {
+//    position: relative;
+//    width: auto;
+//    top: unset;
+//    border-bottom: unset;
+//  }
+//}
+.filter-header {
+  width: 100%;
+  padding: 0 32px;
+  background: #fff;
+
+  //&.fixed-top {
+  //  position: fixed;
+  //  top: 52px;
+  //  width: 1200px;
+  //  z-index: 99;
+  //}
+  section {
+    display: flex;
+    border-bottom: 1px solid #ececec;
+  }
+
+  .f-h-current {
+    flex: 1;
+    display: flex;
+    margin-bottom: 4px;
+    .f-h-c-label {
+      font-size: 14px;
+      line-height: 22px;
+      color: #686868;
+      margin-right: 12px;
+    }
+    .f-h-c-container {
+      flex: 1;
+    }
+  }
+  .f-h-action {
+    display: flex;
+    align-items: center;
+    width: 186px;
+    margin-left: 13px;
+    align-self: flex-start;
+
+    .action-item {
+      cursor: pointer;
+      font-size: 14px;
+      line-height: 22px;
+      color: #2abed1;
+      display: flex;
+      align-items: center;
+      &:not(:last-of-type) {
+        margin-right: 12px;
+      }
+      i {
+        margin-right: 4px;
+      }
+    }
+  }
+  .icon-square-up {
+    display: inline-block;
+    width: 16px;
+    height: 16px;
+    background: url(@/assets/images/icon/square-up.png) no-repeat center;
+    background-size: contain;
+  }
+  //.icon-xiala {
+  //  display: inline-block;
+  //  font-size: 16px;
+  //  flex-shrink: 0;
+  //  transform: rotate(0deg);
+  //  transition: transform 0.5s;
+  //  &.is-reverse {
+  //    transform: rotate(180deg);
+  //  }
+  //}
+}
+</style>

+ 260 - 0
apps/bigmember_pc/src/views/search/sun/components/search-list-table.vue

@@ -0,0 +1,260 @@
+<script setup>
+import { ref, computed } from 'vue'
+import { SearchBidModel } from '../model'
+const { onClickDataExport } = SearchBidModel
+import {
+  dateFromNow,
+  replaceKeyword,
+  moneyUnit,
+  openLinkInWorkspace
+} from '@/utils/'
+
+const props = defineProps({
+  list: {
+    type: Array,
+    default: () => []
+  },
+  listState: {
+    type: Object,
+    default: () => ({
+      finished: false, // 是否已经搜索过
+      loading: false,
+      pageNum: 1, // 当前页
+      pageSize: 50, // 每页多少条数据
+      total: 0 // 一共多少条数据
+    })
+  },
+  matchKeys: {
+    type: Array,
+    default: () => []
+  },
+  tableFixedTop: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const showTableMore = computed(() => {
+  return props.list.length >= 20 && props.listState.total > 20
+})
+
+function calcTitle(item) {
+  if (!item.projectName) {
+    item.projectName = item.title
+  }
+  const { projectName, matchKeys } = item
+  const highKeys = getMatchKeys(matchKeys || [])
+  const hightLightedTitle = replaceKeyword(projectName, highKeys, [
+    '<span class="highlight-text-orange-bd">',
+    '</span>'
+  ])
+  return `${hightLightedTitle}`
+}
+
+function calcMoney(budget) {
+  if (budget && isNaN(budget)) {
+    return budget
+  } else if (budget && !isNaN(budget) && budget !== '0') {
+    return (budget / 10000).toFixed(2).replace('.00', '')
+  } else {
+    return ''
+  }
+}
+function getMatchKeys(matchKeys) {
+  return props.matchKeys.concat(matchKeys)
+}
+
+const emit = defineEmits(['before-close'])
+function toDetail(item) {
+  emit('to-detail', item)
+}
+</script>
+
+<template>
+  <div
+    class="info-list search-table-list"
+    element-loading-background="rgba(255,255,255, .4)"
+    element-loading-custom-class="self-export-loading"
+  >
+    <div class="fixed-table" v-if="tableFixedTop">
+      <table class="table">
+        <thead class="thead fixed-head">
+          <tr>
+            <td width="49.7">序号</td>
+            <td width="326.4" class="deep-border">项目名称</td>
+            <td width="87">公告类型</td>
+            <td width="75.6" class="deep-border">预算<br />(万元)</td>
+            <td width="187">招标单位</td>
+            <td width="106.7" class="deep-border">开标日期</td>
+            <td width="180.3">中标单位</td>
+            <td width="78.7" class="deep-border">中标金额<br />(万元)</td>
+            <td width="106.8">发布日期</td>
+          </tr>
+        </thead>
+      </table>
+    </div>
+    <table class="table" v-show="list.length">
+      <thead class="thead">
+        <tr>
+          <td width="48">序号</td>
+          <td width="315" class="deep-border">项目名称</td>
+          <td width="84">公告类型</td>
+          <td width="73" class="deep-border">预算<br />(万元)</td>
+          <td width="181">招标单位</td>
+          <td width="103" class="deep-border">开标日期</td>
+          <td width="174">中标单位</td>
+          <td width="76" class="deep-border">中标金额<br />(万元)</td>
+          <td width="103">发布日期</td>
+        </tr>
+      </thead>
+      <tbody>
+        <tr
+          v-for="(item, index) in list"
+          :class="{ visited: item.visited }"
+          :key="index + '_' + item.id"
+          @click="toDetail(item)"
+          v-visited:articleContent="item.id"
+        >
+          <td width="48">{{ index + 1 }}</td>
+          <td width="315" class="tt-l" v-html="calcTitle(item, index)"></td>
+          <td width="84">{{ item.subtype ? item.subtype + '公告' : '' }}</td>
+          <td width="73" class="tt-r">{{ calcMoney(item.budget) }}</td>
+          <td width="181" class="tt-l">{{ item.buyer }}</td>
+          <td width="103">
+            {{
+              dateFromNow(
+                item.bidOpenTime ? item.bidOpenTime * 1000 : null,
+                'yyyy-MM-dd HH:mm'
+              )
+            }}
+          </td>
+          <td width="174" class="tt-l">{{ item.winner }}</td>
+          <td width="76" class="tt-r">{{ calcMoney(item.bidAmount) }}</td>
+          <td width="103">{{ dateFromNow(item.publishTime * 1000) }}</td>
+        </tr>
+      </tbody>
+    </table>
+    <div class="shade_table" v-if="showTableMore">
+      <div
+        class="more"
+        data-need-bind-phone=""
+        @click="onClickDataExport('table')"
+      >
+        查看更多&gt;
+      </div>
+    </div>
+    <div class="shade_table_blank" v-if="list.length"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+/* table */
+.in-app {
+  .search-table-list {
+    .fixed-table {
+      width: 100%;
+      background: #fff;
+      top: 48px;
+      left: 0;
+      padding: 0 24px;
+      box-sizing: border-box;
+    }
+  }
+}
+
+.search-table-list {
+  /*全文搜索 表格*/
+  width: 100%;
+  position: relative;
+  //border-bottom: 1px solid #e0e0e0;
+  table {
+    width: 100%;
+    border-collapse: collapse;
+    table-layout: fixed;
+    tr {
+      td {
+        border: 1px solid #e0e0e0;
+        vertical-align: middle;
+        text-align: center;
+        line-height: 26px;
+      }
+      td.tt-l {
+        text-align: left;
+      }
+      td.tt-c {
+        text-align: center;
+      }
+      td.tt-r {
+        text-align: right;
+      }
+    }
+  }
+  .thead {
+    tr {
+      font-size: 14px;
+      color: #888;
+      background-color: #f3fbff;
+      border-top: 2px solid #2cb7ca;
+
+      td {
+        color: #888888;
+        padding: 6px 0;
+        //border-top: 3px solid #2cb7ca;
+      }
+    }
+    .deep {
+      border-top: 3px solid #2c90cb;
+    }
+  }
+  tbody {
+    tr {
+      cursor: pointer;
+      &:hover {
+        background-color: #f5f6f7;
+        box-shadow: inset 0px -1px 0px rgb(0, 0, 0, 0.05);
+        cursor: pointer;
+      }
+      &:nth-of-type(2n) {
+        background-color: #f5f5fb;
+      }
+    }
+  }
+  .shade_table {
+    width: 100%;
+    position: absolute;
+    margin-top: -110px;
+    height: 150px;
+    background: linear-gradient(
+      to bottom,
+      rgba(255, 255, 255, 0),
+      rgba(255, 255, 255, 0.8),
+      rgba(255, 255, 255, 1)
+    );
+    .more {
+      position: absolute;
+      height: 35px;
+      width: 120px;
+      left: 50%;
+      margin-left: -60px;
+      bottom: 20px;
+      color: #2cb7ca;
+      border-radius: 5px;
+      border: 1px solid #2cb7ca;
+      line-height: 35px;
+      text-align: center;
+      cursor: pointer;
+    }
+  }
+  .shade_table_blank {
+    height: 50px;
+  }
+  .fixed-table {
+    position: fixed;
+    top: 100px;
+    width: 1200px;
+    table {
+      margin-bottom: 0;
+    }
+  }
+}
+</style>

+ 127 - 0
apps/bigmember_pc/src/views/search/sun/composables/list-header-actions.js

@@ -0,0 +1,127 @@
+/**
+ * ListHeaderActionItem类用于创建列表头部操作项。
+ * @param {Object} config 配置对象,包含key, icon, label, badge等属性。
+ */
+class ListHeaderActionItem {
+  constructor(config) {
+    this.key = config.key // 操作项的唯一标识
+    this.isActive = config.isActive || false // 是否激活状态
+    this.icon = config.icon || '' // 操作项的图标
+    this.label = config.label || '' // 操作项的标签文本
+    this.badge = config.badge || '' // 操作项的徽标文本(如未使用,则为空)
+  }
+}
+
+/**
+ * 创建一个具有激活状态的列表头部操作项。
+ * @param {Object} config 配置对象,用于创建操作项的初始状态。
+ * @param {Object} activeConfig 激活状态下的配置对象,可选。
+ * @returns {ListHeaderActionItem} 返回一个包含激活状态的操作项实例。
+ */
+function createListHeaderActionItem(config, activeConfig = {}) {
+  const item = new ListHeaderActionItem(config)
+  item.active = new ListHeaderActionItem(
+    Object.assign({}, config, activeConfig) // 结合config和activeConfig创建激活状态的实例
+  )
+  return item
+}
+
+// 列表头部操作项集合
+const ListHeaderActions = {}
+
+/**
+ * 安装列表头部操作项,初始化ListHeaderActions数组。
+ * @returns {Array} 返回初始化后的ListHeaderActions数组。
+ */
+function installListHeader() {
+  const list = []
+  // 精简列表操作项
+  list.push(
+    createListHeaderActionItem({
+      key: 'refined-list',
+      icon: 'icon-liebiao',
+      label: '精简列表',
+      isActive: true
+    })
+  )
+
+  // 详细列表操作项
+  list.push(
+    createListHeaderActionItem({
+      key: 'detailed-list',
+      icon: 'icon-xiangxiliebiao',
+      label: '详细列表',
+      // badge: 'icon-have-vip'
+    })
+  )
+
+  // 表格操作项,带vip徽标
+  list.push(
+    createListHeaderActionItem({
+      key: 'table',
+      icon: 'icon-biaoge',
+      label: '表格'
+    })
+  )
+
+  // 数据导出操作项
+  list.push(
+    createListHeaderActionItem({
+      key: 'data-export',
+      icon: 'icon-shujudaochu',
+      label: '数据导出'
+    })
+  )
+
+  // 标讯收藏操作项
+  list.push(
+    createListHeaderActionItem({
+      key: 'collect-bid',
+      icon: 'icon-shoucang_weishoucang',
+      label: '标讯收藏'
+    })
+  )
+
+  // 分发操作项
+  list.push(
+    createListHeaderActionItem({
+      key: 'distribute-bid',
+      icon: 'icon-shoudongfenfa',
+      label: '分发'
+    })
+  )
+  // 收录操作项
+  list.push(
+    createListHeaderActionItem({
+      key: 'employ-bid',
+      icon: 'icon-a-Property1shoulu',
+      label: '收录'
+    })
+  )
+  // 修改标签操作项
+  list.push(
+    createListHeaderActionItem({
+      key: 'edit-tags',
+      icon: 'icon-edit',
+      label: '修改标签'
+    })
+  )
+  // 取消操作项
+  list.push(
+    createListHeaderActionItem({
+      key: 'cancel-collect',
+      icon: 'icon-shoucang_weishoucang',
+      label: '取消收藏'
+    })
+  )
+
+  list.forEach((item) => {
+    ListHeaderActions[item.key] = item
+  })
+}
+
+// 调用installListHeader安装列表头部操作项
+installListHeader()
+
+// 导出ListHeaderActions
+export { ListHeaderActions }

+ 17 - 0
apps/bigmember_pc/src/views/search/sun/constant/index.js

@@ -0,0 +1,17 @@
+import { createBiSearchBidBaseSchema, createBiSearchBidMoreSchema } from './search-filters-bi.js'
+import { createSearchBidBaseSchema, createSearchBidMoreSchema  } from './search-filters.js'
+
+export function getCreateSearchSchema(conf) {
+  const { bi } = conf
+  if(bi) {
+    return {
+      createSearchBidBaseSchema: createBiSearchBidBaseSchema,
+      createSearchBidMoreSchema: createBiSearchBidMoreSchema
+    }
+  } else {
+    return {
+      createSearchBidBaseSchema,
+      createSearchBidMoreSchema
+    }
+  }
+}

+ 264 - 0
apps/bigmember_pc/src/views/search/sun/constant/search-filters-bi.js

@@ -0,0 +1,264 @@
+import RegionCollapseSelector from '@/components/filter-items/RegionCollapseSelector'
+import CommonCheckboxSelector from '@/components/filter-items/CommonCheckboxSelector'
+import PriceSelector from '@/components/selector/PriceSelector'
+// 信息类型
+import InfoTypeDropdown from '@/components/filter-items/InfoTypeDropdown.vue'
+// 搜索范围
+import SearchRangeDropdown from '@/components/filter-items/SearchRangeDropdown.vue'
+// 发布时间
+import TimeDropdown from '@/components/selector/timeDropdown'
+// 附件
+import AttachmentSelector from '@/components/filter-items/AttachmentSelector.vue'
+// 换手率
+import ChangeHandsDropdown from '@/components/filter-items/ChangeHandsDropdown.vue'
+import SearchTimeScopeSelector from '@/components/selector/SearchTimeScopeSelector.vue'
+import ContactSelector from '@/components/filter-items/ContactSelector.vue'
+import KeywordTagsSelector from '@/components/filter-items/KeywordTagsSelector'
+
+
+function createBiSearchBidBaseSchema(propertyListData = {}) {
+  const businessList = propertyListData['业务类型'] || []
+  const priceList =  propertyListData['价格区间'] || []
+  const periodList =  propertyListData['合同周期'] || []
+  const propertyList =  propertyListData['物业业态'] || []
+  const SearchBidBaseSchema = [
+    {
+      key: 'regionMap',
+      label: '地区:',
+      defaultVal: {},
+      _name: 'regionMap',
+      _type: 'component',
+      expand: {
+        component: RegionCollapseSelector,
+        props: {
+          showSelectedList: false
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'expireTime',
+      label: '到期时间:',
+      defaultVal: 'all',
+      _name: 'expireTime',
+      _type: 'component',
+      expand: {
+        component: SearchTimeScopeSelector,
+        props: {
+          type: 'expire',
+          showConfirmButton: true,
+          exactCanHalf: true,
+          isDateRange: true
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'subInformation',
+      label: '业务类型:',
+      defaultVal: [],
+      _name: 'subInformation',
+      _type: 'component',
+      expand: {
+        component: CommonCheckboxSelector,
+        props: {
+          field: 'value',
+          options: {
+            value: businessList
+          }
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'scale',
+      label: '价格区间:',
+      defaultVal: '',
+      _name: 'scale',
+      _type: 'component',
+      expand: {
+        component: CommonCheckboxSelector,
+        props: {
+          field: 'value',
+          options: {
+            value: priceList
+          }
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'period',
+      label: '合同周期:',
+      defaultVal: '',
+      _name: 'period',
+      _type: 'component',
+      expand: {
+        component: CommonCheckboxSelector,
+        props: {
+          single: true,
+          field: 'value',
+          options: {
+            value: periodList
+          }
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'propertyForm',
+      label: '物业业态:',
+      defaultVal: '',
+      _name: 'propertyForm',
+      _type: 'component',
+      expand: {
+        component: CommonCheckboxSelector,
+        props: {
+          field: 'value',
+          options: {
+            value: propertyList
+          }
+        },
+        hooks: {}
+      }
+    }
+  ]
+
+  return SearchBidBaseSchema
+}
+
+function createBiSearchBidMoreSchema() {
+  const SearchBidMoreSchema = [
+    {
+      key: 'selectType',
+      label: '搜索范围',
+      defaultVal: ['title', 'content'],
+      _name: 'selectType',
+      _type: 'component',
+      expand: {
+        component: SearchRangeDropdown,
+        hooks: {}
+      }
+    },
+    {
+      key: 'subtype',
+      label: '信息类型',
+      defaultVal: '',
+      _name: 'subtype',
+      _type: 'component',
+      expand: {
+        component: InfoTypeDropdown,
+        props: {
+          notNJ: true
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'publishTime',
+      label: '发布时间',
+      defaultVal: 'fiveyear',
+      _name: 'publishTime',
+      _type: 'component',
+      expand: {
+        component: TimeDropdown,
+        props: {
+          placeholder: '发布时间',
+          selectData: [{
+            value: 'lately-7',
+            label: '最近7天'
+          }, {
+            value: 'lately-30',
+            label: '最近30天'
+          }, {
+            value: 'thisyear',
+            label: '最近1年'
+          }, {
+            value: 'threeyear',
+            label: '最近3年'
+          }, {
+            value: 'fiveyear',
+            label: '最近5年'
+          }, {
+            value: '0',
+            label: '自定义',
+            disabled: true
+          }],
+          selectorType: 'line',
+          singleChoice: true,
+
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'changeHand',
+      label: '换手率',
+      defaultVal: '',
+      _name: 'changeHand',
+      _type: 'component',
+      expand: {
+        component: ChangeHandsDropdown,
+        hooks: {}
+      }
+    },
+    {
+      key: 'fileExists',
+      label: '附件',
+      defaultVal: '',
+      _name: 'fileExists',
+      _type: 'component',
+      expand: {
+        component: AttachmentSelector,
+        hooks: {}
+      }
+    },
+    {
+      key: 'buyerTel',
+      label: '采购单位联系方式',
+      defaultVal: '',
+      _name: 'buyerTel',
+      _type: 'component',
+      expand: {
+        component: ContactSelector,
+        props: {
+          source: 'buyer'
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'winnerTel',
+      label: '中标企业联系方式',
+      defaultVal: '',
+      _name: 'winnerTel',
+      _type: 'component',
+      expand: {
+        component: ContactSelector,
+        props: {
+          source: 'winner'
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'notKey',
+      label: '排除词',
+      defaultVal: [],
+      _name: 'notKeyComponent',
+      _type: 'component',
+      expand: {
+        component: KeywordTagsSelector,
+        props: {
+          placeholder: '排除词',
+          inputPlaceholder: '请输入不希望包含的关键词',
+          maxTip: '排除词个数已达上限'
+        },
+        hooks: {}
+      }
+    }
+  ]
+  return SearchBidMoreSchema
+}
+
+export { createBiSearchBidBaseSchema, createBiSearchBidMoreSchema }

+ 347 - 0
apps/bigmember_pc/src/views/search/sun/constant/search-filters.js

@@ -0,0 +1,347 @@
+import TagSelector from '@/components/selector/TagSelector.vue'
+import SearchScopeSelector from '@/components/filter-items/SearchScopeSelector.vue'
+import SearchTimeScopeSelector from '@/components/selector/SearchTimeScopeSelector.vue'
+import ContactSelector from '@/components/filter-items/ContactSelector.vue'
+import AttachmentSelector from '@/components/filter-items/AttachmentSelector.vue'
+import IndustrySelector from '@/components/filter-items/IndustrySelector.vue'
+import RegionSelector from '@/components/filter-items/RegionSelector'
+import SelectorWithBasePower from '@/components/filter-items/SelectorWithBasePower.vue'
+import $bus from '@/utils/bus'
+
+function noPower() {
+  $bus.$emit('search:filter:no-power')
+}
+
+function beforeChangeHandle($event, char, isLogin) {
+  if (isLogin) {
+    return true
+  }
+  // 发布时间
+  if (char === 'publishTime') {
+    if ($event.value === 'sinceLastYear') {
+      return true
+    }
+    else {
+      $bus.$emit('bidding:goLogin')
+      return false
+    }
+  }
+  else if (char === 'selectType') {
+    if ($event.value === 'file') {
+      $bus.$emit('bidding:goLogin')
+      return false
+    }
+    else {
+      return true
+    }
+  }
+  else if (char === 'subtype') {
+    if ($event.value) {
+      $bus.$emit('bidding:goLogin')
+    }
+    else {
+      return true
+    }
+  }
+}
+
+// 更多筛选中需要vip的筛选项
+const moreFiltersNeedVipKeyList = []
+
+function createSearchBidBaseSchema(conf = {}) {
+  const isLogin = conf.isLogin || false
+  const vipUser = conf.vipUser || false
+  const oldUser = conf.oldUser || false
+  const showVip = conf.showVip || false
+  const infoType = conf.infoType
+  const inInjectBI = conf.inInjectBI || false
+  const isInApp = conf.isInApp || false
+  const isVip = conf.isVip || false
+
+  // 发布时间
+  const publishTimeExpandFree = {
+    component: SelectorWithBasePower,
+    props: {
+      component: SearchTimeScopeSelector,
+      vipMaskShow: true,
+      vipModuleShow: showVip,
+      freeConf: {
+        exactCanHalf: true,
+        showConfirmButton: true,
+        beforeChange($event) {
+          return beforeChangeHandle($event, 'publishTime', isLogin)
+        },
+        options: ['lately7', 'lately30', 'sinceLastYear'],
+        isDateRange: true
+      },
+      vipConf: {
+        exactCanHalf: true,
+        showConfirmButton: true,
+        options: ['sinceLastThreeYear', 'sinceLastFiveYear', 'exact'],
+        isDateRange: true
+      }
+    },
+    hooks: {
+      clickVipMask: noPower
+    }
+  }
+  const publishTimeExpandVip = {
+    component: SearchTimeScopeSelector,
+    props: {
+      exactCanHalf: true,
+      showConfirmButton: true,
+      isDateRange: true
+    },
+    hooks: {}
+  }
+  // 搜索范围
+  const defaultScopeOptions = [
+    {
+      label: '标题',
+      value: 'title'
+    },
+    {
+      label: '标的名称',
+      value: 'ppa'
+    },
+  ]
+  const freeOptions = defaultScopeOptions
+  const vipOptions = []
+  const searchScopeExpandFree = {
+    component: SelectorWithBasePower,
+    props: {
+      vipModuleShow: showVip,
+      component: SearchScopeSelector,
+      options: defaultScopeOptions,
+      freeConf: {
+        isOld: oldUser && !vipUser,
+        beforeChange($event) {
+          return beforeChangeHandle($event, 'selectType', isLogin)
+        },
+        options: freeOptions,
+        keepOne: true
+      },
+      vipConf: {
+        options: vipOptions,
+        beforeChange() {
+          noPower()
+        },
+        keepOne: true
+      }
+    },
+    hooks: {
+      noPower
+    }
+  }
+  const searchScopeExpandVip = {
+    component: SearchScopeSelector,
+    props: {
+      options: defaultScopeOptions,
+      isOld: oldUser && !vipUser,
+      keepOne: true
+    },
+    hooks: {}
+  }
+
+  const SearchBidBaseSchema = [
+    {
+      key: 'publishTime',
+      label: '发布时间:',
+      labelHeight: isVip || !isInApp ? '22px' : '32px',
+      defaultVal: 'thisyear',
+      _name: 'time',
+      _type: 'component',
+      expand: vipUser ? publishTimeExpandVip : publishTimeExpandFree
+    },
+    {
+      key: 'selectType',
+      label: '搜索范围:',
+      labelHeight: isVip || !isInApp ? '22px' : '30px',
+      defaultVal: ['content', 'title'],
+      _name: 'selectType',
+      _type: 'component',
+      expand: vipUser ? searchScopeExpandVip : searchScopeExpandFree
+    },
+    {
+      key: 'baomingjiezhizhuangtai',
+      label: '报名截止状态:',
+      defaultVal: '0',
+      _name: 'baomingjiezhizhuangtai',
+      _type: 'component',
+      labelHeight: '25px',
+      labelStyle: {
+        width: '100px',
+      },
+      expand: {
+        component: TagSelector,
+        props: {
+          sourceList: [
+            {
+              label: '全部',
+              value: '0'
+            },
+            {
+              label: '未截止',
+              value: '1'
+            },
+            {
+              label: '已截止',
+              value: '-1'
+            }
+          ],
+          showHeader: false
+        },
+        hooks: {}
+      },
+    },
+    {
+      key: 'baomingjiezhiriqi',
+      label: '报名截止日期:',
+      defaultVal: 'thisyear',
+      _name: 'baomingjiezhiriqi',
+      _type: 'component',
+      labelStyle: {
+        width: '100px',
+      },
+      labelHeight: isVip || !isInApp ? '22px' : '32px',
+      expand: {
+        component: SearchTimeScopeSelector,
+        props: {
+          type: 'bidDeadline',
+          exactCanHalf: true,
+          showConfirmButton: true,
+          isDateRange: true
+        },
+        hooks: {}
+      }
+    },
+  ]
+
+  return SearchBidBaseSchema
+}
+
+function createSearchBidMoreSchema(filterItems, conf) {
+  const isLogin = conf.isLogin || false
+  const isVip = conf.isVip || false
+  const isBidField = conf.isBidField || false
+
+  let SearchBidMoreSchema = [
+    {
+      key: 'jiaofudidian',
+      label: '交付地点',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: RegionSelector,
+        props: {
+          // vip: isVip && isLogin,
+          vip: true,
+          showCount: false,
+          placeholder: '交付地点'
+        },
+        hooks: {
+          limit: noPower
+        }
+      }
+    },
+    {
+      key: 'xiangmudiqu',
+      label: '项目地区',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: RegionSelector,
+        props: {
+          vip: true,
+          showCount: false,
+          placeholder: '项目地区'
+        },
+        hooks: {
+          limit: noPower
+        }
+      }
+    },
+    {
+      key: 'lingyu',
+      label: '领域',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: RegionSelector,
+        props: {
+          vip: true,
+          showCount: false,
+          placeholder: '领域'
+        },
+        hooks: {
+          limit: noPower
+        }
+      }
+    },
+    {
+      key: 'industry',
+      label: '行业',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: IndustrySelector,
+        hooks: {}
+      }
+    },
+    {
+      key: 'fileExists',
+      label: '附件',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: AttachmentSelector,
+        hooks: {}
+      }
+    },
+    {
+      key: 'publisher',
+      label: '发布方',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: ContactSelector,
+        props: {
+          placeholder: '发布方',
+          options: [
+            {
+              label: '全部',
+              value: ''
+            },
+            {
+              label: '用户发布',
+              value: 'y'
+            },
+            {
+              label: '平台发布',
+              value: 'm'
+            }
+          ]
+        },
+        hooks: {}
+      }
+    },
+  ]
+
+  // VIP处理
+  SearchBidMoreSchema.forEach((schema) => {
+    const key = schema.key
+    if (moreFiltersNeedVipKeyList.includes(key)) {
+      schema.vipMark = 1
+    }
+  })
+
+  return SearchBidMoreSchema
+}
+
+export { createSearchBidBaseSchema, createSearchBidMoreSchema }

+ 746 - 0
apps/bigmember_pc/src/views/search/sun/index.vue

@@ -0,0 +1,746 @@
+<script setup>
+import { ref } from 'vue'
+import SearchBidHeader from './components/search-bid-header.vue'
+import SearchBidFilter from './components/search-bid-filter.vue'
+import searchFilterHeader from './components/search-filter-header.vue'
+import SearchListTable from './components/search-list-table.vue'
+import SearchList from '@/views/search/layout/search-list.vue'
+import ArticleItem from '@/components/article-item/ArticleItem.vue'
+import Adsense from '@/views/order/components/adsense/index.vue'
+import CollectInfo from '@/components/collect-info/CollectInfo.vue'
+import CustomDialog from '@/components/dialog/Dialog.vue'
+import ExportTip from '@/views/portrayal/components/DataExportTip.vue'
+import powerPerson from '@/components/subscribe-manager/powerPerson.vue'
+import BidrenewalDialog from '@/views/BidrenewalDialog/index.vue'
+import Empty from '@/components/common/Empty.vue'
+import recommendCard from './components/recommend-card.vue'
+import { openLinkInWorkspace } from '@/utils/'
+// 导入业务模型
+import { useSearchBidModel, SearchBidModel } from './model/index'
+import { getMsgDistributor } from '@/api/modules/'
+import { useRouter } from 'vue-router/composables'
+
+const router = useRouter()
+
+// 初始化模型
+useSearchBidModel()
+
+// 初始化模型 解构业务所需 model \ fn
+const {
+  isLogin,
+  isInApp,
+  inBIPropertyIframe,
+  inResourceBIIframe,
+  inBITopNet,
+  inInjectBI,
+  isInBI,
+  isVip,
+  isFree,
+  vt,
+  filterState,
+  inputKeywordsState,
+  listState,
+  activeItemStyleType,
+  searchListProps,
+  doQuery,
+  doListHeaderAction,
+  doChangeAllSelect,
+  doChangeSelect,
+  doChangePageNum,
+  doChangePageSize,
+  toDetail,
+  list,
+  tableList,
+  tableFixedTop,
+  tagToDetail,
+  setExport,
+  exportDialogChange,
+  showDataExportDialog,
+  onClickSingleCollect,
+  usePowerRef,
+  doSubmitDistribute,
+  onJoinBid,
+  BidrenewalDialogRef,
+  showPropertyDialog,
+  propertyIframeSrc,
+  onSingleEmploy,
+  onAddInfoOfBI,
+  onSingleAddInfo,
+  vipDialogConf,
+  closeVipDialog,
+  timeSelectorText,
+  collectElementRef,
+  onFreeTaste,
+  interceptKeywords,
+  toggleBlurModeTip,
+  doToggleSearchBlurMode,
+  showFilter
+} = SearchBidModel
+
+const {
+  show: showVipDialog,
+  text: vipDialogText,
+  type: vipDialogType
+} = vipDialogConf
+
+// 开通超级订阅
+function toBuySvip() {
+  window.open('/swordfish/page_big_pc/free/svip/buy?type=buy')
+}
+
+// 列表-单条-配置
+const articleRef = ref({
+  bidding: true,
+  detail: false,
+  gray: true,
+  joinBid: false,
+  table: false,
+  collect: false,
+  push: false,
+  tagUserPublishState: false,
+  tagBidEnd: false,
+})
+
+const goToBidSearchPage = () => {
+  const url = '/jylab/supsearch/index.html'
+  if (isInApp.value) {
+    // 工作桌面内跳转
+    openLinkInWorkspace(true, {
+      url: url,
+      newTab: true
+    })
+  } else {
+    window.open(url)
+  }
+}
+</script>
+
+<template>
+  <div class="search-bidding-page">
+    <div
+      class="search-bidding-header-container"
+      :class="{ 'b-rd-self': !showFilter }"
+    >
+      <search-bid-header></search-bid-header>
+    </div>
+    <div
+      class="search-bidding-keywords-tip intercept"
+      v-if="interceptKeywords.interceptOtherWords"
+    >
+      <img src="@/assets/images/icon/tip2.png" alt="" />
+      “<span>{{ interceptKeywords.interceptOtherWords }}</span
+      >“及其后面的字词均被忽略,因为剑鱼标讯的查询限制在<span
+        class="interceptLimit"
+        >{{ interceptKeywords.interceptLimit }}</span
+      >个汉字以内。
+    </div>
+    <div class="search-bidding-filter-container">
+      <el-collapse-transition>
+        <div v-show="showFilter">
+          <search-filter-header></search-filter-header>
+          <search-bid-filter></search-bid-filter>
+        </div>
+      </el-collapse-transition>
+    </div>
+    <!-- <div>
+      <recommend-card></recommend-card>
+    </div> -->
+    <div class="search-bidding-list-container">
+      <search-list
+        class="b-rd-8px"
+        v-bind="searchListProps"
+        @doAction="doListHeaderAction"
+        @doChangeAllSelect="doChangeAllSelect"
+        @doChangeSelect="doChangeSelect"
+        :show-pagination="activeItemStyleType !== 'T'"
+        :is-table="activeItemStyleType === 'T'"
+        :table-fixed-top="tableFixedTop"
+      >
+        <template #other-action-item v-if="inInjectBI">
+          <div class="all-add bi-add-button" @click="onAddInfoOfBI()">添加</div>
+        </template>
+        <!--      <template #list-before v-if="listState.pageNum === 1">-->
+        <!--        <span>如对搜索结果满意,可直接订阅及时接收项目信息。</span>-->
+        <!--      </template>-->
+        <template #item-checkbox v-if="activeItemStyleType === 'T'">
+          <span></span>
+        </template>
+        <template v-slot:table="{ list }" v-if="activeItemStyleType === 'T'">
+          <search-list-table
+            :list="tableList"
+            :list-state="listState"
+            :match-keys="inputKeywordsState.matchKeys"
+            @to-detail="toDetail"
+            :table-fixed-top="tableFixedTop"
+          ></search-list-table>
+        </template>
+        <template
+          v-slot:item="{ item, index }"
+          v-if="activeItemStyleType !== 'T'"
+        >
+          <div>
+            <article-item
+              class="list-item"
+              :model="activeItemStyleType"
+              :class="{ visited: item.visited || item.ca_isvisit }"
+              :match-keys="inputKeywordsState.matchKeys"
+              :article="item"
+              :index="index"
+              :tag-click-list="['area', 'subtype']"
+              @onClick="toDetail(item)"
+              @tag-click="tagToDetail(item, $event)"
+              @onCollect="onClickSingleCollect"
+              @onJoinBid="onJoinBid"
+              :config="articleRef"
+            >
+              <template #bi-slot="{ item }"> </template>
+            </article-item>
+          </div>
+        </template>
+        <template #empty>
+          <empty :mtb60="false" images="jy-back.png">
+            <div class="hasNoData_tiptext">
+              <p>未匹配到您所搜索的信息,</p>
+              <p>
+                您可跳转到
+                <span class="highlight-text">“招标采购搜索”</span>
+                查看是否有对应信息
+              </p>
+              <p class="empty-action-button">
+                <el-button type="primary" size="middle" @click="goToBidSearchPage">点击跳转</el-button>
+              </p>
+            </div>
+          </empty>
+        </template>
+        <template
+          #list-after
+          v-if="listState.finished && activeItemStyleType !== 'T' && isLogin"
+        >
+          <div
+            class="p-16px text-right over-run-tips"
+            v-if="isFree && listState.total >= 500"
+          >
+            为您展示前500条,
+            <span class="highlight-text" @click="onFreeTaste"
+              >点击免费查看更多信息</span
+            >
+          </div>
+          <div
+            class="text-right over-run-tips"
+            v-if="isVip && listState.total >= 5000"
+          >
+            为您展示前5000条,可细化筛选条件查看更多信息
+          </div>
+        </template>
+        <template #pagination>
+          <el-pagination
+            background
+            popper-class="pagination-custom-select"
+            layout="prev, pager, next, sizes, jumper"
+            :current-page="listState.pageNum"
+            :page-size="listState.pageSize"
+            :page-sizes="[5, 10, 50, 100]"
+            :total="listState.pageTotal"
+            :show-confirm-btn="true"
+            @current-change="doChangePageNum($event)"
+            @size-change="doChangePageSize($event)"
+          >
+          </el-pagination>
+        </template>
+      </search-list>
+      <!-- 手动切换筛选模式提示 -->
+      <div
+        class="tip-toggle-search-mode-container"
+        v-show="toggleBlurModeTip.show"
+      >
+        <div>
+          如需查看更多相关信息,建议您将搜索模式切换为
+          <div class="tip-action highlight-text">
+            “模糊搜索”
+            <el-tooltip
+              popper-class="tooltip-help-class"
+              effect="dark"
+              placement="bottom"
+            >
+              <i class="iconfont icon-help" style="font-size: 18px"></i>
+              <template slot="content">
+                <div class="tooltip-slot-content w-360px">
+                  精准搜索: 搜索结果必须完全包含完整的关键词。如搜索"医疗设备"
+                  ,搜索结果一定完整包含“医疗设备”才能被搜索到,而“医疗的设备”或“设备医疗”的项目不会被搜索到。
+                  <br />
+                  <br />
+                  模糊搜索: 系统会先自动智能分词然后再进行搜索。如搜索"医疗设备"
+                  ,系统会自动分成“医疗”“设备”然后进行搜索,只要项目中出现“医疗”和“设备”都会被搜索到,前提是两个词必须一同出现在一则公告内,不分先后顺序。
+                </div>
+              </template>
+            </el-tooltip>
+          </div>
+          ,按照当前条件共匹配到{{ toggleBlurModeTip.count }}条公告。
+        </div>
+        <button @click="doToggleSearchBlurMode">立即切换查看</button>
+      </div>
+
+      <div class="sub-collection tags-box" style="display: none">
+        <div class="tags-inputs">
+          <div class="tag-input">
+            <div class="tag-labels"></div>
+            <input
+              type="text"
+              class="clear-input"
+              maxlength="10"
+              oninput="this.value=this.value.replace(/\s+/g,'')"
+            />
+            <div class="tag-placeholder">新增标签回车保存</div>
+          </div>
+          <div class="add-tag-button">添加并使用</div>
+        </div>
+        <div class="tags-list clearfix"></div>
+        <div class="tags-footer">
+          <div class="tags-button button-confirm">确认添加</div>
+          <div class="tags-button button-cancel">暂不添加</div>
+        </div>
+      </div>
+    </div>
+
+    <adsense class="footer-look-container" code="jy-pcsearch-bottom"></adsense>
+
+    <!--  留资弹窗  -->
+    <collect-info ref="collectElementRef"></collect-info>
+    <!--数据导出提示框-->
+    <custom-dialog
+      @close="showDataExportDialog = false"
+      customClass="export-class-dialog"
+      width="388px"
+      :show-footer="false"
+      :show-close="true"
+      :visible="showDataExportDialog"
+    >
+      <export-tip
+        @setExport="setExport"
+        @checkBoxChange="exportDialogChange"
+      ></export-tip>
+    </custom-dialog>
+    <CustomDialog
+      title="开通超级订阅"
+      customClass="open-vip-dialog"
+      width="380px"
+      top="30vh"
+      center
+      :visible.sync="showVipDialog"
+    >
+      {{ vipDialogText }}
+      <template #footer>
+        <button class="action-button confirm" @click="toBuySvip">去开通</button>
+        <button class="action-button cancel" @click="closeVipDialog">
+          取消
+        </button>
+      </template>
+    </CustomDialog>
+    <!-- 分发企业选择, 只有企业展示,默认vt为企业-->
+    <power-person
+      @manualDiatribution="doSubmitDistribute"
+      vt="q"
+      :list="list"
+      ref="usePowerRef"
+    ></power-person>
+    <!-- 参标更新状态弹窗 -->
+    <bidrenewal-dialog ref="BidrenewalDialogRef"> </bidrenewal-dialog>
+
+    <!--    物业专版收录-->
+    <el-dialog
+      custom-class="property-employ-dialog"
+      :visible.sync="showPropertyDialog"
+    >
+      <iframe
+        width="600"
+        height="650"
+        :src="propertyIframeSrc"
+        frameborder="0"
+      ></iframe>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss">
+.open-vip-dialog {
+  width: 380px !important;
+
+  .el-dialog__header {
+    padding: 32px 32px 20px !important;
+  }
+
+  .el-dialog__body {
+    padding: 0 32px 0 !important;
+    text-align: center !important;
+  }
+
+  .el-dialog__footer {
+    padding: 32px !important;
+  }
+
+  .el-button {
+    width: 132px;
+    font-size: 16px;
+  }
+}
+.search-bidding-page .sub-collection.tags-box {
+  display: flex;
+  flex-direction: column;
+  min-height: 340px;
+  max-height: 360px;
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 332px;
+  padding: 20px 16px;
+  background: #ffffff;
+  border: 1px solid #ececec;
+  box-sizing: border-box;
+  border-radius: 8px;
+  box-shadow: 0px 0px 28px 0px rgba(0, 0, 0, 0.08);
+  z-index: 99;
+  .tags-list .tags-item {
+    float: left;
+    min-width: 44px;
+    padding: 0 8px;
+    margin: 10px 8px 0 0;
+    height: 24px;
+    line-height: 24px;
+    border-radius: 4px;
+    border: 1px solid #ececec;
+    box-sizing: border-box;
+    color: #1d1d1d;
+    text-align: center;
+    font-size: 14px;
+    background: #f5f6f7;
+    cursor: pointer;
+  }
+
+  .tags-list .tags-item {
+    float: left;
+    min-width: 44px;
+    padding: 0 8px;
+    margin: 10px 8px 0 0;
+    height: 24px;
+    line-height: 24px;
+    border-radius: 4px;
+    border: 1px solid #ececec;
+    box-sizing: border-box;
+    color: #1d1d1d;
+    text-align: center;
+    font-size: 14px;
+    background: #f5f6f7;
+    cursor: pointer;
+  }
+
+  .tags-item.tags-active {
+    padding: 0 8px 0 24px !important;
+    background: #2cb7ca
+      url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADPSURBVHgB7ZNREcIwDIYjYRImYRLmZHPAHAwHlUAdIAEJSKiESgjpEY7Qg7uFdXnKd5eXdMmfpX8BHMdxDEDEgSJQ9GANiU4UGZ9cwBISXPFNGWIAKyrxZLr+suq/xdkwV4oFlFBNR3ET4veS0zaJosGoqOtZ8EVUi4tGWbM+rklC/Ax7KH++dY18ZbmZuGi8iKbhxzdTJT5DSyo/LNXZSZxljV80A3T4aayR86vIJTzyjX8xZTATF0NIU24y5xFDSFNGU3ExxNzsmTmO4zAPYEiZdz83IV0AAAAASUVORK5CYII=)
+      no-repeat 6px center !important;
+    color: #fff !important;
+    background-size: 16px !important;
+    border: 0 !important;
+  }
+
+  .tags-item.disabled {
+    color: #8e8e8e !important;
+  }
+  .tags-inputs .tag-input::-webkit-scrollbar {
+    width: 8px;
+  }
+
+  .tags-inputs .tag-input::-webkit-scrollbar-thumb {
+    border-radius: 4px;
+    background-color: #ececec;
+  }
+
+  .tags-list::-webkit-scrollbar {
+    width: 8px;
+  }
+
+  .tags-list::-webkit-scrollbar-thumb {
+    border-radius: 4px;
+    background-color: #ececec;
+  }
+  .tag-close {
+    display: inline-block;
+    width: 16px;
+    height: 16px;
+    margin-left: 8px;
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEwSURBVHgB7ZWxDYMwEEXPER1NRmEFJggUiJpJQiahRhSECViBUdzQIYgdgYQIPnwXmkT+DcbY/s9n3wHg5PTPKoriejRGYB+rqrpP0xT1fR9mWSaBaO77fiuEeCZJ8gAqwGyez68dBWIxV83gbSJEboK47HXWda1DF626Ar2gTUi35lo6iqbxuwBxHEvP80JF3lEg9sxhjp5pDnoHdCSGYWjVDj4W3B4HZo4dHQpgC8E1twI4gtANrrk1AAYxP1nmJAAEArjmZIADiI5TsC5AlJRS5zWcJVIEDLd9LXIUrCNgSjVqsWIBYHnOqZhrHR6BTZGhVEwSAKXCcSHEGebfQBjvAKe8cv6iRgC1i4ZijkHotUxz0TtQlmWuFrpxKtxyHOM4Nmma5sAVJaednH5SLxmyS6JIrGjkAAAAAElFTkSuQmCC);
+    background-position: center 2px;
+    background-repeat: no-repeat;
+    background-size: contain;
+  }
+  .tags-inputs {
+    position: relative;
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+
+    .tag-input {
+      width: 100%;
+      padding: 0;
+      min-height: 34px;
+      max-height: 74px;
+      overflow-y: scroll;
+      display: inline-block;
+      border: 1px solid #ccc;
+      border-radius: 4px;
+      background-color: #fff;
+      cursor: text;
+      text-align: left;
+
+      .tag-labels {
+        display: inline;
+        vertical-align: middle;
+
+        .tag-label {
+          display: inline-block;
+          padding: 5px 12px;
+          font-size: 14px;
+          line-height: 1.2;
+          margin: 5px;
+          cursor: pointer;
+          border: 1px solid #ececec;
+          box-sizing: border-box;
+          border-radius: 4px;
+          background: #f5f6f7;
+          color: #1d1d1d;
+        }
+      }
+
+      .clear-input {
+        display: inline-block;
+        padding: 0 10px;
+        width: 160px;
+        height: 36px;
+        line-height: 1;
+        background: #fff;
+        border-radius: 2px;
+        vertical-align: middle;
+        border: none;
+        background-color: transparent;
+        box-shadow: none;
+        box-sizing: border-box;
+        font-size: 14px;
+        color: #1d1d1d;
+      }
+    }
+  }
+
+  .tags-list {
+    margin-top: 12px;
+    overflow-y: auto;
+    flex: 1;
+  }
+
+  .add-tag-button {
+    margin-left: 16px;
+    color: #2cb7ca;
+    font-size: 14px;
+    line-height: 22px;
+    white-space: nowrap;
+    cursor: pointer;
+  }
+
+  .tags-footer {
+    margin-top: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .tags-button {
+    padding: 3px 17px;
+    color: #1d1d1d;
+    font-size: 14px;
+    line-height: 22px;
+    border-radius: 4px;
+    border: 1px solid #e0e0e0;
+    text-shadow: 0px 0px 28px 0px rgba(0, 0, 0, 0.08);
+    cursor: pointer;
+  }
+
+  .button-confirm {
+    margin-right: 16px;
+    color: #fff;
+    background: #2cb7ca;
+    border-color: #2cb7ca;
+  }
+
+  .tag-placeholder {
+    position: absolute;
+    top: 12px;
+    left: 16px;
+    color: #bbb;
+    font-size: 14px;
+  }
+}
+
+.empty-action-button {
+  margin-top: 12px;
+  .el-button {
+    padding: 6px 36px;
+    height: 36px;
+  }
+}
+</style>
+
+<style lang="scss" scoped>
+.in-app {
+  .search-bidding-page {
+    margin-top: 0;
+    width: 100%;
+    padding: 24px;
+  }
+}
+.in-web {
+  .search-bidding-page {
+    margin-top: 24px;
+  }
+}
+.search-bidding-page {
+  width: 1200px;
+  margin: 0 auto;
+
+  .search-bidding-header-container {
+    background-color: #fff;
+    border-radius: 8px 8px 0 0;
+    &.b-rd-self {
+      border-radius: 8px;
+    }
+  }
+  .search-bidding-filter-container {
+    width: 100%;
+    background-color: #fff;
+    margin-bottom: 16px;
+    border-radius: 0 0 8px 8px;
+  }
+
+  .search-bidding-list-container {
+    position: relative;
+  }
+  .footer-look-container.adsense {
+    padding: 0;
+    padding-bottom: 16px;
+    ::v-deep {
+      .content {
+        border: none;
+      }
+    }
+  }
+
+  .bi-employ-bid {
+    display: inline-block;
+    cursor: pointer;
+    font-size: 14px;
+    color: #1d1d1d;
+    .iconfont {
+      margin-right: 3px;
+      font-size: 20px !important;
+      vertical-align: top;
+    }
+    .icon-a-Property1shoulu {
+      color: #afafaf;
+    }
+    .icon-a-Property1yishoulu {
+      color: #2abed1;
+    }
+  }
+  .bi-add-button {
+    &.all-add {
+      margin-right: 8px;
+    }
+    display: inline-block;
+    margin-left: 16px;
+    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;
+  }
+  ::v-deep {
+    .a-i-detail {
+      padding-left: 0;
+    }
+    .keyword-tags-container {
+      margin-top: -6px;
+      .el-tag,
+      .add-keyword-actions,
+      .keyword-radio-group {
+        margin-top: 6px;
+      }
+    }
+    //.filter-layout{
+    //  position: unset!important;
+    //}
+    .hasNoData_tiptext {
+      font-size: 14px;
+
+      text-align: center;
+      color: #999999;
+      line-height: 22px;
+    }
+    .el-date-editor.el-input,
+    .el-date-editor.el-input__inner {
+      width: unset;
+      background: transparent;
+      padding: 0;
+    }
+  }
+  .over-run-tips {
+    color: #686868;
+    text-align: right;
+    font-size: 14px;
+    line-height: 22px;
+    padding: 0 20px 16px 0;
+    cursor: pointer;
+  }
+}
+.search-bidding-keywords-tip {
+  background: #fff;
+  padding: 0 0 10px 30px;
+  font-size: 14px;
+  img {
+    vertical-align: middle;
+    width: 15px;
+    margin-right: 5px;
+    position: relative;
+    top: -2px;
+  }
+}
+.tip-toggle-search-mode-container {
+  border-top: 1px solid #0000000d;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 16px 56px;
+  border-bottom-left-radius: 8px;
+  border-bottom-right-radius: 8px;
+  color: #5f5e64;
+  font-size: 13px;
+  line-height: 20px;
+  background: linear-gradient(90deg, #edfdff 0.51%, #ffffff 100%);
+  .tip-action {
+    display: inline-flex;
+    align-items: center;
+  }
+  button {
+    margin-left: 12px;
+    background: #2abed1;
+    padding: 5px 12px;
+    border-radius: 4px;
+    color: #f7f9fa;
+  }
+}
+</style>

+ 1625 - 0
apps/bigmember_pc/src/views/search/sun/model/base.js

@@ -0,0 +1,1625 @@
+import { computed, reactive, ref, toRefs, onMounted,onBeforeMount, onBeforeUnmount, getCurrentInstance, provide } from 'vue'
+import { without, throttle } from 'lodash'
+import { useStore } from '@/store'
+import { useRoute, useRouter } from 'vue-router/composables'
+import { MessageBox } from 'element-ui'
+import  {
+  FilterHistoryAjaxModelRestore,
+  FilterHistoryAjaxModel2ViewModel,
+  FilterModel2ViewModel,
+  getParam,
+  openLinkInWorkspace,
+  InContainer
+} from '@/utils'
+import $bus from '@/utils/bus'
+// 筛选条件动态组件方法
+import { getCreateSearchSchema } from '../constant/index'
+// API 业务模型
+import useQuickSearchModel from '@jy/data-models/modules/quick-search/model'
+// 扩展业务模型
+import { useSearchFilterModel } from './modules/filter'
+import { useSearchInputKeywordsModel } from './modules/filter-keywords'
+import { useSearchListHeaderActionsModel } from './modules/list-header-actions'
+import { useSearchTabsModel } from './modules/tabs'
+// 保存、重置、查看筛选条件业务
+import { saveFilterActionsModel } from './modules/save-filter-actions'
+// 数据导出业务
+import { dataExportActionsModel } from './modules/data-export-actions'
+// 标讯收藏业务
+import { dataCollectActionModel } from './modules/data-collect-actions'
+// 企业分发业务
+import { dataDistributeActionsModel } from './modules/data-distribute-actions'
+// 参标业务
+import { joinBidActionsModel } from './modules/join-bid-actions'
+// 收录业务
+import { dataEmployActionsModel } from './modules/data-employ-actions'
+// 添加业务
+import { dataAddActionsModel } from './modules/data-add-actions'
+// 进行搜索前业务判断
+import { beforeSearchModel } from './modules/before-search'
+// 潜在客户引流--市场分析报告&&超前项目推荐数据请求
+import { recommendCardModel } from './modules/recommend-card'
+// 搜索历史业务模型
+import useSearchHistoryModel from '@jy/data-models/modules/quick-search-history/model'
+
+export default function () {
+  const that = getCurrentInstance().proxy
+
+  const router = useRouter()
+  // 是否是免费用户
+  const isFree = computed(() => {
+    return false
+    // return useStore().getters['user/isFree']
+  })
+  // 企业管理员
+  const isEntAdmin = computed(() => {
+    return false
+    // return useStore().getters['user/isEntAdmin']
+  })
+  // 部门管理员
+  const isDepartmentAdmin = computed(() => {
+    // return false
+    // return useStore().getters['user/isDepartmentAdmin']
+  })
+  // 是否登录
+  const isLogin = computed(() => {
+    return useStore().getters['user/loginFlag']
+  })
+  // 用户权限
+  const userPowerInfo = computed(() => {
+    return useStore().getters['user/getInfo']
+  })
+  // 是否是vip
+  const isVip = computed (() => {
+    // const { entniche, memberStatus, vipStatus } = userPowerInfo.value
+    // return entniche || memberStatus > 0 || vipStatus > 0
+    return true
+  })
+  // 是否是老用户
+  const isOld = computed (() => {
+    const { isOld } = userPowerInfo.value
+    return isOld
+  })
+
+  // 获取搜索历史业务数据模型
+  const searchHistoryModel = useSearchHistoryModel({ type: 1 })
+  const { getHistoryQuery, clearHistoryQuery, searchHistoryList } = searchHistoryModel
+
+  // 标讯搜索历史列表
+  const bidHistoryList = computed(() => {
+    return searchHistoryList.value
+  })
+
+  // 跨组件通信 将历史搜索列表数据注入 在SearchHeader子组件中引用
+  provide('searchHistoryList', bidHistoryList)
+
+  // 是否在工作台内
+  // 本地调试,可改为工作台内isInApp = ref(true),  isInWeb = ref(false)   提交记得改回!
+  // const isInApp = ref(InContainer.inApp) // InContainer.inApp
+  // const isInWeb = ref(InContainer.inWeb) // InContainer.inWeb
+
+  const isInApp = ref(true)
+  const isInWeb = ref(false)
+
+  // const isInApp = ref(false)
+  // const isInWeb = ref(true)
+
+  // 是否是渠道商
+  const cooperateCode = ref(false)
+  // 一切都好渠道商,是否是渠道商
+  const cookieInfo = document.cookie.split('; ')
+  cooperateCode.value = cookieInfo.some(item => item.indexOf('channelCode') > -1)
+  onMounted(() => {
+    window.addEventListener('scroll', watchScroll)
+  })
+
+  // 物业专版
+  const inBIPropertyIframe = useRoute().query.property === 'BIProperty'
+  // 营销BI嵌套
+  const inResourceBIIframe = useRoute().query.resource === 'BI'
+
+  // 是否是拓普权限
+  // const inBITopNet = useRoute().query.crm_type
+  const inBITopNet = false
+
+  // 是否山川应用嵌入环境 添加操作按钮 (个人年终报告嵌套)
+  // const inInjectBI = useRoute().query.report === 'bi' || (location.href.indexOf('/jylab/bi/index') !== -1)
+  const inInjectBI = false
+
+  // 是否是BI嵌套页面
+  const isInBI = computed(() => {
+    return inResourceBIIframe || inResourceBIIframe || inInjectBI
+  })
+
+   // 是否是领域化页面(医械通)
+  const isBidField =  location.href.includes('/jylab/medical/index')
+
+  const { goback, keywords: urlKeywords, selectType: urlSelectType } = useRoute().query
+
+  // 缓存存储配置
+  const storageConfig = {
+    listTab: {
+      key: 'pc_search_bidding_listTabActive',
+      _storage: localStorage
+    },
+    // 筛选条件上次搜索筛选项缓存key
+    filter: {
+      key: 'pc_search_bidding_lastFilters',
+      _storage: localStorage
+    },
+    // 页面tab(筛选项的searchGroup)
+    searchGroup: {
+      key: 'pc_search_bidding_lastSearchGroup',
+      _storage: localStorage
+    }
+  }
+  // 解构基础业务
+  const APIModel = useQuickSearchModel({
+    type: 'search-bid'
+  })
+  /**
+   * 列表相关model
+   */
+  const {
+    list,
+    total,
+    loading,
+    finished,
+    selectIds,
+    listIds,
+    searchResultCount,
+    isSelectSomeCheckbox,
+    selectCheckboxCount,
+    isSelectListAllCheckbox,
+    doToggleItemSelection,
+    doToggleListSelection,
+    doClearAllSelection,
+    doQuery: doRunQuery
+  } = APIModel
+  /**
+   * 关键词搜索相关
+   */
+  const {
+    inputKeywordsState,
+    searchModelOptions,
+    getFormatAPIParams: getFormatOfInputKeywords,
+    updateInputKeywordsState,
+  } = useSearchInputKeywordsModel()
+  /**
+   * 筛选v-model数据
+   */
+  const {
+    filterState,
+    filterBase,
+    getFormatAPIParams: getFormatOfFilter,
+    updateFilterBase
+  } = useSearchFilterModel({ inBIPropertyIframe, isFree, isInApp, isBidField })
+  /**
+   * 列表头操作
+   */
+  const {
+    limitActions,
+    headerActions,
+    listItemStyleType,
+    activeItemStyleType,
+    activeHeaderActions,
+    disabledHeaderActions
+  } = useSearchListHeaderActionsModel()
+
+  $bus.$on('bidding:updateInputKeywords', function (obj) {
+    updateInputKeywordsState(obj)
+  })
+
+  /**
+   * 搜索前校验
+   */
+  const {
+    getWhiteList,
+    onTheWhiteList,
+    checkAndClearTextIncludesCommonWords
+  } = beforeSearchModel({ inputKeywordsState, urlKeywords })
+
+  onBeforeMount( async() => {
+    try{
+     await getWhiteList()
+    } catch (e){
+      console.warn('e')
+    }
+  })
+
+  /**
+   * 页面tab切换Model
+   */
+  const SearchTabsModel = useSearchTabsModel({ showTabs2: !isInApp.value, inInjectBI })
+  const { activeTab, doChangeTab } = SearchTabsModel
+  // tab切换处理
+  function onChangeTab (item) {
+    filterState.value = Object.assign(filterState.value, {
+      subtype: []
+    })
+    if(isInApp.value) {
+      doChangeTab(item)
+      doQuery({}, 'tab')
+    } else {
+      if(item.link) {
+        location.replace(item.link)
+      }
+    }
+  }
+
+  /**
+   * 筛选条件动态配置处理
+   */
+  const {
+    createSearchBidBaseSchema,
+    createSearchBidMoreSchema
+  } = getCreateSearchSchema({ bi: inBIPropertyIframe })
+  // 处理当前信息类型--展示数据类型
+  const  infoTypeDataType = computed(() =>  {
+    let result = 'all'
+    switch (activeTab.value) {
+      case 0:
+        result = 'all'
+        break
+      case 1:
+        result = 'base'
+        break
+      case 2:
+        result = 'advance'
+        break
+    }
+    return result
+  })
+
+
+  // 筛选条件动态配置处理
+  function disposeFilterSchema() {
+    const conf = computed(() => {
+      return {
+        isInApp: isInApp.value,
+        isLogin: isLogin.value,
+        vipUser: isVip.value && isInApp.value,
+        oldUser: isOld.value && isInApp.value,
+        showVip: isLogin.value && isInApp.value,
+        infoType: infoTypeDataType.value,
+        isBidField,
+        inInjectBI,
+        isVip: isVip.value
+      }
+    })
+
+    // 标准筛选
+    const SearchBidBaseSchema = ref([])
+    const SearchBidMoreSchema = ref([])
+    // 更多筛选
+    const searchBidMoreFreeSchema = ref([])
+    const searchBidMoreVipSchema = ref([])
+
+    SearchBidBaseSchema.value = createSearchBidBaseSchema(conf.value)
+    SearchBidMoreSchema.value =  createSearchBidMoreSchema(null, conf.value)
+
+    searchBidMoreFreeSchema.value = SearchBidMoreSchema.value?.filter(
+      (s) => !s.vipMark
+    )
+    searchBidMoreVipSchema.value = SearchBidMoreSchema.value?.filter(
+      (s) => s.vipMark
+    )
+
+    console.log(SearchBidMoreSchema.value)
+
+
+    function doUpdateData(schema, type) {
+      const payload = schema || conf.value
+      if(type === 'more') {
+        SearchBidMoreSchema.value = createSearchBidMoreSchema(schema, conf.value)
+      } else {
+        SearchBidBaseSchema.value = createSearchBidBaseSchema(payload)
+      }
+    }
+
+    return {
+      SearchBidBaseSchema,
+      SearchBidMoreSchema,
+      searchBidMoreFreeSchema,
+      searchBidMoreVipSchema,
+      doUpdateData
+    }
+  }
+
+  // 列表状态
+  const listState = reactive({
+    finished,
+    loading,
+    pageNum: 1,
+    pageSize: 50,
+    total,
+    pageTotal: 0
+  })
+  // 当前展示的列表
+  const activeList = computed(() => {
+    let arr = []
+    if(list.value && list.value.length > 0) {
+      arr = list.value.map((v) => {
+        v._id = v.id
+        v.checked = selectIds.value.includes(v.id)
+        // 中标单位联系方式处理
+        if(!v.winnerTel &&  v.winnerInfo && v.winnerInfo.length > 0) {
+          v.winnerTel = v.winnerInfo[0].winnerTel || ''
+          v.winnerPerson = v.winnerInfo[0].winnerPerson || ''
+        }
+        const region = []
+        if(v.area && (v.city && v.city.indexOf(v.area) === -1)) {
+          region.push(v.area)
+        }
+        if(v.city) {
+          region.push(v.city)
+        }
+        if(v.district) {
+          region.push(v.district)
+        }
+        v.region = region.join('-')
+        return v
+      })
+    }
+    return arr
+  })
+  // 表格展示的数据
+  const tableList = ref([])
+
+  // 搜索关键词限制
+  const interceptKeywords = reactive({
+    interceptOtherWords: '',
+    interceptLimit: 0
+  })
+
+  // 根据权限处理列表头部的操作按钮展示
+  const limitFilterHeaderActions =  computed(() => {
+    // 数据导出-非营销都展示
+    if(!inResourceBIIframe) {
+      limitActions.value['data-export'] = true
+    }
+
+    // BI营销以及个人分析报告不展示以下操作
+    if(!inResourceBIIframe && !inInjectBI) {
+      limitActions.value['refined-list'] = true
+      limitActions.value['table'] = true
+      limitActions.value['collect-bid'] = true
+      // 登录后才展示详细列表
+      if(isLogin.value) {
+        limitActions.value['detailed-list'] = true
+      }
+    }
+
+    // 展示收录
+    if(canBatchEmploy.value && inResourceBIIframe) {
+      limitActions.value['employ-bid'] = true
+    }
+    // 展示分发
+    if((isEntAdmin.value || isDepartmentAdmin.value) && !inBIPropertyIframe && !inResourceBIIframe) {
+      limitActions.value['distribute-bid'] = true
+    }
+    return headerActions.value
+  })
+
+  // search-list 组件所需参数
+  const searchListProps = computed(() => {
+    return {
+      isSelectAllCheckbox: isSelectListAllCheckbox.value,
+      isSelectSomeCheckbox: isSelectSomeCheckbox.value,
+      selectCheckboxCount: selectCheckboxCount.value,
+      searchResultCount: searchResultCount.value,
+      headerActions: limitFilterHeaderActions.value,
+      list: activeList.value,
+      listState
+    }
+  })
+
+  /**
+   * 切换列表展示风格
+   * @param type - 可选风格 ['refined-list', 'detailed-list', 'table']
+   */
+  function doChangeItemStyleType(type) {
+    const styleTypes = ['refined-list', 'detailed-list', 'table']
+    if (!styleTypes.includes(type)) {
+      return console.warn('Not find style type!')
+    }
+    listItemStyleType.value = type
+    activeHeaderActions.value = without(
+      activeHeaderActions.value,
+      ...styleTypes
+    )
+    activeHeaderActions.value.push(type)
+    // 记录列表风格切换--存到缓存
+    if(type !== 'table') {
+      const { _storage, key } = storageConfig['listTab']
+      _storage.setItem(key, type)
+    }
+  }
+
+  /**
+   * 恢复以前选过的tab
+   */
+  function restoreListTabActive() {
+    const { _storage, key } = storageConfig['listTab']
+    const storage = _storage.getItem(key)
+    if (storage) {
+      if(storage === 'detailed-list' && isVip.value && isLogin.value) {
+        doChangeItemStyleType('detailed-list')
+      } else {
+        doChangeItemStyleType('refined-list')
+      }
+    } else {
+      _storage.setItem(key, 'refined-list')
+    }
+  }
+
+
+  /**
+   * 列表顶部按钮操作事件统一入口
+   * @param item - 按钮原型
+   * @param item.key - 按钮标识
+   */
+  function doListHeaderAction(item, $event) {
+    const { key } = item
+    switch (key) {
+      case 'refined-list': {
+        doChangeItemStyleType(key)
+        break
+      }
+      case 'detailed-list': {
+        detailListClick(key)
+        break
+      }
+      case 'table': {
+        doChangeItemStyleType(key)
+        break
+      }
+      case 'data-export': {
+        onClickDataExport()
+        break
+      }
+      case 'collect-bid' : {
+        onClickDataCollect($event)
+        break
+      }
+      case 'distribute-bid': {
+        onClickDataDistribute()
+        break
+      }
+      case 'employ-bid' : {
+        onClickDataEmploy()
+        break
+      }
+    }
+  }
+  function detailListClick(key) {
+    if (isFree.value) {
+      openListVipDialog()
+      return
+    }
+    doChangeItemStyleType(key)
+  }
+  // 全选复选框事件
+  function doChangeAllSelect(type) {
+    doToggleListSelection(type)
+  }
+
+  // 单个复选框事件
+  function doChangeSelect(item) {
+    doToggleItemSelection(item.id)
+  }
+
+  // 分页事件
+  function doChangePageNum(page) {
+    listState.pageNum = page
+    doQuery({}, 'pageNumChange', page)
+  }
+  // 分页大小事件
+  function doChangePageSize(size) {
+    listState.pageSize = size
+    listState.pageNum = 1
+    doQuery({}, 'pageNumChange', 1)
+  }
+  // 登录
+  $bus.$on('bidding:goLogin', goLogin)
+  function goLogin() {
+    that.$showLoginDialog()
+  }
+  // 跳转详情页
+  function toDetail(item) {
+    let aHref = ".html"
+    if (inputKeywordsState.value.matchKeys && inputKeywordsState.value.matchKeys.length > 0){
+      const kds = inputKeywordsState.value.matchKeys.join(' ')
+      aHref+= '?kds=' + encodeURIComponent(kds)
+    }
+    if (getParam('resource') === 'BI') {
+      if (getParam('property') === 'BIProperty') {
+        aHref +=
+          aHref.indexOf('?') > -1
+            ? '&resource=BI&property=BIProperty'
+            : '?resource=BI&property=BIProperty'
+      } else {
+        if (inBITopNet) {
+          aHref +=
+            aHref.indexOf('?') > -1
+              ? '&resource=BI&crm_type=1'
+              : '?resource=BI&crm_type=1'
+        } else {
+          aHref += aHref.indexOf('?') > -1 ? '&resource=BI' : '?resource=BI'
+        }
+      }
+    }
+
+    const id = item.id
+    try {
+      that.$visited.push({
+        type: 'articleContent',
+        id:  id
+      })
+      item.visited = true
+    } catch(e) {
+      console.log(e)
+    }
+
+
+    if(isLogin.value) {
+      const prefix = isInApp.value ? '/article/content/' : '/nologin/content/'
+      const targetLink = `${prefix}${id}${aHref}`
+
+      // 在iframe里,往工作桌面跳转。不在iframe里,正常跳转
+      openLinkInWorkspace(isInApp.value, {
+        url: targetLink,
+        newTab: true,
+      })
+    } else{//没有登录跳转新的详情
+      // 一切都好渠道合作页,未登录跳转需要弹出登录,重置到已登录后的详情页
+      if(cooperateCode.value) {
+        window.location.href = '/notin/page'
+        // openLoginDig(null, "/article/content/"+thisId+aHref)
+      } else {
+        const targetLink = `/nologin/content/${id}${aHref}`
+        window.open(targetLink)
+      }
+    }
+  }
+  // 列表tag跳转处理
+  function tagToDetail (item, label) {
+    if(label === 'subtype' && (item['subtype'] === '拟建' || item['subtype'] === '采购意向')) {
+      return
+    }
+    let link = ''
+    if(label === 'subtype') {
+      link = item['subtypeUrl']
+    } else if(label === 'area'){
+      link = item['areaUrl']
+    }
+    if(link) {
+      window.open(link)
+    }
+  }
+
+  // 获取 store getters
+  const userType = computed(() => {
+    return useStore().getters['user/userType']
+  })
+
+  /**
+   * 变更搜索模式
+   * @type {boolean}
+   */
+  // P260需求精准搜索无结果时 自动切换到模糊搜索(查询) 模糊搜索也无结果时再切回精准(不查询)
+  // 条件:1.精准搜索模式 2.返回无数据 3.有主关键词或附加词
+  let autoSwitchModel = false
+  function changeSearchMode (searchType) {
+    const total = listState.total
+    const { searchMode, additionalWords,  input } = inputKeywordsState.value
+    const jzModel = searchMode === '0' && (additionalWords || input)
+    const mhModel = searchMode === '1' && (additionalWords || input)
+    if(!autoSwitchModel && jzModel && total === 0) {
+      autoSwitchModel = true
+      inputKeywordsState.value.searchMode = '1'
+      doQuery({}, searchType)
+    }
+    if(mhModel && autoSwitchModel) {
+      if( total > 0) {
+        if(!goback) {
+          that.$toast('精准搜索无结果,已为您自动切换到模糊搜索')
+        }
+        autoSwitchModel = false
+      } else {
+        // 模糊搜索无结果时 再切回精准
+        inputKeywordsState.value.searchMode = '0'
+        autoSwitchModel = false
+      }
+    }
+  }
+
+  /**
+   * 格式化请求参数
+   * @param [params] - 可选值,部分情况会提供,默认会和该函数返回值进行合并
+   */
+
+  function getParams(params = {}) {
+    // 合并所有模型的搜索筛选项
+    const result = Object.assign(
+      {
+        searchGroup: activeTab.value,
+        reqType: 'lastNews', // cache:空搜索缓存数据;lastNews:最新数据
+        pageNum: listState.pageNum,
+        pageSize: listState.pageSize,
+        // 该接口与用户身份有关
+        _expand: {
+          type: userType.value
+        }
+      },
+      getFormatOfInputKeywords(),
+      getFormatOfFilter(),
+      params
+    )
+    return result
+  }
+
+  function beforeSearchCheck (searchType) {
+    // 第一次搜索或者tab切换不校验白名单规则
+    if (searchType) {
+      return true
+    }
+    // 如果在反爬白名单,则空搜索刷新搜索结果(即允许空搜索)
+    // 不在,则不允许空搜索(此处空搜索指的是主搜索框是否为空)
+    if (!onTheWhiteList.value) {
+      const searchKeywords = inputKeywordsState.value.input
+      const hasOneKey = (filterState.value.buyer?.length || filterState.value.winner?.length ||  filterState.value.agency?.length) > 0
+      // 关键词去两边空格后为空
+      if (!searchKeywords && inputKeywordsState.value.additionalWords.length === 0) {
+        if (!hasOneKey) {
+          that.$toast('请先输入关键词')
+          return false
+        }
+      } else {
+        // 判断关键词中是否有通用词,并清空对应通用词的项
+        const hasCommonWords = checkAndClearTextIncludesCommonWords(searchKeywords)
+        if (hasCommonWords) {
+          that.$toast('请输入项目名称等关键词')
+          return false
+        }
+      }
+    }
+    return true
+  }
+  // 搜索前 处理一些事情
+  function beforeSearchSomething (pageNum) {
+    // 列表清空
+    list.value = []
+    listState.total = 0
+    // 列表重新获取时
+    if(!pageNum) {
+      listState.pageNum = 1
+      // 清空已选中数据
+      doClearAllSelection()
+      // 清空表格数据
+      tableList.value = []
+    }
+    // 如果列表展示状态为table,将列表展现形式换成详细列表或者精简列表
+    if(activeItemStyleType.value === 'T') {
+      restoreListTabActive()
+    }
+
+    //P271需求--潜客圈进引流需求
+    if(!advancedInfo.show && !getShowChart.value && inputKeywordsState.value.input) {
+      getCustomReportData({ keywords:  inputKeywordsState.value.input })
+    }
+
+  }
+  /**
+   * 统一查询入口
+   * - 拦截 doQuery 进行一些返回值处理
+   * @param [params] - 可选值,默认会和 getParams(params) 返回值进行合并
+   */
+  function doQuery(params = {}, searchType, pageNum) {
+    const bSearch = beforeSearchCheck(searchType)
+    if (!bSearch) {
+      return
+    }
+    beforeSearchSomething (pageNum)
+    // Ajax请求
+    return doRunQuery(getParams(params)).then((res) => {
+      // 搜索重新获取搜索历史
+      getHistoryQuery({ type: 1 })
+      afterQueryAjax(res, searchType)
+    })
+  }
+  // 处理查询请求后的数据以及其他业务
+  function afterQueryAjax (res, searchType) {
+    // 存储筛选条件
+    if(isInApp.value && isLogin.value && listState.pageNum === 1 && !inBIPropertyIframe) {
+      saveSearchGroupToLocal()
+
+      if(inputKeywordsState.value.input) {
+        saveFilterToLocal()
+      }
+    }
+    // 变更搜索模式
+    changeSearchMode(searchType)
+
+    const { origin } = res
+
+    listState.pageTotal = origin?.count || 0
+    // 限制关键词
+    interceptKeywords.interceptOtherWords = origin?.interceptOtherWords
+    interceptKeywords.interceptLimit = origin?.interceptLimit
+
+    let matchKeys = []
+    if(inputKeywordsState.value.input) {
+      matchKeys.push(inputKeywordsState.value.input)
+    }
+    if(inputKeywordsState.value.selectMoreKey) {
+      if(inputKeywordsState.value.additionalWords?.length) {
+        matchKeys = matchKeys.concat(inputKeywordsState.value.additionalWords)
+      }
+    }
+    // 用于搜索关键词高亮
+    inputKeywordsState.value.matchKeys = res.origin?.keyWords?.split(' ') || matchKeys
+
+    //表格列表数据--只取第一页的前20条展示
+    if(listState.pageNum === 1) {
+      if(listState.total > 0) {
+        tableList.value = list.value.slice(0, 20)
+      } else {
+        tableList.value = []
+      }
+    }
+
+    // 列表无数据,禁用数据导出按钮
+    if(listState.total === 0) {
+      disabledHeaderActions.value = ['data-export']
+    } else {
+      disabledHeaderActions.value = []
+    }
+
+    if(isLogin.value) {
+      // 获取参标的数据
+      getJoinBidInfo(listIds.value)
+      // BI 是否批量收录,获取收录数据
+      if(inBIPropertyIframe || inResourceBIIframe) {
+        getEmployData(listIds.value)
+      }
+      // 个人报告嵌套BI页面
+      if(inInjectBI) {
+        getBidAddInfos()
+      }
+
+    }
+    list.value = list.value.map(item => {
+      // 是否已读字段
+      const visited = that.$visited.check({
+        type: 'articleContent',
+        id: item.id
+      })
+      that.$set(item, 'visited', visited)
+
+      // 收藏字段显示
+      that.$set(item, 'collection', item.isCollected ? 1 : 0)
+
+      // 收录字段
+      that.$set(item, 'isEmploy', false)
+
+      return item
+    })
+
+    // 列表底部-是否展示需要切换模糊搜索的tip
+    checkToggleSearchMode({
+      pageNum: 1,
+      count: origin?.total || 0,
+      blurCount: origin?.bCount || 0,
+      searchMode: Number(inputKeywordsState.value.searchMode)
+    })
+  }
+
+  // 是否展示筛选条件
+  const showFilter = ref(true)
+  // 展开收起筛选条件
+  function toggleFilter() {
+    showFilter.value = !showFilter.value
+  }
+
+  // 组合好的组件格式的筛选条件
+  function packageFilter () {
+    const originParams = Object.assign(
+      {
+        searchGroup: activeTab.value,
+      },
+      filterState.value,
+      inputKeywordsState.value
+    )
+
+    return originParams
+  }
+
+  // 格式化当前已选筛选条件
+  // P611需求,展示当前已选条件文案
+  const showCurrentFilterText = computed(() => {
+    if(!(isLogin.value && isInApp.value && !inBIPropertyIframe)) {
+      return {}
+    }
+    const originParams = packageFilter()
+    const text = FilterModel2ViewModel.formatAll(originParams)
+    // 融创
+    if (originParams.mobileTag) {
+      text.mobileTagText = originParams.mobileTag.includes('all') ? '' : originParams.mobileTag.join(',')
+    }
+    return text
+  })
+
+// P271需求,潜客圈进,展示超前项目or分析报告引流
+  const recommendCardCircleModel = recommendCardModel({ that })
+  const {
+    getShowChart,
+    advancedInfo,
+    getCustomReportData,
+  } = recommendCardCircleModel
+
+  /******开通超级订阅弹窗start**********/
+
+  // 开通超级订阅弹窗配置
+  const vipDialogConfig = reactive({
+    type: 'filter',
+    show: false,
+    text: '立享更多搜索权限,寻找商机更精准'
+  })
+
+  // 筛选条件打开超级订阅弹窗
+  function openFilterVipDialog () {
+    vipDialogConfig.type = 'filter'
+    vipDialogConfig.show =  true
+    vipDialogConfig.text = '立享更多搜索权限,寻找商机更精准'
+  }
+  // 关闭超级订阅弹窗
+  function closeVipDialog () {
+    vipDialogConfig.show = false
+  }
+  // 数据列表--开通超级订阅弹窗
+  function openListVipDialog () {
+    vipDialogConfig.type = 'list'
+    vipDialogConfig.show = true
+    vipDialogConfig.text = '立享列表展示更多公告关键信息,例如:采购单位、中标单位、招标代理机构等,提高公告查看效率'
+  }
+
+  const vipDialogConf = toRefs(vipDialogConfig)
+
+  // 筛选条件无权限提示开通超级订阅弹窗
+  $bus.$on('search:filter:no-power', openFilterVipDialog)
+
+  /******开通超级订阅弹窗end**********/
+
+  /** 保存、重置筛选条件部分 start ****/
+
+  const disposeFilterActionModel = saveFilterActionsModel({ isBidField })
+  const {
+    historyFilterDialogVisible, //已存筛选条件弹窗
+    getFilterHistoryList, // 已存筛选条件列表
+    checkFilterPass, // 检测是否可以保存筛选条件
+  } = disposeFilterActionModel
+
+  /**
+   * 保存筛选条件
+   */
+  async function onSaveFilter() {
+    const originParams = packageFilter()
+    const config = { filter: originParams }
+    await checkFilterPass(config)
+  }
+
+  /**
+   * 恢复筛选条件
+   * @param item
+   */
+  function onSelectedFilter (item) {
+    // 动态插入的存储的筛选条件
+    const _expand = getExpandSearchParams()
+    const { isPay } = item
+    // 恢复选中状态
+    if (isFree?.value && isPay) {
+      MessageBox.confirm('已存筛选条件包含仅限会员用户可用的筛选条件,如需使用请开通超级订阅', '',{
+        confirmButtonText: '开通超级订阅',
+        cancelButtonText: '使用免费条件',
+        center: true,
+        showClose: false,
+        customClass: 'filter-delete-messagebox',
+        confirmButtonClass: 'btn-group confirm-btn',
+        cancelButtonClass: 'btn-group cancel-btn',
+      })
+        .then(() => {
+          window.open('/swordfish/page_big_pc/free/svip/buy', '_blank')
+        })
+        .catch(() => {
+          // 之前是付费用户,现在变成普通用户了,需要重置一些vip选项!!!!
+          const params = Object.assign(item,{
+            bidField: isBidField ? 'medical' : '',
+            publishTime: 'thisyear',
+            selectType: ['title', 'content'],
+            subtype: item.subtype,
+            regionMap: {},
+            industry: item.industry,
+            fileExists: item.fileExists,
+            price: item.price,
+            buyerclass: {},
+            buyertel: '',
+            winnertel: '',
+            notkey: '',
+            buyer: '',
+            winner: '',
+            agency: '',
+          })
+          restoreFilter(params, 'saveBack', {_expand})
+          historyFilterDialogVisible.value = false
+        })
+    } else {
+      restoreFilter(item, 'saveBack', {_expand})
+    }
+  }
+  function restoreFilter (viewFilter, type, expandFilter) {
+    let resultFilter = {}
+    if(type === 'saveBack') {
+      resultFilter = FilterHistoryAjaxModelRestore.formatAll(viewFilter)
+    } else {
+      resultFilter = viewFilter
+    }
+    // 额外需要回显的拓展数据
+    if(expandFilter && typeof expandFilter === 'object') {
+      Object.assign(resultFilter, expandFilter)
+    }
+    doChangeTab({ key: resultFilter.searchGroup })
+    let  _expand = resultFilter._expand || {}
+
+    filterState.value = {
+      bidField: isBidField ? 'medical' : '',
+      publishTime: resultFilter.publishTime,
+      selectType: resultFilter.selectType,
+      subtype: resultFilter.subtype,
+      regionMap: resultFilter.regionMap,
+      industry: resultFilter.industry,
+      fileExists: resultFilter.fileExists,
+      price: resultFilter.price,
+      buyerclass: resultFilter.buyerclass,
+      buyertel: resultFilter.buyertel,
+      winnertel: resultFilter.winnertel,
+      notkey: resultFilter.notkey,
+      buyer: resultFilter.buyer,
+      winner: resultFilter.winner,
+      agency: resultFilter.agency,
+      ..._expand,
+      _expand: _expand
+    }
+    inputKeywordsState.value = {
+      input: resultFilter.input,
+      // 关键词筛选模式
+      searchMode: resultFilter.searchMode,
+      // 接口返回的关键词,如果模糊搜索,服务端会根据一定规则切割多个词
+      matchKeys: resultFilter.matchKeys,
+      // 附加关键词筛选模式
+      wordsMode: resultFilter.wordsMode,
+      // 附件关键词组
+      additionalWords: resultFilter.additionalWords,
+      selectMoreKey: resultFilter.additionalWords?.length > 0
+    }
+    if(type === 'saveBack') {
+      historyFilterDialogVisible.value = false
+    }
+    doQuery({}, 'not-filter')
+  }
+
+  // 重置筛选条件
+  function onResetFilter () {
+    updateFilterBase()
+    doQuery({}, 'not-filter')
+  }
+ /**保存、重置、查看筛选条件end******/
+
+ /*** 筛选条件头部、列表头部滚动start *****/
+
+  // 页面滚动方法 用于悬浮吸顶,将【筛选条件】置顶
+  const fixedTop = ref(false)
+  // 表格顶部操作方法滚动吸顶
+  const tableFixedTop = ref(false)
+
+  function watchScroll () {
+    const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
+    const watchFilter = document.querySelector('.searchTender').offsetTop - 25// 25为margin-top值
+    const watchTable = document.querySelector('.search-bidding-list-container').offsetTop - 15 // 15为margin-top值
+    if (scrollTop >= watchFilter) {
+      fixedTop.value = true
+      tableFixedTop.value = false
+    } else {
+      fixedTop.value = false
+    }
+    if (scrollTop >= watchTable) {
+      tableFixedTop.value = true
+      fixedTop.value = false
+    } else {
+      tableFixedTop.value = false
+    }
+  }
+
+  /*** 筛选条件头部、列表头部滚动end *****/
+
+  /*******数据导出 start **********/
+  const {
+    dataExport,
+    setExport,
+    exportDialogChange,
+    showDataExportDialog,
+    toPayDataExport
+  } = dataExportActionsModel ()
+
+  // 数据导出操作
+  function onClickDataExport (table) {
+    if(!isLogin.value) {
+      goLogin()
+      return
+    }
+
+    const originParams = packageFilter()
+    const params = {
+      listState: listState,
+      selectCheckboxCount: selectCheckboxCount.value,
+      selectIds: selectIds.value,
+      filter: originParams
+    }
+    if(table) {
+      toPayDataExport(params)
+    } else {
+      dataExport(params)
+    }
+
+  }
+
+  /*******数据导出 end ***********/
+
+
+  /********* 标讯收藏部分start ********/
+  const {
+    onCollect
+  } = dataCollectActionModel({ that })
+
+  // 批量标讯收藏
+  function onClickDataCollect (e) {
+    const config = {
+      total: listState.total,
+      isSelectSomeCheckbox: isSelectSomeCheckbox.value,
+      event: e,
+      selectIds: selectIds.value,
+    }
+    onCollect(config, 'batch')
+  }
+
+  // 单个标讯收藏
+  function onClickSingleCollect (data) {
+    const config = {
+      item: data.item,
+      total: listState.total,
+      event: data.event
+    }
+    onCollect(config, 'single')
+  }
+
+  // 挂载搜索事件,供模块js回调
+  /**
+   * params: {
+   *   type:  C存储, R取消
+   *   ids: 要操作的id数组
+   * }
+   */
+  $bus.$on('bidding:updateListCollectStatus', function (obj) {
+    const { type, ids } = obj
+    list.value = list.value.map(temp => {
+      if (type === 'R' && ids.includes(temp.id)) {
+        that.$set(temp, 'collection', 0)
+      } else if(type === 'C' && ids.includes(temp.id)){
+        that.$set(temp, 'collection', 1)
+      }
+      return temp
+    })
+  })
+  /********* 标讯收藏部分end ********/
+
+  /********* 分发部分start ********/
+  const {
+    usePowerRef,
+    openDistribute,
+    doSubmitDistribute
+  } = dataDistributeActionsModel()
+
+  function onClickDataDistribute () {
+    openDistribute(selectIds.value)
+  }
+  /*********分发部分end ********/
+
+  /********参标start *********/
+  // 参标只有莱茵有权限
+  const {
+    BidrenewalDialogRef,
+    getJoinBidInfo,
+    onJoinBid
+  } = joinBidActionsModel ()
+
+  // 窗口切换刷新参标数据
+  document.addEventListener('visibilitychange', function () {
+    if (document.visibilityState === 'visible') {
+      that.$visited.refreshVisited()
+      if(isLogin.value) {
+        getJoinBidInfo(listIds.value)
+      }
+    }
+  })
+
+  // 变更列表数据的参标状态
+  $bus.$on('bidding:updateListJoinStatus', function (obj) {
+    const {item, joinData } = obj
+    if(joinData) {
+      for (const temp of joinData) {
+        list.value = list.value.map(v => {
+          if (temp.id === v.id) {
+            that.$set(v, 'joinBid', Boolean(temp.value))
+          }
+          return v
+        })
+      }
+    } else {
+      list.value = list.value.map((temp) => {
+        if (temp.id === item?.id) {
+          that.$set(temp, 'joinBid', true)
+        }
+        return temp
+      })
+    }
+  })
+  /*******参标end ***********/
+
+  /*******收录start *********/
+  const {
+    showPropertyDialog,
+    propertyIframeSrc,
+    canBatchEmploy,
+    getEmployData,
+    onSingleEmploy,
+    onBatchEmploy
+  } = dataEmployActionsModel({ inBIPropertyIframe })
+
+  // 变更列表收录状态
+  $bus.$on('bidding:updateDateEmployStatus', function (obj) {
+    const { type, employIds } = obj
+    list.value = list.value.map(function (item) {
+      // 添加收录
+      if(type === 'add' && employIds.includes(item.id)) {
+        item.isEmploy = true
+      }
+      // 取消收录
+      if(type === 'cancel' && employIds.includes(item.id)) {
+        item.isEmploy = false
+      }
+      return item
+    })
+  })
+  // 批量收录
+  function onClickDataEmploy() {
+    onBatchEmploy(selectIds.value)
+  }
+  /*******收录end ***********/
+
+  /*****BI添加操作start*********/
+  const {
+    getBidAddInfos,
+    doAddInfoOfBI
+  } = dataAddActionsModel()
+
+  $bus.$on('bidding:updateDataAddStatus', function(params) {
+    const { type, ids } = params
+    list.value = list.value.map(function (item) {
+      // 添加收录
+      if(type === 'add' && ids.includes(item.id)) {
+        item.isAdd = true
+      }
+      // 取消收录
+      if(type === 'cancel' && ids.includes(item.id)) {
+        item.isAdd = false
+      }
+      return item
+    })
+  })
+  // 批量添加
+  function onAddInfoOfBI () {
+    doAddInfoOfBI({ ids: selectIds.value })
+  }
+  // 单个添加
+  function onSingleAddInfo (item) {
+    doAddInfoOfBI({ item })
+  }
+  /*****BI添加操作end*********/
+
+  // 处理数据列表为空时,需要展示的提示文案包含时间
+  const timeSelectorText = computed(() => {
+    const publishTime = filterState.value.publishTime
+    const split = inBIPropertyIframe ? '-' : '_'
+    const { publishTimeText } = FilterHistoryAjaxModel2ViewModel.formatTime(publishTime, split)
+    let result = ''
+    if(publishTimeText) {
+      if(publishTimeText.includes('最')) {
+        result = publishTimeText.replace('最', '')
+      } else if(publishTimeText.includes('以后')){
+        result = publishTimeText.replace('以后', '')
+      }else if(publishTimeText.includes('以前')) {
+        result = publishTimeText.replace('以前', '')
+      } else {
+        result = publishTimeText
+      }
+    } else {
+      result = inBIPropertyIframe ? '近5年' : '近一年'
+    }
+
+    return result
+  })
+
+
+  /*********页面留资相关************/
+    // 打开留资弹窗
+  const collectElementRef = ref(null)
+  // 免费用户点免费体验留资
+  function onFreeTaste () {
+    if( collectElementRef.value) {
+      collectElementRef.value.isNeedSubmit('jylab_see500_plus', () => {
+      })
+    }
+  }
+
+  /*******工作台跳转start***********/
+  function guideGoWorkSpace () {
+    if (!isLogin.value) {
+      goLogin()
+      return
+    }
+    goWorkSpace()
+  }
+  // 跳转到工作台
+  function goWorkSpace () {
+    const { key, _storage } = storageConfig.filter
+    const goHref_ = location.origin + '/jylab/supsearch/index.html'
+    // 组件筛选条件
+    const originParams = packageFilter()
+    _storage.setItem(key, JSON.stringify(originParams))
+
+    // window.location.replace('/swordfish/page_big_pc/search/bidding?goback=true')
+    window.location.replace(`/page_workDesktop/work-bench/page?link=${encodeURIComponent(goHref_ + '?goback=true')}`)
+  }
+
+  // 监听路由事件
+  function backRouteParams () {
+    const { searchGroup, subtype } = useRoute().query
+    if(searchGroup) {
+      // 进入页面,默认回显tab
+      const key = Number(searchGroup)
+      doChangeTab({ key})
+    } else if(subtype && subtype ==='采购意向' && inBIPropertyIframe) {
+      // 物业专版,采购意向回显
+      filterState.value.subtype = {采购意向: ['采购意向']}
+    }
+  }
+  /*******工作台跳转end***********/
+
+  /***页面缓存回显相关*******/
+  /**
+   * searchGroup筛选项缓存
+   */
+  function saveSearchGroupToLocal() {
+    const { key, _storage } = storageConfig.searchGroup
+    const params = {
+      searchGroup: activeTab.value
+    }
+    _storage.setItem(key, JSON.stringify(params))
+  }
+
+  /**
+   * 从缓存恢复searchGroup筛选项
+   */
+  function restoreSearchGroupFromLocal() {
+    const { key, _storage } = storageConfig.searchGroup
+    const params =_storage.getItem(key)
+    const jParams = JSON.parse(params)
+    if (jParams) {
+      doChangeTab({ key: jParams.searchGroup })
+      filterState.value.searchGroup = jParams.searchGroup
+    }
+  }
+
+  /**
+   * 从缓存中恢复搜索范围
+   */
+  function restoreSelectTypeFromLocal () {
+  }
+
+  /**
+   *  恢复和保存筛选条件到本地业务说明:
+   *  工作台内 1、所有用户都存 2、只有付费用户回显上次筛选
+   *  工作台外:所有用户不存不会回显
+   *
+   *  补充业务:
+   *  付费用户回显筛选条件逻辑:
+   *  本地缓存有,则恢复本地缓存
+   *  本地缓存无,则恢复保存的筛选条件第一条
+   *  本地缓存无,已存筛选条件无,不处理回显
+   *
+   *  搜索范围缓存单独处理
+   *  工作台内外:本地有缓存有则回显,无则默认选中标题
+   */
+
+  /**
+   * 保存筛选条件到本地
+   * @param replace
+   */
+  function saveFilterToLocal(replace = {}) {
+    const { key, _storage } = storageConfig.filter
+    const originParams = packageFilter()
+    let params = originParams
+    if (replace && Object.keys(replace).length) {
+      params = Object.assign(params, replace)
+    }
+    _storage.setItem(key, JSON.stringify(params))
+  }
+
+  /**
+   * 从本地缓存恢复筛选条件
+   *
+   */
+  function restoreFilterFromLocal () {
+    const { key, _storage } = storageConfig.filter
+    const params =_storage.getItem(key)
+    if (params) {
+      const expandFilter = {}
+      if(urlSelectType) {
+        expandFilter.selectType = urlSelectType.split(',')
+      }
+      if(urlKeywords) {
+        expandFilter.input = urlKeywords
+      }
+      // 身份切换时候,本地缓存的上次筛选条件有融创等动态拓展筛选条件,但是切换到的身份没有时候,需要移除这些动态筛选条件
+      const jParams = JSON.parse(params)
+      restoreFilter(jParams, 'localBack', expandFilter)
+    }
+  }
+
+  // 恢复筛选条件
+  // 恢复优先级、外边跳转进工作台的最高、其次是本地缓存、最后是已存筛选条件第一条
+  function getLastFilter () {
+    if(isLogin.value) {
+      const { key, _storage } = storageConfig.filter
+      const params =_storage.getItem(key)
+      const jParams = params ? JSON.parse(params) : null
+      // 工作台内
+      if(isInApp.value) {
+        // 免费用户恢复tab,搜索范围默认标题和正文
+        if(isFree.value) {
+          restoreSearchGroupFromLocal()
+          let defaultSelectType = ['title', 'content']
+          // 如果路由传参带入搜索范围参数
+          if(urlSelectType) {
+            const arr = urlSelectType.split(',')
+            defaultSelectType = arr
+          }
+          filterState.value.selectType = defaultSelectType
+          if(!goback) {
+            // 非工作台外跳转进入则默认搜索
+            firstSearch()
+          }
+        }
+
+        // vip 或者 桌面跳转回显筛选条件
+        if(goback || (isVip.value && jParams)) {
+          restoreFilterFromLocal()
+        }
+
+        // 获取已存筛选条件的列表
+        getFilterHistoryList(null,function (arr) {
+          if(isVip.value && !jParams) {
+            // 付费用户,优先恢复本地缓存,本地无缓存,则恢复已存筛选第一条数据
+            if(Array.isArray(arr) && arr.length > 0) {
+              const _expand = getExpandSearchParams()
+              const expandFilter = {
+                _expand: _expand,
+              }
+              if(urlSelectType) {
+                expandFilter.selectType = urlSelectType.split(',')
+              }
+              if(urlKeywords) {
+                expandFilter.input = urlKeywords
+              }
+              restoreFilter(arr[0], 'saveBack', expandFilter)
+            } else {
+              firstSearch()
+            }
+          }
+        })
+      } else {
+        filterState.value.selectType = ['title', 'content']
+        firstSearch()
+      }
+    } else {
+      filterState.value.selectType = ['title', 'content']
+      firstSearch()
+    }
+  }
+  // 获取本地缓存的动态插入的筛选条件
+  function getExpandSearchParams () {
+    // 动态获取存储的筛选条件
+    const expandSearchParams = localStorage.getItem('search_bidding_expandSearchParams')
+    let _expand = {}
+    if(expandSearchParams) {
+      _expand = JSON.parse(expandSearchParams)
+    }
+    return  _expand
+  }
+
+  function firstSearch () {
+    return doQuery({}, 'firstSearch')
+  }
+  /**
+   * 检查是否需要切换模糊搜索、是否展示提示
+   * 1. 精准搜索无数据 (自动切换模糊搜索)
+   * 2. 精准搜索有数据,< 50,提示手动切换搜索模式
+   */
+
+   const toggleSearchBlurData = reactive( {
+    show: false,
+    count: ''
+  })
+  function checkToggleSearchMode(params) {
+    const searchMode = params.searchMode
+    const pageNum = params.pageNum
+    const count = params.count || 0
+    const blurCount = params.blurCount || 0
+
+    if (pageNum === 1) {
+      // 重置变量
+      toggleSearchBlurData.show = false
+      toggleSearchBlurData.count = ''
+
+      if (searchMode === 0) {
+        const canShowToggleBlurModeTip = count >= 1 && count < 50 && blurCount > count
+
+        if (canShowToggleBlurModeTip) {
+          toggleSearchBlurData.show = true
+          toggleSearchBlurData.count = blurCount
+        }
+      }
+    }
+  }
+
+  // 切换到模糊搜索
+  function doToggleSearchBlurMode() {
+    inputKeywordsState.value.searchMode = '1'
+    setTimeout(function() {
+      scrollToTop(572)
+    }, 50)
+    doQuery()
+  }
+  // 滚动到某个位置
+  function scrollToTop(scrollTop) {
+    document.scrollingElement.scrollTop = scrollTop || 0
+  }
+  // 切换模糊搜索
+  const toggleBlurModeTip = computed( () => {
+    const isBlurMode = Number(inputKeywordsState.value.searchMode) === 1
+    let canShow = isBlurMode ? false : toggleSearchBlurData.show
+    if(listState.loading) {
+      canShow = false
+    }
+    const result = {
+      show: canShow,
+      count: toggleSearchBlurData.count
+    }
+    return result
+  })
+  /**********切换模糊搜索end*********/
+
+  // 页面初始化
+  function initPage () {
+    const { searchGroup } = useRoute().query
+    // 路由所带参数回显
+    backRouteParams()
+    // 恢复以前选过的tab
+    if(!searchGroup) {
+      restoreListTabActive()
+    }
+  }
+  initPage()
+
+  // // 恢复上次筛选条件
+  onMounted(() => {
+    if(!inBIPropertyIframe) {
+      // getLastFilter()
+      firstSearch()
+    } else {
+      firstSearch()
+    }
+    //  P271潜客圈进--客户引流请求
+    if(inputKeywordsState.value.input) {
+      getCustomReportData({ keywords: inputKeywordsState.value.input})
+    }
+  })
+
+
+  return {
+    isLogin,
+    isInApp,
+    isInWeb,
+    inResourceBIIframe,
+    inBIPropertyIframe,
+    inBITopNet,
+    inInjectBI,
+    isInBI,
+    isFree,
+    isVip,
+    isBidField,
+    goLogin,
+    guideGoWorkSpace,
+    cooperateCode,
+    list,
+    tableList,
+    searchModelOptions,
+    searchListProps,
+    SearchTabsModel,
+    inputKeywordsState,
+    filterState,
+    updateFilterBase,
+    listState,
+    interceptKeywords,
+    activeItemStyleType,
+    disposeFilterSchema,
+    doQuery,
+    doListHeaderAction,
+    doChangeAllSelect,
+    doChangeSelect,
+    doChangePageNum,
+    doChangePageSize,
+    onChangeTab,
+    activeTab,
+    vipDialogConf,
+    closeVipDialog,
+    onSaveFilter, // 以下存筛选条件相关
+    disposeFilterActionModel,
+    historyFilterDialogVisible,
+    onSelectedFilter,
+    onResetFilter,
+    toggleFilter,
+    showFilter,
+    fixedTop,
+    tableFixedTop,
+    toDetail,
+    tagToDetail,
+    onClickDataExport, // 以下为导出相关
+    setExport,
+    exportDialogChange,
+    showDataExportDialog,
+    onClickSingleCollect, // 收藏
+    usePowerRef, // 以下分发相关
+    doSubmitDistribute,
+    onJoinBid,//以下 参标
+    BidrenewalDialogRef,
+    showPropertyDialog,// 以下收录
+    propertyIframeSrc,
+    onSingleEmploy,
+    onAddInfoOfBI,
+    onSingleAddInfo,
+    goWorkSpace,
+    timeSelectorText,
+    collectElementRef,
+    onFreeTaste,
+    getLastFilter,
+    toggleBlurModeTip,
+    doToggleSearchBlurMode,
+    recommendCardCircleModel,
+    getWhiteList,
+    storageConfig,
+    clearHistoryQuery,
+    searchHistoryList,
+    showCurrentFilterText
+  }
+}

+ 9 - 0
apps/bigmember_pc/src/views/search/sun/model/index.js

@@ -0,0 +1,9 @@
+import useModel from './base'
+
+let SearchBidModel = {}
+function useSearchBidModel() {
+  SearchBidModel = useModel()
+  return SearchBidModel
+}
+
+export { useSearchBidModel, SearchBidModel }

+ 99 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/before-search.js

@@ -0,0 +1,99 @@
+// 获取是否允许空搜索白名单
+import { ref } from 'vue'
+import { trim } from 'lodash'
+// 获取反爬虫白名单,用于搜索前处理
+import { getInAntiSpiderWhiteList } from '@/api/modules/'
+import { showToast } from '@/components/toast'
+import $bus from '@/utils/bus'
+
+
+export function beforeSearchModel (paramsObj = { }) {
+
+  const { inputKeywordsState, urlKeywords } = paramsObj
+  // 是否是白名单用户
+  let onTheWhiteList = ref(false)
+  // 关键词搜索的校验规则
+  const commonSearchWordsRegExp = ref([])
+
+  async function getWhiteList () {
+    const keywords = urlKeywords || inputKeywordsState.value.input
+
+    const { error_code: code, data } = await getInAntiSpiderWhiteList()
+    if(code === 0 && data) {
+      onTheWhiteList.value = data.onTheWhitelist
+
+      if (Array.isArray(data.filterReg)) {
+        const newExp = []
+        data.filterReg.forEach(function(item) {
+          newExp.push(new RegExp(item))
+        })
+        commonSearchWordsRegExp.value = newExp
+      }
+      if (keywords && !onTheWhiteList.value) {
+        const timer = setTimeout(function() {
+          clearTimeout(timer)
+          const hasCommonWords = checkAndClearTextIncludesCommonWords(keywords)
+          if (hasCommonWords) {
+            showToast('请输入项目名称等关键词')
+          }
+        })
+      }
+    }
+  }
+
+  // 判断文字中是否有通用词(返回true表示text是通用词)
+  // (关键词不为空时,判断是否包含通用词)
+  function checkTextIncludesCommonWords (text) {
+    if (!text) {
+      return false
+    }
+    const passArr = []
+    text = trim(text)
+    if (Array.isArray(commonSearchWordsRegExp.value)) {
+      commonSearchWordsRegExp.value.forEach((reg) => {
+        const newText = text.replace(reg, '')
+        passArr.push(!!newText)
+      })
+    }
+
+    const hasNoPass = passArr.indexOf(false) !== -1
+    return hasNoPass
+  }
+   // text: 去空格后的主关键词
+  function checkAndClearTextIncludesCommonWords (text) {
+    let mainKeysHasCommonWords = false
+    let additionalWordsHasCommonWords = false
+    // 检查主关键词
+    if (checkTextIncludesCommonWords(text)) {
+      mainKeysHasCommonWords = true
+      $bus.$emit('bidding:updateInputKeywords', { input: ''})
+    }
+    // 检查附加词
+    const additionalWords = inputKeywordsState.value.additionalWords
+    const replacedAdditionalWords = []
+    if (Array.isArray(additionalWords)) {
+      for(let i = 0; i < additionalWords.length; i++) {
+        const item = additionalWords[i]
+        if (checkTextIncludesCommonWords(item)) {
+          // ...
+          additionalWordsHasCommonWords = true
+        } else {
+          replacedAdditionalWords.push(item)
+        }
+      }
+      if (additionalWordsHasCommonWords) {
+        $bus.$emit('bidding:updateInputKeywords', { additionalWords: replacedAdditionalWords})
+      }
+    }
+    return mainKeysHasCommonWords || additionalWordsHasCommonWords
+  }
+
+  return {
+    getWhiteList,
+    onTheWhiteList,
+    checkAndClearTextIncludesCommonWords
+  }
+
+}
+
+

+ 57 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/data-add-actions.js

@@ -0,0 +1,57 @@
+import { ref } from 'vue'
+import { ajaxGetInfoIds, ajaxSetInfoId } from '@/api/modules/bi'
+import $bus from '@/utils/bus'
+import { difference } from 'lodash'
+import { showToast } from '@/components/toast'
+
+export function dataAddActionsModel () {
+  let hadAddList = []
+
+  // 获取已添加的信息id
+  async function getBidAddInfos () {
+    const { error_code: code, data } = await ajaxGetInfoIds()
+    if(code === 0) {
+      const ids = data || []
+      $bus.$emit('bidding:updateDataAddStatus', { type: 'add', ids })
+      hadAddList = hadAddList.concat(ids)
+    }
+  }
+  async function doAddInfoOfBI(objParams) {
+    const { item, ids } = objParams
+    // 已添加过不处理
+    if(item?.isAdd){
+      return
+    }
+    let infoIds = []
+    if(item) {
+      infoIds = [item.id]
+    }
+    if(ids) {
+      if (ids.length > 0) {
+        infoIds = difference(ids, hadAddList )
+      } else {
+        return showToast('尚未选择数据,请选择')
+      }
+
+    }
+    if(!infoIds.length) {
+      return showToast('所选数据均已添加过,请重新选择')
+    }
+    const params = {
+      info_id: infoIds.join(','),
+      source: 2
+    }
+    const { data, error_code: code } = await ajaxSetInfoId(params)
+    if (code === 0 && data?.status === 1) {
+      showToast('添加成功')
+      getBidAddInfos()
+    } else {
+      showToast('添加操作失败')
+    }
+  }
+
+  return {
+    getBidAddInfos,
+    doAddInfoOfBI
+  }
+}

+ 414 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/data-collect-actions.js

@@ -0,0 +1,414 @@
+import { computed, onMounted, ref } from 'vue'
+import { useStore } from '@/store'
+import { getEventTarget } from '@/utils/jq-help'
+import $bus from '@/utils/bus'
+import {
+  bidCollAction,
+  getBidCollTagList,
+  saveBidCollAddTag,
+  createBidTag,
+  checkBidsIsColl,
+} from '@/api/modules/'
+
+// 是否登录
+const isLogin = computed(() => {
+  return useStore().getters['user/loginFlag']
+})
+
+// 是否是免费用户
+const isFree = computed(() => {
+  return useStore().getters['user/isFree']
+})
+
+export function dataCollectActionModel (gParams) {
+  const { that } = gParams
+
+  onMounted(() => {
+    initCollectEvent()
+  })
+
+  function onCollect (config, type) {
+    // 未登录跳转登录
+    if (!isLogin.value) {
+      $bus.$emit('bidding:goLogin')
+      return
+    }
+
+    const { total, isSelectSomeCheckbox, event, item, selectIds } = config
+
+    // 列表无数据
+    if (!total) return
+
+    let binfo = []
+    if(type === 'batch') {
+      // 无选择标讯
+      if (!isSelectSomeCheckbox) {
+        return that.$toast('尚未选择标讯,请选择')
+      }
+      binfo = selectIds.map(id => {
+        return {
+          bid: id
+        }
+      })
+    } else {
+      binfo = [{
+        bid: item.id
+      }]
+    }
+    // 单条取消收藏
+    if(item?.collection) {
+      const reParams = {
+        baction: 'R',
+        binfo,
+        type,
+        event
+      }
+      ajaxForCollectChange(reParams, function (res) {
+        if (res.data) {
+          // 更新列表当前收藏状态
+          const ids = [item.id]
+          $bus.$emit('bidding:updateListCollectStatus', { type: 'R', ids })
+
+          that.$toast('已取消收藏', 800)
+        } else {
+          that.$toast(res.error_msg, 1000)
+        }
+      })
+    } else {
+      // 将本次收藏的标讯id缓存起来 用于绑定标签时使用
+      sessionStorage.setItem('$save-tags-binfo', JSON.stringify(binfo))
+      const reParams = {
+        baction: 'C',
+        binfo,
+        type,
+        event
+      }
+      if (isFree.value) {
+        // 打开留资弹窗
+        const collectElementRef = that.$refs.collectElementRef
+        collectElementRef?.isNeedSubmit('article_collection', () => {
+          ajaxForCollectChange(reParams)
+        })
+      } else {
+        ajaxForCollectChange(reParams)
+      }
+    }
+  }
+
+  async function ajaxForCollectChange(reParams, callback) {
+    const $ = that.$querySelector.bind(that)
+    const { baction, binfo, type, event } = reParams
+    const { top, left } = calcCardTopLeft(event, type === 'batch')
+    /*
+      收藏或取消收藏ajax
+      params: {
+        baction: String, //用户行为:R:移除收藏;C:收藏(默认)非必填
+        binfo: Array, // 招标信息数组 必填
+        bid: String, // 招标信息加密后id 必填
+      }
+      参数示例 (baction=R binfo数组只需要bid即可)onSizeChange
+    */
+    const params = {
+      baction: baction,
+      binfo: binfo
+    }
+    const res = await bidCollAction(params)
+    if(callback) {
+      callback(res)
+      return
+    }
+    const {error_code: code, data, error_msg } = res
+    if(code === 0 && data) {
+      that.$toast('收藏成功', 1500)
+      $('.tags-box')
+        .show(function () {
+          window.pushListActiveTags = []
+          $('.tag-labels').empty()
+          $('.clear-input').val('')
+          $('.tags-list').find('.tags-item').removeClass('tags-active')
+          $('.tag-placeholder').show()
+        })
+        .css({
+          top: top,
+          right: 'unset',
+          left: left
+        })
+      window.getUserTags()
+      // 更新列表当前收藏状态
+      const ids = binfo.map(temp => {
+        return temp.bid
+      })
+      $bus.$emit('bidding:updateListCollectStatus', { type: 'C', ids })
+    } else {
+      if (error_msg.indexOf('付费') > -1) {
+        that.$toast('您的标讯收藏上限为5000条,请联系客服人员。', 1500)
+      }
+    }
+  }
+
+
+  // 处理收藏弹出的标签框
+  function initCollectEvent () {
+    const $ = that.$querySelector.bind(that)
+
+    function toastFn(text, duration) {
+      that.$toast(text, duration)
+    }
+
+    // 自定义标签
+    // 标签输入框事件
+    $('.tags-box').click(function (e) {
+      e.stopPropagation()
+    })
+    $('.tag-input').click(function (e) {
+      e.stopPropagation()
+      $(this).children('.tag-placeholder').hide()
+      $(this).children('input').focus()
+    })
+    // 标签输入框回车事件
+    $('.tag-input .clear-input').keydown(function (event) {
+      event.stopPropagation()
+      if (event.keyCode == 13) {
+        if (!$('.tags-box').is(':hidden')) {
+          $('.tags-inputs .add-tag-button').trigger('click')
+        }
+      }
+    })
+    // 标签输入框失去焦点事件
+    $('.tag-input .clear-input').blur(function () {
+      if ($('.tag-labels').children().length == 0 && $(this).val() == '') {
+        $('.tag-placeholder').show()
+      }
+    })
+    // 添加标签按钮事件
+    $('.tags-inputs .add-tag-button').on('click', function () {
+      var input = $('.tag-input .clear-input')
+      if (input.val().length >= 2 && input.val().length < 11) {
+        // ajax提交自定义标签
+        addTagsAjax(input.val())
+      }
+    })
+    // 点击确定按钮,绑定标签
+    $('.tags-footer .button-confirm').on('click', function () {
+      if (!$('.tags-box').is(':hidden')) {
+        var lids = ''
+        var lname = ''
+        $('.tags-item.tags-active').each(function () {
+          if ($(this).attr('data-id')) {
+            if (lids != '') {
+              lids += ','
+            }
+            if (lname != '') {
+              lname += ','
+            }
+            lids += $(this).attr('data-id')
+            lname += $(this).text()
+          }
+        })
+        var params = {
+          lids: lids,
+          laction: 'S',
+          binfo: JSON.parse(sessionStorage.getItem('$save-tags-binfo'))
+        }
+
+        // 执行保存绑定标签操作
+        if (params.lids !== '') {
+          saveChooseTags(params, function () {
+            $('.tags-footer .button-cancel').trigger('click')
+          })
+        }
+      }
+    })
+
+    $('.tags-footer .button-cancel').on('click', function () {
+      $('.tags-box').hide(function () {
+        // 标签弹框消失时 清除上次选择的标签分类
+        pushListActiveTags = []
+        $('.tag-labels').empty()
+        $('.clear-input').val('')
+        $('.tags-list').find('.tags-item').removeClass('tags-active')
+        $('.tag-placeholder').show()
+      })
+    })
+
+    window.pushListActiveTags = [] // 选中的自定义标签 作为全局变量使用
+    // 解绑自定义标签
+    function deleteInputTag(item) {
+      var index = $(item).parent().attr('data-index')
+      var id = $(item).parent().attr('data-id')
+      pushListActiveTags.splice(index, 1)
+      inputTagList()
+      $('.tags-item[data-id="' + id + '"]').removeClass('tags-active')
+    }
+
+    function inputTagList() {
+      var ht = ''
+      $('.tag-labels').html(ht)
+      pushListActiveTags.forEach(function (v, i) {
+        ht +=
+          '<span class="tag-label" data-index=' +
+          i +
+          ' data-id="' +
+          v.lid +
+          '">'
+        ht += '<em>' + v.lname + '</em>'
+        ht += '<i class="tag-close"></i>'
+        ht += '</span>'
+      })
+      $('.tag-labels')
+        .html(ht)
+        .off('click')
+        .on('click', '.tag-close', function (e) {
+          const target = getEventTarget(e)
+          deleteInputTag(target)
+        })
+      if ($('.tag-labels').children('.tag-label').length > 0) {
+        $('.tag-placeholder').hide()
+      }
+      checkTagDisabled()
+    }
+
+    // 渲染标签列表数据
+    function renderTagsList(data) {
+      if (data && data.length > 0) {
+        var ht = ''
+        data.forEach(function (v, i) {
+          ht +=
+            '<span class="tags-item" data-count=' +
+            v.count +
+            ' data-id=' +
+            v.lid +
+            '>' +
+            v.lanme +
+            '</span>'
+        })
+        $('.tags-list').html(ht)
+        pushListActiveTags.forEach(function (s, j) {
+          $('.tags-list .tags-item[data-id="' + s.lid + '"]').addClass(
+            'tags-active'
+          )
+        })
+        $('.tags-item').click(function (e) {
+          e.stopPropagation()
+          if ($(this).hasClass('disabled')) return
+          var id = $(this).attr('data-id')
+          var name = $(this).text()
+          $(this).toggleClass('tags-active')
+          if ($(this).hasClass('tags-active')) {
+            pushListActiveTags.push({
+              lid: id,
+              lname: name
+            })
+            inputTagList()
+          } else {
+            var newArr = pushListActiveTags.filter(function (item) {
+              return item.lid != id
+            })
+            pushListActiveTags = newArr
+            inputTagList()
+          }
+        })
+      }
+      inputTagList()
+    }
+
+    // 获取用户自定义标签
+    function getUserTags() {
+      getBidCollTagList().then((r) => {
+        if (r.error_code == 0 && Array.isArray(r.data)) {
+          renderTagsList(r.data.reverse())
+        }
+      })
+    }
+
+    window.getUserTags = getUserTags
+
+    /*
+      保存或清除标签 ajax
+      params: {
+        lids: String 标签id(加密后),  非必传
+        lname: String 标签名称,  非必传
+        laction: String  用户行为:S添加或绑定标签;D删除标签  非必传
+        binfo: Array 招标信息数组(已收藏的招标信息) 非必传
+        bid: String 招标信息加密后id  必传
+      }
+      1:lids为空;lname不为空;laction=”S”;binfo数组不为空->新增标签并且绑定收藏信息
+      2:lids不为空;laction=”S”;binfo数组不为空->收藏信息绑定标签
+      3:lids不为空;laction=”D”;->删除标签 并解绑收藏的信息
+    */
+    function saveChooseTags(params, callback) {
+      saveBidCollAddTag(params).then((r) => {
+        if (r.data) {
+          toastFn('标签绑定成功', 1000)
+          callback && callback()
+        }
+      })
+    }
+
+    // 新增标签
+    function addTagsAjax(name) {
+      createBidTag({ name }).then((r) => {
+        if (r.data) {
+          $('.tag-input .clear-input').val('')
+          // 添加标签成功后 绑定标签
+          if (pushListActiveTags.length < 3) {
+            pushListActiveTags.push({
+              lid: r.data,
+              lname: name
+            })
+          }
+          getUserTags()
+        } else {
+          // toastFn(r.error_msg, 1000)
+          toastFn('标签已经存在,无需添加', 1000)
+        }
+      })
+    }
+
+    function checkTagDisabled() {
+      if (pushListActiveTags.length >= 3) {
+        // 禁用标签
+        $('.tags-list')
+          .find('.tags-item:not(.tags-active)')
+          .addClass('disabled')
+      } else {
+        // 解除禁用
+        $('.tags-list').find('.disabled').removeClass('disabled')
+      }
+    }
+
+    getUserTags()
+  }
+
+  //计算收藏标签框的位置数据
+  function calcCardTopLeft (e, batch) {
+    const $ = that.$querySelector.bind(that)
+    const containerWidth = that.$el.clientWidth
+    const containerHeight = that.$el.clientHeight
+    const cardWidth = 332
+    const cardHeight = 362
+    var top = parseInt($(getEventTarget(e)).position().top) + 40
+    if (!batch) {
+      top -= 10
+    }
+    var left = parseInt($(getEventTarget(e)).offset().left) - 300
+
+    if (top >= containerHeight - cardHeight) {
+      top = containerHeight - cardHeight
+    }
+    if (left >= containerWidth - cardWidth) {
+      left = containerWidth - cardWidth
+    }
+
+    left += 'px'
+    top += 'px'
+    return {
+      top,
+      left
+    }
+  }
+
+  return {
+    onCollect
+  }
+}

+ 46 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/data-distribute-actions.js

@@ -0,0 +1,46 @@
+// 分发业务
+import { ref } from 'vue'
+import { ajaxSetDidDistributor } from '@/api/modules'
+import { Message } from 'element-ui'
+import { showToast } from '@/components/toast'
+
+export function dataDistributeActionsModel() {
+
+  const usePowerRef = ref(null)
+  let sIds = []
+  function openDistribute(selectIds) {
+    if(!selectIds.length) {
+     return showToast('尚未选择公告,请选择')
+    }
+    sIds = selectIds
+    usePowerRef.value.titleMsg = '选择接收人员'
+    usePowerRef.value.searchVal = ''
+    usePowerRef.value.centerDialogVisible = true
+    usePowerRef.value.selectDataIds = selectIds
+    usePowerRef.value.getData('yes')
+  }
+
+  // 提交分发
+  function doSubmitDistribute(data) {
+    ajaxSetDidDistributor({
+      infoids: sIds,
+      staffs: data
+    }).then((res) => {
+      if (res.error_code === 0) {
+        if (res.data === 1) {
+          Message({ message: '分发成功', type: 'success' })
+        } else {
+          Message({ message: res.error_msg, type: 'warning' })
+        }
+      } else {
+        Message({ message: res.error_msg, type: 'warning' })
+      }
+    })
+  }
+
+  return {
+    usePowerRef,
+    openDistribute,
+    doSubmitDistribute
+  }
+}

+ 155 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/data-employ-actions.js

@@ -0,0 +1,155 @@
+// 收录业务
+import { computed, ref } from 'vue'
+import { useStore } from '@/store'
+import $bus from '@/utils/bus'
+import { ajaxEmployInfo, ajaxEmployOperate, ajaxOptEmployOperate, ajaxOptOperateExist } from '@/api/modules/'
+import { showToast } from '@/components/toast'
+
+// 是否登录
+const isLogin = computed(() => {
+  return useStore().getters['user/loginFlag']
+})
+
+
+export function dataEmployActionsModel (gParams) {
+  const { inBIPropertyIframe } = gParams
+  // 是否可以批量收录
+  const canBatchEmploy = ref(false)
+
+  // 物业专版收录-创建情报iframe弹窗是否展示
+  const showPropertyDialog = ref(false)
+  // 创建情报iframe地址
+  const propertyIframeSrc = ref('')
+
+  // 查询收录情况
+  async function getEmployData (listIds) {
+    const params = {
+      employType: 1,
+      idArr: listIds.join()
+    }
+    const { data, batchEmploy, error_code: code } = await ajaxEmployInfo(params)
+    if(code === 0) {
+      // 批量收录操作权限
+      if(batchEmploy) {
+        canBatchEmploy.value = !!batchEmploy
+      }
+      if (Array.from(data) && data.length > 0) {
+        const employIds = []
+        data.forEach(item => {
+          if(item.isEmploy) {
+            employIds.push(item.id)
+          }
+        })
+        // 添加收录
+        $bus.$emit('bidding:updateDateEmployStatus', { type:'add', employIds: employIds })
+      }
+    }
+  }
+  // 批量收录
+  function onBatchEmploy (selectIds) {
+    // 未登录跳转登录
+    if (!isLogin.value) {
+      location.href = '/notin/page?close_goBack=1'
+      return
+    }
+    if(!selectIds.length) {
+     return showToast('尚未选择标讯,请选择')
+    }
+
+    // 物业专版收录
+    if(inBIPropertyIframe) {
+      const params = {
+        ids: selectIds.join(',')
+      }
+      ajaxOptEmployOperate(params).then((res) => {
+        const { code, data, msg } = res
+        // 物业专版
+        if(inBIPropertyIframe) {
+          if (code === 1) {
+            // 全部收录成功
+            $bus.$emit('bidding:updateDateEmployStatus',  { type:'add', employIds: selectIds })
+          } else if(code === 2) {
+            const ids = data?.split(',')
+            // code 2 部分收录成功
+            $bus.$emit('bidding:updateDateEmployStatus', { type:'add', employIds: ids })
+          }
+          showToast(msg)
+        }
+      })
+    } else {
+      // 营销收录
+      let params = {
+        idArr: selectIds.join(','),
+        isEmploy: true,
+        employType: 1
+      }
+      ajaxEmployOperate(params).then((res) => {
+        const { error_code: code, data, msg } = res
+        if (code === 0 && data.status) {
+          $bus.$emit('bidding:updateDateEmployStatus', { type:'add', employIds: selectIds })
+        }
+      })
+    }
+
+  }
+
+  // 收录操作
+  function onSingleEmploy(item) {
+    let params = {}
+    params = {
+      idArr: item.id,
+      isEmploy: !item.isEmploy,
+      employType: 1,
+    }
+    // 取消收录添加参数
+    if(!item.isEmploy) {
+      params.from = 'jhfp'
+    }
+    // 物业专版,添加收录
+    if(inBIPropertyIframe && !item.isEmploy) {
+      params = {
+        id: item.id
+      }
+      ajaxOptOperateExist(params).then(res => {
+        const { code, msg } = res
+        if (code === 2) {
+          // 已经创建情报信息,直接收录
+          $bus.$emit('bidding:updateDateEmployStatus', { type:'add', employIds: [item.id] })
+        } else if (code === 1) {
+          // 未创建情报信息,需要手动创建
+          propertyIframeSrc.value = `${location.origin}/succbi/crm_system/app/crm.app/%E9%80%9A%E7%94%A8%E5%88%9B%E5%BB%BA/create_intelligence.spg?t=${new Date().getTime()}`
+          showPropertyDialog.value = true
+        } else {
+          showToast(msg)
+        }
+      })
+    } else {
+      // 物业专版添加收录、营销添加或者取消收录
+      ajaxEmployOperate(params).then(res => {
+        const {error_code: code, data } = res
+        if(code === 0) {
+          if(data.status) {
+            // 取消收录操作
+            if(item.isEmploy) {
+              $bus.$emit('bidding:updateDateEmployStatus', { type:'cancel', employIds: [item.id] })
+            } else {
+              $bus.$emit('bidding:updateDateEmployStatus', { type:'add', employIds: [item.id] })
+            }
+          } else {
+            showToast(data.msg)
+          }
+        }
+      })
+    }
+  }
+
+  return {
+    showPropertyDialog,
+    propertyIframeSrc,
+    canBatchEmploy,
+    getEmployData,
+    onSingleEmploy,
+    onBatchEmploy
+  }
+
+}

+ 121 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/data-export-actions.js

@@ -0,0 +1,121 @@
+import { computed, ref } from 'vue'
+import $bus from '@/utils/bus'
+import {
+  ajaxGetDontPromptAgain,
+  ajaxSetDontPromptAgain,
+  getPushListExport,
+  defaultSelectEnt,
+  searchIndexDataExport
+} from '@/api/modules/'
+
+import { useStore } from '@/store'
+import  { FilterHistoryViewModel2AjaxModel, openOuterLink } from '@/utils'
+
+// 是否登录
+const isLogin = computed(() => {
+  return useStore().getters['user/loginFlag']
+})
+
+export function dataExportActionsModel () {
+  const exportLoading = ref(false)
+  const showDataExportDialog = ref(false)
+
+  // 数据导出获取的筛选条件
+  const filterFormatParams = ref({})
+
+  async function dataExport (config) {
+    const { listState, selectCheckboxCount, selectIds, filter } = config
+    // 未登录跳转登录
+    if (!isLogin.value) {
+      $bus.$emit('bidding:goLogin')
+      return
+    }
+    if (!listState.total) return
+
+    filterFormatParams.value = FilterHistoryViewModel2AjaxModel.formatAll(filter)
+    const _expand = filter._expand || {}
+    Object.assign(filterFormatParams.value, _expand)
+    exportLoading.value = true
+    // 查询是否需要弹窗
+    const { error_code: code, isPrompt } = await ajaxGetDontPromptAgain()
+    if(code === 0) {
+      exportLoading.value = false
+
+      let countBool = false
+      // 限制2000条提示弹窗
+      if(selectCheckboxCount > 0) {
+        countBool = selectCheckboxCount >= 20000
+      } else {
+        countBool = listState.total >= 20000
+      }
+      if (isPrompt && countBool) {
+        showDataExportDialog.value = isPrompt
+      } else {
+        toDataExportEvent(selectIds)
+      }
+    } else {
+      toDataExportEvent(selectIds)
+    }
+  }
+  // 根据条件获取到导出id, 并跳转到导出付费页
+  async function toDataExportEvent (selectIds) {
+    try {
+      // 判断是否选择过企业。未选择过调用 selectEnt 选择一个默认的企业
+      await defaultSelectEnt()
+      const { area, city, district } = FilterHistoryViewModel2AjaxModel.formatAreaCity(filterFormatParams.value.regionMap)
+      let searchGroup = filterFormatParams.value.searchGroup === 3 ? 1 : filterFormatParams.value.searchGroup
+      const dParams = {
+        scope: area, // 地区省份(数据导出接口用到)
+        area, // 地区省份(保存筛选接口用到)
+        city,
+        district,
+        ...filterFormatParams.value,
+        searchGroup
+      }
+      const params = Object.assign(dParams, {
+        selectIds: selectIds ? selectIds.join(',') : null
+      })
+      const { _id } = await searchIndexDataExport(params)
+      if (!_id) return
+      const link = `/front/dataExport/toCreateOrderPage/${_id}`
+      openOuterLink(link, true)
+    } catch (error) {
+    } finally {
+      exportLoading.value = false
+    }
+  }
+  // 超过2000条确认导出
+  function setExport () {
+    toDataExportEvent()
+  }
+  // 超过2000条是否在提示
+  async function  exportDialogChange(data) {
+    const params = {
+      status: data ? 1 : 0
+    }
+    const { error_code: code } = await ajaxSetDontPromptAgain(params)
+  }
+
+  //携带当前检索跳转数据导出支付页面
+  function toPayDataExport (config) {
+   const { listState, selectCheckboxCount, selectIds, filter } = config
+   filterFormatParams.value = FilterHistoryViewModel2AjaxModel.formatAll(filter)
+    const _expand = filter._expand || {}
+    filterFormatParams.value = Object.assign(filterFormatParams.value, _expand)
+    // 未登录跳转登录
+    if (!isLogin.value) {
+      $bus.$emit('bidding:goLogin')
+      return
+    } else {
+      toDataExportEvent()
+    }
+  }
+
+  return {
+    dataExport,
+    setExport,
+    exportDialogChange,
+    showDataExportDialog,
+    toPayDataExport
+  }
+}

+ 52 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/filter-keywords.js

@@ -0,0 +1,52 @@
+import { ref } from 'vue'
+
+const searchModelOptions = [
+  {
+    label: '精准搜索',
+    value: '0'
+  },
+  {
+    label: '模糊搜索',
+    value: '1'
+  }
+]
+export function useSearchInputKeywordsModel() {
+  const inputKeywordsState = ref({
+    input: '',
+    // 关键词筛选模式
+    searchMode: '0',
+    matchKeys: [],
+    // 附加关键词筛选模式
+    wordsMode: 0,
+    // 附件关键词组
+    additionalWords: [],
+    selectMoreKey: false
+  })
+
+  function getFormatAPIParams() {
+    const params = {
+      keyWords: inputKeywordsState.value.input,
+      searchMode: Number(inputKeywordsState.value.searchMode),
+    }
+
+    if (inputKeywordsState.value.selectMoreKey) {
+      params.additionalWords =
+        inputKeywordsState.value.additionalWords.join(',')
+      params.wordsMode = inputKeywordsState.value.wordsMode
+    }
+
+    return params
+  }
+
+  // 动态更新筛选条件
+  function updateInputKeywordsState (keyObj = {}) {
+    inputKeywordsState.value = Object.assign(inputKeywordsState.value, keyObj)
+  }
+
+  return {
+    inputKeywordsState,
+    searchModelOptions,
+    getFormatAPIParams,
+    updateInputKeywordsState
+  }
+}

+ 244 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/filter.js

@@ -0,0 +1,244 @@
+import { ref, readonly } from 'vue'
+import  {
+  FilterHistoryViewModel2AjaxModel,
+  areaObjTwoToSingle,
+  infoTypeMapFormat
+} from '@/utils'
+
+export function useSearchFilterModel(conf) {
+  const { inBIPropertyIframe, isFree,isInApp, isBidField } = conf
+  const initBase = {
+    // 发布时间
+    publishTime: 'thisyear',
+    // 搜索范围
+    selectType: ['title', 'content'],
+    // 报名截止状态
+    baomingjiezhizhuangtai: '0',
+    // 报名截止时间
+    baomingjiezhiriqi: 'fiveyear',
+    // 交付地点
+    jiaofudidian: {},
+    // 项目地区
+    xiangmudiqu: {},
+    // 领域
+    lingyu: {},
+    // 行业
+    industry: {},
+    // 附件
+    fileExists: '',
+    // 发布方
+    publisher: '',
+  }
+
+  // 筛选组件状态
+  const filterBase = ref({
+    // 发布时间
+    publishTime: 'thisyear',
+    // 搜索范围
+    selectType: ['title', 'content'],
+    // 报名截止状态
+    baomingjiezhizhuangtai: '0',
+    // 报名截止时间
+    baomingjiezhiriqi: '',
+    // 交付地点
+    jiaofudidian: {},
+    // 项目地区
+    xiangmudiqu: {},
+    // 领域
+    lingyu: {},
+    // 行业
+    industry: {},
+    // 附件
+    fileExists: '',
+    // 发布方
+    publisher: '',
+  })
+  const filterState = ref({})
+  filterState.value = filterBase.value
+ // 搜索接口需要的格式化后的数据
+ function getFormatAPIParams (){
+    return getFormatApiBaseParams()
+ }
+  // 格式化招标采购基础筛选条件
+  function getFormatApiBaseParams () {
+    const { publishTime, baomingjiezhizhuangtai, baomingjiezhiriqi, buyerclass, fileExists, publisher } = filterState.value
+    // const { area, city, district } = FilterHistoryViewModel2AjaxModel.formatAreaCity(regionMap)
+    // const rPublishTime = publishTime?.indexOf('_') > -1 ? FilterHistoryViewModel2AjaxModel.formatExactTime(publishTime, '-') :  FilterHistoryViewModel2AjaxModel.formatTime(publishTime, true, '-')
+    // const rIndustry = FilterHistoryViewModel2AjaxModel.formatIndustry(industry)
+    // const rBuyerClass = FilterHistoryViewModel2AjaxModel.formatBuyerClass(buyerclass)
+    // const rSubtype = FilterHistoryViewModel2AjaxModel.formatInfoType(subtype)
+    // const expandTag = {}
+    // if(typeof (_expand) === 'object' && Object.keys(_expand).length > 0) {
+    //   for(let key in _expand) {
+    //     expandTag[key] = filterState.value[key]
+    //     _expand[key] = filterState.value[key]
+    //   }
+    // }
+
+
+    // // 搜索范围
+    // selectType: ['title', 'content'],
+    // // 报名截止状态
+    // baomingjiezhizhuangtai: '0',
+    // // 报名截止时间
+    // baomingjiezhiriqi: 'fiveyear',
+    // // 交付地点
+    // jiaofudidian: {},
+    // // 项目地区
+    // xiangmudiqu: {},
+    // // 领域
+    // lingyu: {},
+    // // 行业
+    // industry: {},
+    // // 附件
+    // fileExists: '',
+    // // 发布方
+    // publisher: '',
+
+    // const params = {
+    //   bidField: filterState.value.bidField,
+    //   publishTime: rPublishTime,
+    //   selectType: filterState.value.selectType.join(','),
+    //   subtype: rSubtype,
+    //   exclusionWords: notkey.join(','), // 排除词
+    //   buyer: filterState.value.buyer.join(','),
+    //   winner: filterState.value.winner.join(','),
+    //   agency: filterState.value.agency.join(','),
+    //   industry: !isBidField ? rIndustry : '',
+    //   province: area,
+    //   city,
+    //   district,
+    //   buyerClass: !isBidField ? rBuyerClass : '',
+    //   fileExists: filterState.value.fileExists,
+    //   price: filterState.value.price,
+    //   buyerTel: filterState.value.buyertel,
+    //   winnerTel: filterState.value.winnertel,
+    //   ...expandTag
+    // }
+    const params = {}
+    return params
+  }
+  // 格式化物业专版的筛选条件
+  function getFormatAPIPropertyParams() {
+    const { publishTime, regionMap, notKey, subtype, expireTime } = filterState.value
+    const { area, city } = areaObjTwoToSingle(regionMap)
+    const rSubtype = infoTypeMapFormat(subtype)
+    let rPublishTime = ''
+    if(publishTime && publishTime.indexOf('-') > -1) {
+      const arr =  publishTime.split('-')
+      if(isNaN(arr[0])) {
+        rPublishTime = FilterHistoryViewModel2AjaxModel.formatTime(publishTime, true, '-')
+      } else {
+        rPublishTime = arr[0]/ 1000  + '-' +  arr[1] / 1000
+      }
+    } else {
+      const pTime  = publishTime || 'fiveyear'
+      rPublishTime = FilterHistoryViewModel2AjaxModel.formatTime(pTime, true, '-')
+    }
+    let rExpireTime = ''
+    if(expireTime && expireTime.indexOf('_') > -1) {
+      const arr =  expireTime.split('_')
+      if(arr[0] !== '0') {
+        rExpireTime = arr[0]
+      }
+      if(arr[1] !== '0') {
+        rExpireTime += '_' + arr[1]
+      } else {
+        rExpireTime +=  '_'
+      }
+    } else {
+      rExpireTime = (expireTime === 'all' || !expireTime) ? '' : expireTime
+    }
+    const params = {
+      bidField: 'BIProperty',
+      province: area,
+      city,
+      expireTime: rExpireTime,
+      subInformation: filterState.value.subInformation?.join(),
+      scale: filterState.value.scale?.join(),
+      period:  filterState.value.period?.join(),
+      propertyForm: filterState.value.propertyForm?.join(),
+      selectType: filterState.value.selectType?.join(','),
+      subtype: rSubtype,
+      publishTime: rPublishTime ,
+      changeHand: filterState.value.changeHand ?  Number(filterState.value.changeHand) : 0,
+      fileExists: filterState.value.fileExists,
+      buyerTel: filterState.value.buyerTel,
+      winnerTel: filterState.value.winnerTel,
+      exclusionWords: notKey?.join(','), // 排除词
+    }
+    return params
+  }
+  // 动态更新筛选条件
+  function updateFilterBase (keyObj = {}, type) {
+    const {key, value } = keyObj
+    const initParams = JSON.parse(JSON.stringify(initBase))
+    if(keyObj && Object.keys(keyObj).length > 0) {
+      // 重置单个筛选条件
+      if(type === 'resetItem') {
+        // 动态组件重置
+        if(filterState.value._expand[key]) {
+          const expandSearchParams = localStorage.getItem('search_bidding_expandSearchParams')
+          let _expand = {}
+          if(expandSearchParams) {
+            _expand = JSON.parse(expandSearchParams)
+          }
+          filterBase.value[key] = _expand[key]
+          filterBase.value._expand[key] = _expand[key]
+          filterState.value[key] = _expand[key]
+          filterState.value._expand[key] = _expand[key]
+        } else {
+          // 普通组件重置
+          filterBase.value[key] = initParams[key]
+          filterState.value[key] = initParams[key]
+        }
+
+      } else if(type === 'expand') {
+        // 动态拓展筛选条件
+        const _expand = filterState.value._expand || {}
+        let _expandResult = {
+          _expand: {}
+        }
+        if(Object.keys(_expand).length  > 0 && filterState.value[key]) {
+          _expandResult._expand =  Object.assign(_expand, { [key]: value })
+        } else {
+          _expandResult._expand =  {
+            [key]: value
+          }
+        }
+        if(!inBIPropertyIframe) {
+          filterState.value = Object.assign(filterBase.value, filterState.value, _expandResult)
+        }
+      } else {
+        filterBase.value[key] = value
+        filterState.value[key] = value
+      }
+    } else {
+      // 动态获取存储的筛选条件
+      const expandSearchParams = localStorage.getItem('search_bidding_expandSearchParams')
+      let _expand = {}
+      if(expandSearchParams) {
+        _expand = JSON.parse(expandSearchParams)
+      }
+      // 重置所有筛选项
+      filterBase.value = {
+        ...initParams,
+        ..._expand,
+        expand: _expand
+      }
+      //
+      if(!inBIPropertyIframe) {
+        filterState.value =  filterBase.value
+      }
+    }
+
+  }
+
+  return {
+    filterState,
+    filterBase,
+    getFormatAPIParams,
+    getFormatAPIPropertyParams,
+    updateFilterBase
+  }
+}

+ 56 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/join-bid-actions.js

@@ -0,0 +1,56 @@
+import { ref } from 'vue'
+import { getBidIsJoin, joinBidAction } from '@/api/modules/'
+import { showToast } from '@/components/toast'
+import $bus from '@/utils/bus'
+
+export function joinBidActionsModel () {
+  const  BidrenewalDialogRef = ref(null)
+
+  async function getJoinBidInfo(listIds) {
+      const ids = listIds.join()
+      const { error_code: code, data } = await getBidIsJoin({ ids })
+      if (code === 0 && data) {
+        $bus.$emit('bidding:updateListJoinStatus', {joinData: data})
+      }
+  }
+  // 参标
+  async function onJoinBid(item) {
+    // 终止参标
+    if (item.joinBid) {
+      showToast('如需终止参标,需要在详情页进行操作。')
+      return
+    }
+    // 参标
+    const params = {
+      bidIds: item.id
+    }
+    try {
+      const {
+        error_code: code,
+        error_msg: msg,
+        data
+      } = await joinBidAction('in', params)
+      if (code === 0 && data) {
+        // showToast.$toast('已参标,请前往我的参标项目列表查看。')
+        // 拉起参标更新弹窗
+        if(BidrenewalDialogRef.value) {
+          BidrenewalDialogRef.value.passVisible = true
+          BidrenewalDialogRef.value.setid(item.id)
+          BidrenewalDialogRef.value.refreshData()
+        }
+        $bus.$emit('bidding:updateListJoinStatus', { item })
+      } else if (code === -1) {
+        showToast.$toast(msg || '操作错误,请稍后重试')
+      }
+    } catch (e) {
+      console.warn(e)
+      showToast.$toast('操作错误,请稍后重试')
+    }
+  }
+
+  return {
+    BidrenewalDialogRef,
+    getJoinBidInfo,
+    onJoinBid
+  }
+}

+ 101 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/list-header-actions.js

@@ -0,0 +1,101 @@
+import { computed, ref } from 'vue'
+import { ListHeaderActions } from '../../composables/list-header-actions'
+
+// 列表顶部操作按钮
+const actions = [
+  [
+    'refined-list',
+    'detailed-list',
+    // 'table'
+  ],
+  // ['data-export'],
+  // ['collect-bid'],
+  // ['distribute-bid'],
+  // ['employ-bid'],
+  // ['edit-tags'],
+  // ['cancel-collect']
+]
+
+// 列表 Item 样式风格切换,用于兼容旧参数
+const ItemStyleTypes = {
+  'refined-list': 'S',
+  'detailed-list': 'D',
+  table: 'T'
+}
+
+// 权限列表,过滤操作按钮权限
+const limitActions = ref({
+  'refined-list': true,
+  'detailed-list': true,
+  'table': false,
+  'data-export': false,
+  'collect-bid': false,
+  'distribute-bid': false,
+  'employ-bid': false,
+  'edit-tags': false,
+  'cancel-collect': false
+})
+
+// 默认的风格样式
+const defaultItemStyleType = 'refined-list'
+
+export function useSearchListHeaderActionsModel() {
+  // 默认的风格样式
+  const listItemStyleType = ref(defaultItemStyleType)
+
+  // 当前激活的风格样式
+  const activeItemStyleType = computed(() => {
+    return ItemStyleTypes[listItemStyleType.value]
+  })
+
+  // 需要高亮 active 的顶部操作按钮
+  const activeHeaderActions = ref([defaultItemStyleType])
+
+  // 需要禁用的 disable 的顶部操作按钮
+  const disabledHeaderActions = ref([])
+
+  // 顶部操作按钮图标等配置填充
+  const headerActions = computed(() => {
+    if (actions.length > 0) {
+      // 过滤权限
+      const authActions = actions.map(acItem => {
+        if (Array.isArray(acItem)) {
+          const arr = acItem.filter(flagKey => {
+            return flagKey && limitActions.value[flagKey]
+          })
+          return  arr
+        }
+        return []
+      }).filter(item => {
+        return item.length > 0
+      })
+
+      // 变更actions状态字段
+      const updateAction =  authActions.map((actions) => {
+        if (Array.isArray(actions)) {
+          return actions.map((actionKey) => {
+            const result = ListHeaderActions[actionKey]
+            // 对应高亮上方 activeHeaderActions 中的按钮
+            result.isActive = activeHeaderActions.value.includes(actionKey)
+            // 禁用某指定操作
+            result.disabled = disabledHeaderActions.value.includes(actionKey)
+            return result
+          })
+        }
+        return []
+      })
+      return updateAction
+    } else {
+      return []
+    }
+  })
+
+  return {
+    headerActions,
+    listItemStyleType,
+    activeItemStyleType,
+    activeHeaderActions,
+    disabledHeaderActions,
+    limitActions
+  }
+}

+ 306 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/recommend-card.js

@@ -0,0 +1,306 @@
+import { computed, reactive, ref } from 'vue'
+import { tryCallHooks } from '@jianyu/easy-inject-qiankun'
+import { difference } from 'lodash'
+import { ajaxSetLeadGetDateRecord, leadGetDate } from '@/api/modules/'
+import {
+  formatPrice,
+  replaceKeyword
+} from '@/utils/'
+
+export function recommendCardModel(modelConfig) {
+  const { that } = modelConfig
+
+  const advancedInfo = reactive({
+    show: false,
+    showContent: false,
+    showDialog: false,
+    dialogContent: '体验超前项目推荐服务!',
+    moduleInfo: {
+      超前项目推荐: {
+        title: '超前项目推荐',
+        desc: '提前推送超前项目,优先对接项目负责人'
+      },
+      市场分析报告: {
+        title: '市场分析报告',
+        desc: '量身定制个性化报告,分析市场竞争格局,为企业找准市场机会!'
+      }
+    },
+    briefList: [],
+    projectList: []
+  })
+
+  const chartCustomData = ref({})
+
+  const chart = reactive({
+    treeMapKey: 1,
+    treeMapData: [],
+    buyLineKey: 1,
+    buyLineData: {},
+    winnerLineKey: 1,
+    winnerLineData: {}
+  })
+
+  const nowModuleName = computed(() => {
+    if (advancedInfo.briefList.length || advancedInfo.projectList.length) {
+      return '超前项目推荐'
+    }
+    else {
+      return '市场分析报告'
+    }
+  })
+  // 获取当前模块展示文本信息
+  const getNowInfo = computed(() => {
+    return advancedInfo.moduleInfo[nowModuleName.value]
+  })
+
+  // 获取当前模块缩小后是否有数据展示
+  const getNotModuleDataStatus = computed(() => {
+    if (nowModuleName.value === '超前项目推荐') {
+      return !advancedInfo.briefList.length
+    }
+    else {
+      return true
+    }
+  })
+
+  // 是否展示市场分析报告模块
+  const getShowChart = computed(() => {
+    return Object.keys(chartCustomData.value).length > 0
+  })
+  // 当前第一位展示内容标识
+  const showModuleChart = computed(() => {
+    const chartKeyArr = Object.keys(chartCustomData.value)
+    const fieldText = getRandomArrayElements(chartKeyArr, 2)
+    const noShowFiled = difference(chartKeyArr, fieldText)
+    return noShowFiled[0]
+  })
+  // 市场分析报告&&超前项目推荐数据请求
+  function getCustomReportData(conf) {
+    const { keywords } = conf
+    const params = {
+      dType: 0,
+      keyWords: keywords
+    }
+    leadGetDate(params).then((res) => {
+      const { error_code: code = 0, data } = res
+      if (code === 0 && data && Object.keys(data).length > 0) {
+        advancedInfo.briefList = (data?.ahead?.subTypeCount || []).map((v) => {
+          v.value = v.doc_count
+          return v
+        })
+
+        advancedInfo.projectList = data?.ahead?.projectTop2 || []
+
+        chartCustomData.value = data.custom || {}
+
+        if (
+          advancedInfo.briefList.length
+          || advancedInfo.projectList.length
+          || Object.keys(chartCustomData.value).length
+        ) {
+          advancedInfo.show = true
+          advancedInfo.showContent = false
+
+          setTimeout(() => {
+            advancedInfo.showContent = true
+            setChartData(data.custom)
+          }, 1000)
+        }
+      }
+    })
+  }
+  // 市场分析报告图表
+  function setChartData(newval = {}) {
+    const chartKeyArr = Object.keys(newval)
+    chartKeyArr.forEach((item) => {
+      if (item === 'customer_scale') {
+        const data = newval.customer_scale.map((item) => {
+          return {
+            ...item,
+            name: item.buyclass,
+            value: item.total,
+            amount: formatPrice(item.amount / 10000)
+          }
+        })
+        chart.treeMapData = data
+        chart.treeMapKey = new Date().getTime()
+      }
+      else if (item === 'winner_time_distribution') {
+        const data = {
+          columns: ['采购规模分布', '采购总金额占比', '采购单位总数占比'],
+          rows: []
+        }
+        let total = 0
+        const chartLIst = newval.winner_time_distribution
+        if (Array.isArray(chartLIst)) {
+          const field = {
+            [data.columns[0]]: 'key',
+            [data.columns[1]]: 'total_amount',
+            [data.columns[2]]: 'total_number'
+          }
+          chartLIst.forEach((item) => {
+            const row = {}
+            data.columns.forEach((column) => {
+              if (
+                field[column] === 'total_amount'
+                || field[column] === 'total_number'
+              ) {
+                row[column] = (item[field[column]] * 100).toFixed(2)
+                total += item[field[column]] - 0
+              }
+              else {
+                row[column] = item[field[column]]
+              }
+            })
+            data.rows.push(row)
+          })
+        }
+        if (total) {
+          data.rows.reverse()
+          chart.buyLineData = data
+          chart.buyLineKey = new Date().getTime()
+        }
+      }
+      else if (item === 'buyer_time_distribution') {
+        const data = {
+          columns: ['中标规模分布', '中标总金额占比', '中标单位总数占比'],
+          rows: []
+        }
+        let total = 0
+        const buyerclassList = newval.buyer_time_distribution
+        if (Array.isArray(buyerclassList)) {
+          const field = {
+            [data.columns[0]]: 'key',
+            [data.columns[1]]: 'total_amount',
+            [data.columns[2]]: 'total_number'
+          }
+          buyerclassList.forEach((item) => {
+            const row = {}
+            data.columns.forEach((column) => {
+              if (
+                field[column] === 'total_amount'
+                || field[column] === 'total_number'
+              ) {
+                row[column] = (item[field[column]] * 100).toFixed(2)
+                total += item[field[column]] - 0
+              }
+              else {
+                row[column] = item[field[column]]
+              }
+            })
+            data.rows.push(row)
+          })
+        }
+        if (total) {
+          data.rows.reverse()
+          chart.winnerLineData = data
+          chart.winnerLineKey = new Date().getTime()
+        }
+      }
+    })
+  }
+
+  // 收起或打开 展示超前项目、模块
+  function toggleAdvancedContent(type) {
+    if (typeof type !== 'boolean') {
+      type = !advancedInfo.showContent
+    }
+    advancedInfo.showContent = type
+  }
+  // 展示弹窗
+  function showAdvancedDialog(title = '体验超前项目推荐服务!') {
+    advancedInfo.dialogContent = title
+    advancedInfo.showDialog = true
+  }
+
+  // 点击感兴趣
+  function onClickInterested(type) {
+    const title
+      = type === 'A' ? '体验超前项目推荐服务!' : '为您量身定制个性化报告!'
+    showAdvancedDialog(title)
+    try {
+      ajaxSetLeadGetDateRecord({ type })
+    }
+    catch (e) {
+      console.warn(e)
+    }
+  }
+  // 查看完整报告
+  async function goToReport() {
+    try {
+      await ajaxSetLeadGetDateRecord({ type: 'B' })
+    }
+    catch (e) {
+      console.warn(e)
+    }
+    tryCallHooks({
+      fn: () => {
+        that.$BRACE.methods.open({
+          route: {
+            link: '/desktop/report_analysis',
+            appName: 'big',
+            appType: 'qiankun'
+          }
+        })
+      },
+      spareFn: () => {
+        window.open(
+          '/swordfish/page_big_pc/desktop/report_analysis?tab=analysis'
+        )
+      }
+    })
+  }
+  // 查看项目详情
+  function goToContent(item) {
+    try {
+      ajaxSetLeadGetDateRecord({ type: 'A' })
+    }
+    catch (e) {
+      console.warn(e)
+    }
+    let goURL = `/article/content/${item._id}.html`
+    if (Array.isArray(item.keyWord)) {
+      goURL += `?kds=${encodeURIComponent(item.keyWord.join('+'))}`
+    }
+    window.open(goURL)
+  }
+  // 匹配高亮文本信息
+  function getProjectTitle({ title, keyWord }) {
+    return replaceKeyword(title, keyWord, [
+      '<span class="highlight-text-orange-bd">',
+      '</span>'
+    ])
+  }
+  // /* 随机获取数组中的数据*/
+  function getRandomArrayElements(arr, count) {
+    const shuffled = arr.slice(0)
+    let i = arr.length
+    const min = i - count
+    let temp
+    let index
+    while (i-- > min) {
+      index = Math.floor((i + 1) * Math.random())
+      temp = shuffled[index]
+      shuffled[index] = shuffled[i]
+      shuffled[i] = temp
+    }
+    return shuffled.slice(min)
+  }
+
+  return {
+    getCustomReportData,
+    toggleAdvancedContent,
+    advancedInfo,
+    onClickInterested,
+    goToContent,
+    getProjectTitle,
+    goToReport,
+    getShowChart,
+    getNotModuleDataStatus,
+    getNowInfo,
+    nowModuleName,
+    chartCustomData,
+    showModuleChart,
+    chart
+  }
+}

+ 178 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/save-filter-actions.js

@@ -0,0 +1,178 @@
+import { computed, ref } from 'vue'
+import { checkBiddingFilterPass, addBiddingFilter, getBiddingFilterList, deleteBiddingFilter } from '@/api/modules'
+import  { FilterHistoryViewModel2AjaxModel, FilterHistoryAjaxModel2ViewModel} from '@/utils'
+import { showToast } from '@/components/toast'
+import { MessageBox } from 'element-ui'
+
+
+const saveConfig = {
+  savedFilterListMaxCount: 10 //筛选条件保存条数最大值
+}
+
+// P611医械通:要处理参数 bidField: isBidField ? 'medical' : '',
+
+export function saveFilterActionsModel (conf) {
+   const { savedFilterListMaxCount: maxCount } = saveConfig
+  // 是否是医械通
+  const { isBidField }  = conf
+   // 已存筛选列表
+   let filterHistoryList = ref([])
+   // 保存筛选条件框展示与否
+   const saveFilterDialogVisible = ref(false)
+  // 查看已保存的历史筛选条件
+   const historyFilterDialogVisible = ref(false)
+   // 当前保存的筛选条件多返回的ID
+   const currentFilterParamsKey = ref('')
+  // 当前保存的筛选条件
+   let filterSaveParams = ref({})
+  // 视图可视筛选条件
+   let viewFilterParams = ref({})
+  // 已存搜索条件个数
+   const historyFilterCount = computed(() => {
+     return filterHistoryList.value.length
+   })
+
+  // 检测筛选条件是否可保存
+   async function checkFilterPass (config) {
+     const  { filter } = config
+     filterSaveParams.value = FilterHistoryViewModel2AjaxModel.formatAll(filter)
+
+     const { buyer, winner, agency, searchvalue, additionalWords, industry } = filterSaveParams.value
+     const hasOneKey = buyer || winner || agency
+     if (!searchvalue && !additionalWords && !industry && !hasOneKey) {
+       return showToast('请先输入关键词')
+     }
+     if(historyFilterCount.value > maxCount) {
+       return showToast(`对不起,最多可保存${maxCount}个筛选条件。`)
+     }
+     const { data: id, error_code: code = 0, error_msg: msg } = await checkBiddingFilterPass(filterSaveParams.value)
+     if(code === 0) {
+       currentFilterParamsKey.value = id
+       viewFilterParams.value = FilterHistoryAjaxModel2ViewModel.formatAll(filterSaveParams.value)
+       saveFilterDialogVisible.value = true
+     } else {
+      if (msg) {
+        if (msg.includes('无用户身份')) {
+          showToast('请登录')
+        } else {
+          showToast(msg)
+        }
+      }
+    }
+  }
+
+  // 取消保存筛选条件操作
+  function saveFilterCancel () {
+    saveFilterDialogVisible.value = false
+  }
+
+  // 确定保存筛选条件操作
+  async function saveFilterConfirm () {
+    try {
+      const params = {
+        ...filterSaveParams.value,
+        inkey: currentFilterParamsKey.value
+      }
+      const { error_code: code = 0, error_msg: msg } = await addBiddingFilter(params)
+      if (code === 0) {
+        saveFilterDialogVisible.value = false
+        showToast('保存成功')
+        getFilterHistoryList()
+      } else {
+        if (msg) {
+          showToast(msg)
+        }
+      }
+    } catch (error) {
+    }
+  }
+  // 获取已存筛选条件
+  async function  getFilterHistoryList(type, callback) {
+    try {
+        const par = {
+          bidField: isBidField ? 'medical' : ''
+        }
+        const { data, error_code: code, error_msg: msg } = await getBiddingFilterList(par)
+      if (code === 0) {
+        const resData = data || []
+        const arr = resData.map((item, index) => {
+          return {
+            ...FilterHistoryAjaxModel2ViewModel.formatAll(item),
+            id: item.id,
+            inKey: item.inKey,
+            isPay: item.isPay,
+            open: index === 0
+          }
+        })
+        filterHistoryList.value = arr
+        if(type === 'delete' && arr.length === 0) {
+          historyFilterDialogVisible.value = false
+        }
+        callback && callback(arr)
+      } else {
+        if (msg) {
+          showToast(msg)
+        }
+      }
+    } catch (error) {
+    }
+  }
+
+  /**
+   * 获取已保存的筛选条件
+   * @returns {Promise<void>}
+   */
+  async function onHasFilter () {
+    if(!historyFilterCount.value) {
+      showToast('请先保存筛选条件')
+      return
+    }
+    historyFilterDialogVisible.value = true
+  }
+  // 已存筛选条件弹窗,单条展开收起
+  function onHasToggle (item) {
+    filterHistoryList.value.forEach(function(v) {
+      v.open = false
+    })
+    item.open = !item.open
+  }
+
+  function onDeleteFilter (item) {
+    MessageBox.confirm('确定删除该筛选条件吗?', '删除', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      center: true,
+      showClose: false,
+      customClass: 'filter-delete-messagebox',
+      confirmButtonClass: 'btn-group confirm-btn',
+      cancelButtonClass: 'btn-group cancel-btn'
+    }).then(function() {
+      const params = {
+        id: item.id
+      }
+      deleteBiddingFilter(params).then(res => {
+        const { error_code: code} = res
+        if(code === 0) {
+          showToast('删除成功')
+          getFilterHistoryList('delete')
+        }
+      })
+    }).catch(function() {})
+  }
+
+  return {
+    saveFilterDialogVisible,
+    historyFilterDialogVisible,
+    filterSaveParams,
+    viewFilterParams,
+    historyFilterCount,
+    checkFilterPass,
+    saveFilterCancel,
+    saveFilterConfirm,
+    filterHistoryList,
+    getFilterHistoryList,
+    onHasFilter,
+    onHasToggle,
+    onDeleteFilter
+  }
+}

+ 80 - 0
apps/bigmember_pc/src/views/search/sun/model/modules/tabs.js

@@ -0,0 +1,80 @@
+import { computed, ref } from 'vue'
+
+export function useSearchTabsModel(conf) {
+  const { showTabs2, inInjectBI } = conf
+  const defaultTab = showTabs2 ? 7 : 0
+  const activeTab = ref(defaultTab)
+  const Tabs = [
+    {
+      label: '采购信息',
+      key: 0
+    }
+  ]
+  const Tabs2 = [
+    {
+      label: '招标采购公告',
+      key: 1,
+      link: '/jylab/supsearch/index.html?searchGroup=1'
+    },
+    {
+      label: '企业搜索',
+      key: 3,
+      link: '/jylab/entSearch/index.html'
+    },
+    {
+      label: '采购单位搜索',
+      key: 4,
+      link: '/jylab/purSearch/index.html'
+    },
+    {
+      label: '阳光直采',
+      key: 7,
+      link: '/swordfish/page_big_pc/search/sun'
+    },
+    {
+      label: '供应搜索',
+      key: 5,
+      link: '/swordfish/page_big_pc/search/supply'
+    },
+    {
+      label: '超前项目查询',
+      key: 2,
+      link: '/jylab/supsearch/index.html?searchGroup=2'
+    },
+    {
+      label: '拟在建项目查询',
+      key: 6,
+      link: '/swordfish/page_big_pc/search/nzj'
+    }
+  ]
+  let resTab = []
+  if (showTabs2) {
+    resTab = Tabs2
+  }
+  else {
+    // 个人分析报告不展示超前项目
+    if (inInjectBI) {
+      resTab = Tabs.filter((item) => {
+        return item.label !== '超前项目'
+      })
+    }
+    else {
+      resTab = Tabs
+    }
+  }
+  const searchTabs = computed(() => {
+    return resTab.map((v) => {
+      v.active = activeTab.value === v.key
+      return v
+    })
+  })
+
+  function doChangeTab(tab) {
+    activeTab.value = tab.key
+  }
+  return {
+    searchTabs,
+    activeTab,
+    doChangeTab
+  }
+}