Browse Source

Merge branch 'feature/v1.0.53' into dev/v1.0.53_tsz

tangshizhe 1 year ago
parent
commit
7d874992f4
60 changed files with 4396 additions and 91 deletions
  1. 1 0
      apps/bigmember_pc/.env.development
  2. 1 0
      apps/bigmember_pc/.env.production
  3. 7 4
      apps/bigmember_pc/package.json
  4. 52 0
      apps/bigmember_pc/scripts/updateGitInfo.js
  5. 180 0
      apps/bigmember_pc/src/assets/style/component/collect-tags-box.scss
  6. 8 0
      apps/bigmember_pc/src/assets/style/reset-ele.scss
  7. 3 3
      apps/bigmember_pc/src/components/article-item/ArticleItem.vue
  8. 2 1
      apps/bigmember_pc/src/components/drawer/Drawer.vue
  9. 80 0
      apps/bigmember_pc/src/components/filter-items/CollectSearchInput.vue
  10. 353 0
      apps/bigmember_pc/src/components/filter-items/CollectTagSelector.vue
  11. 10 0
      apps/bigmember_pc/src/components/selector/SearchTimeScopeSelector.vue
  12. 10 0
      apps/bigmember_pc/src/components/selector/TimeSelector.vue
  13. 69 2
      apps/bigmember_pc/src/components/selector/TimeSelectorContent.vue
  14. 2 2
      apps/bigmember_pc/src/components/subscribe-manager/KeyConfig.vue
  15. 4 0
      apps/bigmember_pc/src/main.js
  16. 9 0
      apps/bigmember_pc/src/router/modules/bidding.js
  17. 6 1
      apps/bigmember_pc/src/router/modules/search.js
  18. 2 1
      apps/bigmember_pc/src/router/router-interceptors.js
  19. 2 0
      apps/bigmember_pc/src/router/router.js
  20. 20 0
      apps/bigmember_pc/src/sentry.js
  21. 189 0
      apps/bigmember_pc/src/views/collection/components/search-filter.vue
  22. 261 0
      apps/bigmember_pc/src/views/collection/components/search-list-table.vue
  23. 129 0
      apps/bigmember_pc/src/views/collection/composables/constant/list-header-actions.js
  24. 8 0
      apps/bigmember_pc/src/views/collection/constant/index.js
  25. 257 0
      apps/bigmember_pc/src/views/collection/constant/search-filters.js
  26. 291 0
      apps/bigmember_pc/src/views/collection/index.vue
  27. 814 0
      apps/bigmember_pc/src/views/collection/model/base.js
  28. 9 0
      apps/bigmember_pc/src/views/collection/model/index.js
  29. 57 0
      apps/bigmember_pc/src/views/collection/model/modules/data-add-actions.js
  30. 94 0
      apps/bigmember_pc/src/views/collection/model/modules/data-collect-actions.js
  31. 370 0
      apps/bigmember_pc/src/views/collection/model/modules/data-collect-tag.js
  32. 117 0
      apps/bigmember_pc/src/views/collection/model/modules/data-export-actions.js
  33. 125 0
      apps/bigmember_pc/src/views/collection/model/modules/filter.js
  34. 56 0
      apps/bigmember_pc/src/views/collection/model/modules/join-bid-actions.js
  35. 96 0
      apps/bigmember_pc/src/views/collection/model/modules/list-header-actions.js
  36. 22 3
      apps/bigmember_pc/src/views/portrayal/UnitPortrayal.vue
  37. 1 3
      apps/bigmember_pc/src/views/portrayal/components/UnitChart.vue
  38. 4 1
      apps/bigmember_pc/src/views/search/bidding/components/search-bid-filter.vue
  39. 39 15
      apps/bigmember_pc/src/views/search/bidding/model/base.js
  40. 4 0
      apps/bigmember_pc/src/views/search/bidding/model/modules/data-export-actions.js
  41. 24 9
      apps/bigmember_pc/src/views/search/bidding/model/modules/filter.js
  42. 2 2
      apps/bigmember_pc/src/views/subscribe/components/key/KeyConfig.vue
  43. 1 2
      apps/bigmember_pc/vite.config.js
  44. 8 0
      apps/mobile/src/router/modules/search.js
  45. 5 1
      apps/mobile/src/store/modules/search.js
  46. 3 0
      apps/mobile/src/views/search/layout.vue
  47. 81 0
      apps/mobile/src/views/search/middle/docs/index.vue
  48. 11 2
      apps/mobile/src/views/search/middle/layout.vue
  49. 46 0
      data/data-models/modules/quick-collect-bid/README.md
  50. 45 0
      data/data-models/modules/quick-collect-bid/api/collect-api.js
  51. 1 0
      data/data-models/modules/quick-collect-bid/index.js
  52. 96 0
      data/data-models/modules/quick-collect-bid/model/index.js
  53. 46 0
      data/data-models/modules/quick-collect-bid/plugins/base.js
  54. 23 0
      data/data-models/modules/quick-collect-bid/plugins/collect-list.js
  55. 82 0
      data/data-models/modules/quick-collect-bid/plugins/collect-tags.js
  56. 31 0
      data/data-models/modules/quick-search-history/api/search-history.js
  57. 18 0
      data/data-models/modules/quick-search-history/model/index.js
  58. 82 0
      data/data-models/modules/quick-search-history/plugins/base.js
  59. 2 2
      packages/pc-ui/src/packages/keywords/index.vue
  60. 25 37
      pnpm-lock.yaml

+ 1 - 0
apps/bigmember_pc/.env.development

@@ -3,3 +3,4 @@ VITE_APP_BASE_API='/dev-api'
 VITE_APP_BASE_URL='/swordfish/page_big_pc'
 VITE_APP_BASE_PUBLIC='/'
 VITE_APP_WORK_DESKTOP_URL='/page_workDesktop/work-bench/app'
+VITE_APP_GIT_BRANCH='v0.0.1'

+ 1 - 0
apps/bigmember_pc/.env.production

@@ -3,3 +3,4 @@ VITE_APP_BASE_API=''
 VITE_APP_BASE_URL='/page_big_pc'
 VITE_APP_BASE_PUBLIC='/page_big_pc/'
 VITE_APP_WORK_DESKTOP_URL='/page_workDesktop/work-bench/app/big'
+VITE_APP_GIT_BRANCH='v1.0.54.1'

+ 7 - 4
apps/bigmember_pc/package.json

@@ -4,19 +4,22 @@
   "private": true,
   "scripts": {
     "dev": "vite",
-    "build": "vite build",
+    "build": "pnpm run update && pnpm run build:vite",
+    "build:vite": "vite build",
+    "update": "node ./scripts/updateGitInfo.js",
     "preview": "vite preview --port 4173",
     "lint": "eslint . --ext .vue,.js,.jsx --fix",
     "format": "prettier --write \"./**/*.{,vue,ts,js,json,md}\""
   },
   "dependencies": {
-    "@jy/util": "workspace:^",
-    "@jy/data-models": "workspace:^",
-    "@jy/pc-ui": "workspace:^",
     "@jianyu/easy-fix-sub-app": "^0.0.2",
     "@jianyu/easy-inject-qiankun": "^0.1.11",
     "@jianyu/icon": "^0.1.7",
     "@jianyu/reset.css": "~0.1.1",
+    "@jy/data-models": "workspace:^",
+    "@jy/pc-ui": "workspace:^",
+    "@jy/util": "workspace:^",
+    "@sentry/vue": "^7.64.0",
     "dayjs": "^1.11.7",
     "echarts": "4.8.0",
     "element-ui": "^2.15.16-rc",

+ 52 - 0
apps/bigmember_pc/scripts/updateGitInfo.js

@@ -0,0 +1,52 @@
+const { exec } = require('child_process')
+const fs = require('fs')
+
+const getGitBranch = () => {
+  return new Promise((resolve, reject) => {
+    exec(
+      'git symbolic-ref --quiet --short HEAD || git describe --all --exact-match HEAD',
+      (error, stdout, stderr) => {
+        if (error) {
+          reject(error)
+        } else {
+          const branch = stdout.trim()
+          resolve(branch)
+        }
+      }
+    )
+  })
+}
+
+const extractVersion = (branch) => {
+  const versionRegex = /(v.*?)$/
+  const matches = branch.match(versionRegex)
+  return matches ? matches[0] : ''
+}
+
+const updateEnvFile = (branch) => {
+  try {
+    const envFilePath = '.env.production'
+    const existingContent = fs.readFileSync(envFilePath, 'utf8')
+    const updatedBranch = extractVersion(branch)
+    const updatedContent = existingContent.replace(
+      /(VITE_APP_GIT_BRANCH=).*/,
+      `$1'${updatedBranch}'`
+    )
+    fs.writeFileSync(envFilePath, updatedContent)
+    console.log(
+      'Git 分支信息已更新到 .env.production 文件',
+      branch,
+      updatedBranch
+    )
+  } catch (error) {
+    console.error('更新 .env.production 文件失败:', error)
+  }
+}
+
+getGitBranch()
+  .then((branch) => {
+    updateEnvFile(branch)
+  })
+  .catch((error) => {
+    console.error('获取 Git 分支失败:', error)
+  })

+ 180 - 0
apps/bigmember_pc/src/assets/style/component/collect-tags-box.scss

@@ -0,0 +1,180 @@
+.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-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;
+      }
+      &.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;
+  }
+}

+ 8 - 0
apps/bigmember_pc/src/assets/style/reset-ele.scss

@@ -175,6 +175,14 @@
   text-align: center;
   color: #1d1d1d;
 }
+.el-pagination{
+  .el-select.el-select--mini{
+    .el-input__suffix{
+      display: block;
+      top: -2px;
+    }
+  }
+}
 
 // 分页组件页码选择select下拉框样式
 .pagination-custom-select {

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

@@ -63,11 +63,11 @@
         >
         <span class="tag"
               v-if="article.area"
-              :class="{'tag-handle': tagClickList.includes('area')}"
+              :class="{'tag-handle': tagClickList.includes('area') && article.areaUrl}"
               @click.stop="tagClick('area')">
-          {{ article.area }}</span>
+          {{ article.region || article.area }}</span>
         <span class="tag orange"
-              :class="{'tag-handle': tagClickList.includes('subtype')}"
+              :class="{'tag-handle': tagClickList.includes('subtype') && article.subtypeUrl}"
               v-if="article.type || article.subtype"
               @click.stop="tagClick('subtype')">
           {{article.type || article.subtype }}

+ 2 - 1
apps/bigmember_pc/src/components/drawer/Drawer.vue

@@ -16,7 +16,7 @@
       <div class="el-container">
         <div class="el-header">
           <div slot="title">
-            <span>{{ title }}</span>
+            <span v-html="title"></span>
             <el-icon
               v-if="showClose && !withHeader"
               class="el-icon-close self-close"
@@ -166,6 +166,7 @@ export default {
       border-radius: 6px;
       border: 1px solid #2cb7ca;
       color: #2cb7ca;
+      font-size: 16px;
       &.el-cancel {
         margin-left: 40px;
         color: #686868;

+ 80 - 0
apps/bigmember_pc/src/components/filter-items/CollectSearchInput.vue

@@ -0,0 +1,80 @@
+<template>
+  <div class="collect-search-input">
+    <div class="search-input-container">
+      <el-input
+        clearable
+        v-model="value"
+        :placeholder="placeholder"
+        @input="onInput"
+      >
+      </el-input>
+      <el-button type="text" @click="onButtonClick">{{ buttonText }}</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Input, Button } from 'element-ui'
+export default {
+  name: 'CollectSearchInput',
+  components: {
+    [Input.name]: Input,
+    [Button.name]: Button
+  },
+  props: {
+    placeholder: {
+      type: String,
+      default: '请输入要搜索的关键词'
+    },
+    maxlength: {
+      type: Number,
+      default: 100
+    },
+    buttonText: {
+      type: String,
+      default: '搜索'
+    }
+  },
+  data() {
+    return {
+      value: ''
+    }
+  },
+  methods: {
+    onInput(val) {
+      this.value = val
+    },
+    onButtonClick() {
+      this.$emit('change', this.value)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.collect-search-input{
+  flex: 1;
+  .search-input-container{
+    display: flex;
+    align-items: center;
+    .el-input{
+      width: 280px;
+      height: 30px;
+    }
+    .el-button--text{
+      padding: 0;
+      margin-left: 8px;
+      font-size: 14px;
+    }
+  }
+  ::v-deep{
+    .el-input__inner{
+      height: 30px;
+      line-height: 30px;
+    }
+    .el-input__icon{
+      line-height: 30px;
+    }
+  }
+}
+</style>

+ 353 - 0
apps/bigmember_pc/src/components/filter-items/CollectTagSelector.vue

@@ -0,0 +1,353 @@
+<template>
+  <div class="collect-tag-selector">
+    <div class="select-group-container">
+      <div
+        v-for="(item, index) in tagsList"
+        :key="index"
+        class="j-button-item"
+        :class="{
+          active: item.selected,
+          all: item.name === '全部'
+        }"
+        @click="buttonClick(item)"
+      >
+        {{ item.name }}
+      </div>
+    </div>
+    <span class="manage-button" @click="onManageClick">标签管理</span>
+    <DrawerCard
+      customClass="drawer-class"
+      percent="600px"
+      :title="calcTitle"
+      :with-header="false"
+      confirmText="确定"
+      v-model="showDrawer"
+      @close="onCloseDrawer"
+      @saveData="onSaveDrawer"
+    >
+      <div class="tag-header flex">
+        <el-input
+          placeholder="输入标签"
+          v-model.trim="addTagInput"
+          maxlength="10"
+          clearable
+          @keyup.enter.native="addTagConfirmed">
+        </el-input>
+        <div class="add-tag-confirm-button flex no-select" @click="addTagConfirmed">确认添加</div>
+      </div>
+      <div class="tag-main">
+        <el-tag
+          :key="tag.value"
+          v-for="tag in drawerTags"
+          closable
+          type="info"
+          :disable-transitions="false"
+          @close="delThisTag(tag)">
+          {{ tag.name }} ( {{ tag.count }} )
+        </el-tag>
+      </div>
+    </DrawerCard>
+    <vDialog
+      customClass="custom-dialog"
+      width="380px"
+      top="30vh"
+      center
+      title="删除标签"
+      :show-close="false"
+      :visible.sync="dialog.delete"
+      @cancel="onDialogCancel"
+      @confirm="onDialogConfirm"
+    >
+      <div class="dialog-content">
+        <span v-if="currentItem.count === 0">确定删除{{currentItem.name ? ('“' + currentItem.name + '”') : '该'}}标签?</span>
+        <span v-else>警告:当前有{{currentItem.count}}条标讯使用了该标签,如果删除标签,那么对应的标讯也将删除该关联标签。</span>
+      </div>
+    </vDialog>
+  </div>
+</template>
+
+<script>
+import { Input, Tag, Dialog } from 'element-ui'
+import DrawerCard from '@/components/drawer/Drawer'
+import vDialog from '@/components/dialog/Dialog'
+export default {
+  name: 'CollectTags',
+  components: {
+    [Input.name]: Input,
+    [Tag.name]: Tag,
+    [Dialog.name]: Dialog,
+    DrawerCard,
+    vDialog
+  },
+  props: {
+    sourceList: {
+      type: Array,
+      default() {
+        return []
+      }
+    },
+    singleChoice: {
+      // 是是否单选
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      tagsList: [],
+      defaultItemExp: {
+        name: '全部',
+        value: '',
+        selected: true
+      },
+      showDrawer: false,
+      addTagInput: '',
+      dialog: {
+        delete: false,
+        already: false
+      },
+      currentItem: {
+        name: '',
+        value: '',
+        count: 0
+      }
+    }
+  },
+  model: {
+    prop: 'value',
+    event: 'change'
+  },
+  computed: {
+    calcTitle() {
+      return `标签管理 <em class="highlight-text">${this.tagsList.length}</em>`
+    },
+    drawerTags() {
+      return this.tagsList.filter(item => item.name !== '全部')
+    }
+  },
+  watch: {
+    value(val) {
+      this.setState(val)
+    },
+    sourceList() {
+      this.init()
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  methods: {
+    init() {
+      const list = [JSON.parse(JSON.stringify(this.defaultItemExp))]
+      this.sourceList.forEach((item) => {
+        list.push({
+          name: item.name,
+          value: item.value,
+          count: item.count,
+          selected: false
+        })
+      })
+      this.tagsList = list
+    },
+    buttonClick(item) {
+      if (item.name === this.defaultItemExp.name) {
+        this.setState()
+      } else {
+        if (this.singleChoice) {
+          this.setState()
+          this.tagsList[0].selected = false
+          item.selected = !item.selected
+        } else {
+          this.tagsList[0].selected = false
+          item.selected = !item.selected
+
+          // 子项全部选中则全部按钮选中,子项如果一个没有被选中,则全部按钮被选中
+          const { allNotSelected } = this.checkAllSelectedState()
+          if (allNotSelected) {
+            this.setState()
+          }
+        }
+      }
+      this.onChange()
+    },
+    // 除了全部按钮之外的按钮的状态
+    checkAllSelectedState() {
+      const stateArr = []
+
+      this.tagsList.forEach((item) => {
+        if (item.value !== this.defaultItemExp.value) {
+          stateArr.push(item.selected)
+        }
+      })
+      return {
+        // 找不到false,就说明全部被选中
+        allSelected: stateArr.indexOf(false) === -1,
+        // 找不到true,就说明没有一个被选中
+        allNotSelected: stateArr.indexOf(true) === -1
+      }
+    },
+    onChange() {
+      const state = this.getState()
+      this.$emit('change', state)
+    },
+    setState(data = []) {
+      if (!Array.isArray) return
+      if (data.length === 0) {
+        this.tagsList.forEach((item) => (item.selected = false))
+        this.tagsList[0].selected = true
+      } else {
+        this.tagsList[0].selected = false
+        this.tagsList.forEach((item) => {
+          if (data.includes(item.value)) {
+            item.selected = true
+          }
+        })
+      }
+    },
+    getState() {
+      const arr = []
+      if (this.tagsList[0].selected) {
+        return arr
+      }
+      return this.tagsList
+        .filter((item) => item.selected)
+        .map((item) => item.value)
+    },
+    onManageClick() {
+      this.$emit('manage')
+      this.showDrawer = true
+    },
+    onCloseDrawer() {
+      this.$emit('close')
+      this.showDrawer = false
+    },
+    onSaveDrawer() {
+      this.$emit('save')
+      this.showDrawer = false
+    },
+    addTagConfirmed() {
+      this.$emit('addTag', this.addTagInput)
+      this.addTagInput = ''
+    },
+    delThisTag(tag) {
+      this.dialog.delete = true
+      this.currentItem = tag
+    },
+    onDialogCancel() {
+      this.dialog.delete = false
+      this.$emit('cancel')
+    },
+    onDialogConfirm() {
+      this.dialog.delete = false
+      this.$emit('delTag', this.currentItem)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.collect-tag-selector{
+  display: flex;
+  justify-content: space-between;
+  .select-group-container{
+    flex: 1;
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    .j-button-item{
+      display: flex;
+      align-items: center;
+      margin: 0 5px;
+      padding: 1px 8px;
+      line-height: 20px;
+      border-radius: 4px;
+      font-size: 14px;
+      text-align: center;
+      background-color: #F5F6F7;
+      border: 1px solid #ECECEC;
+      color: #1D1D1D;
+      cursor: pointer;
+      &.active,
+      &.hover{
+        background-color: #2abed1;
+        color: #fff;
+        border-color: #2abed1;
+      }
+    }
+  }
+  .manage-button{
+    display: flex;
+    align-items: center;
+    padding: 4px 17px;
+    line-height: 22px;
+    border-radius: 4px;
+    font-size: 14px;
+    text-align: center;
+    background-color: #2abed1;
+    color: #fff;
+    cursor: pointer;
+  }
+  ::v-deep{
+    .drawer-class{
+      .el-header{
+        padding: 20px;
+        font-size: 20px;
+        line-height: 32px;
+        border-bottom: 1px solid #ebebeb;
+      }
+      .tag-header{
+        display: flex;
+        align-items: center;
+        padding: 28px 28px 20px;
+        justify-content: space-between;
+      }
+      .add-tag-confirm-button{
+        margin-left: 16px;
+        height: 100%;
+        color: #2CB7CA;
+        white-space: nowrap;
+        cursor: pointer;
+      }
+      .tag-main{
+        padding: 0 20px;
+        flex: 1;
+        overflow-y: scroll;
+        .el-tag {
+          margin: 6px;
+          padding: 1px 8px;
+          height: auto;
+          color: #1d1d1d;
+          background-color: #f5f6f7;
+          border: 1px solid #ececec;
+          border-radius: 5px;
+          line-height: 22px;
+          cursor: pointer;
+          &:hover {
+            color: #2CB7CA;
+            border-color: #2CB7CA;
+            .el-icon-close {
+              color: #2CB7CA;
+              background: transparent;
+            }
+          }
+        }
+      }
+    }
+    .custom-dialog{
+      .dialog-content{
+        text-align: center;
+      }
+      .el-dialog__footer{
+        padding-bottom: 40px;
+        .dialog-footer{
+          flex-direction: row-reverse;
+        }
+        .action-button:not(:last-of-type){
+          margin-right: 0;
+          margin-left: 48px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 10 - 0
apps/bigmember_pc/src/components/selector/SearchTimeScopeSelector.vue

@@ -9,6 +9,8 @@
       :defaultSelectedKey="value"
       :exactCanHalf="exactCanHalf"
       :showConfirmButton="showConfirmButton"
+      :startPlaceholder="startPlaceholder"
+      :endPlaceholder="endPlaceholder"
       @onChange="onChange"
     />
   </div>
@@ -55,6 +57,14 @@ export default {
     showConfirmButton: {
       type: Boolean,
       default: false
+    },
+    startPlaceholder: {
+      type: String,
+      default: ''
+    },
+    endPlaceholder: {
+      type: String,
+      default: ''
     }
   },
   data() {

+ 10 - 0
apps/bigmember_pc/src/components/selector/TimeSelector.vue

@@ -14,6 +14,8 @@
       :selectorType="selectorType"
       :defaultSelectedKey="defaultSelectedKey"
       :showExact="showExact"
+      :startPlaceholder="startPlaceholder"
+      :endPlaceholder="endPlaceholder"
       @onChange="onChange"
     />
   </selector-card>
@@ -42,6 +44,14 @@ export default {
       type: Boolean,
       default: true
     },
+    startPlaceholder: {
+      type: String,
+      default: ''
+    },
+    endPlaceholder: {
+      type: String,
+      default: ''
+    },
     value: {
       type: [Object, String],
       default: () => {

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

@@ -242,6 +242,65 @@ const timeSelectMap = {
       value: '12',
       selected: false
     }
+  ],
+  bidPushTime: [
+    {
+      name: '全部',
+      value: 'all',
+      selected: true
+    },
+    {
+      name: '最近7天',
+      value: 'lately7',
+      selected: false
+    },
+    {
+      name: '最近30天',
+      value: 'lately30',
+      selected: false
+    },
+    {
+      name: '最近1年',
+      value: 'sinceLastYear',
+      selected: false
+    },
+    {
+      name: '最近3年',
+      value: 'sinceLastThreeYear',
+      selected: false
+    },
+    {
+      name: '最近5年',
+      value: 'sinceLastFiveYear',
+      selected: false
+    }
+  ],
+  collectTime: [
+    {
+      name: '全部',
+      value: 'all',
+      selected: true
+    },
+    {
+      name: '今天',
+      value: 'today',
+      selected: false
+    },
+    {
+      name: '昨天',
+      value: 'yesterday',
+      selected: false
+    },
+    {
+      name: '最近7天',
+      value: 'lately7',
+      selected: false
+    },
+    {
+      name: '最近30天',
+      value: 'lately30',
+      selected: false
+    }
   ]
 }
 
@@ -298,6 +357,14 @@ export default {
     showConfirmButton: {
       type: Boolean,
       default: false
+    },
+    startPlaceholder: {
+      type: String,
+      default: ''
+    },
+    endPlaceholder: {
+      type: String,
+      default: ''
     }
   },
   data() {
@@ -314,8 +381,8 @@ export default {
       dateTimePickerState: {
         start: '',
         end: '',
-        startPlaceHolder: '', // 开始日期
-        endPlaceHolder: '' // 结束日期
+        startPlaceHolder: this.startPlaceholder, // 开始日期
+        endPlaceHolder: this.endPlaceholder // 结束日期
       },
       startPickerOptions: {
         disabledDate: (time) => {

+ 2 - 2
apps/bigmember_pc/src/components/subscribe-manager/KeyConfig.vue

@@ -19,7 +19,7 @@
           <img
             @click="setClickEvent"
             src="@/assets/images/icon/help.png"
-            class="icon-help-img"
+            class="help-img"
           />
         </p>
       </div>
@@ -193,7 +193,7 @@ export default {
     color: #1d1d1d;
     line-height: 28px;
     border-bottom: 1px solid #ececec;
-    .icon-help-img {
+    .help-img {
       display: inline-block;
       width: 18px;
       height: 18px;

+ 4 - 0
apps/bigmember_pc/src/main.js

@@ -21,6 +21,7 @@ import '@/utils/directive'
 import '@/utils/prototype'
 import MetaInfo from 'vue-meta-info'
 import JyIcon from '@jianyu/icon' // 需要单独引入icon/index.css
+import { initSentry } from './sentry'
 
 Vue.use(VueCookies)
 Vue.use(Loading.directive)
@@ -56,6 +57,9 @@ const app = easySubAppRegister(
   {
     bootstrap() {
       fixGetComputedStyle()
+
+      // Sentry 异常监控
+      initSentry(Vue)
     }
   }
 )

+ 9 - 0
apps/bigmember_pc/src/router/modules/bidding.js

@@ -0,0 +1,9 @@
+export default [
+  // 标讯搜索
+  {
+    path: '/bidding/collection',
+    name: 'collection',
+    alias: ['/swordfish/frontPage/collection/sess/index'],
+    component: () => import('@/views/collection/index.vue')
+  }
+]

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

@@ -3,7 +3,12 @@ export default [
   // 标讯搜索
   {
     path: '/search/bidding',
-    alias: ['/jylab/supsearch/index.html', '/jylab/bi/index.html', '/jylab/medical/index.html'],
+    alias: [
+      '/jylab/supsearch/index.html',
+      '/jylab/bi/index.html',
+      '/jylab/medical/index.html',
+      '/channel/cooperate/*'
+    ],
     name: 'bidding-search',
     component: () => import('@/views/search/bidding/index.vue')
   },

+ 2 - 1
apps/bigmember_pc/src/router/router-interceptors.js

@@ -49,7 +49,8 @@ const powerCheckWhiteList = [
   'ent-search',
   'purchase-search',
   'supply-search',
-  'nzj-search'
+  'nzj-search',
+  'collection'
 ]
 
 const regListCheck = function (regList, path) {

+ 2 - 0
apps/bigmember_pc/src/router/router.js

@@ -8,6 +8,7 @@ import myProperty from './my-property'
 import order from './modules/order'
 import analyse from './modules/analyse'
 import search from './modules/search'
+import bidding from './modules/bidding'
 
 if (import.meta.env.NODE_ENV !== 'production') {
   Vue.use(VueRouter)
@@ -27,6 +28,7 @@ const router = new VueRouter({
     ...order,
     ...analyse,
     ...search,
+    ...bidding,
     {
       path: '/404',
       name: '404',

+ 20 - 0
apps/bigmember_pc/src/sentry.js

@@ -0,0 +1,20 @@
+import * as Sentry from '@sentry/vue'
+
+export function initSentry(Vue) {
+  if (!import.meta.env.DEV) {
+    Sentry.init({
+      Vue,
+      dsn: 'https://1009883edc154953bec82b5e9ec88bce@jysentry.jydev.jianyu360.cn/7',
+      release: import.meta.env.VITE_APP_GIT_BRANCH,
+      environment: 'produce',
+      sampleRate: 1,
+      beforeSend: (event) => {
+        event.user.email = localStorage.getItem('BIGMEMBER_PC')
+        return event
+      }
+    })
+    Sentry.setTag('url', location.href)
+    const id = document.cookie.match(/(^|;)\s*ud_safe\s*=\s*([^;]+)/)
+    Sentry.setUser({ id: id && id[2] })
+  }
+}

+ 189 - 0
apps/bigmember_pc/src/views/collection/components/search-filter.vue

@@ -0,0 +1,189 @@
+<script setup>
+import { computed, watch, onMounted } from 'vue'
+import { SearchBidModel } from '../model/index'
+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,
+  filterState,
+  updateFilterBase,
+  doQuery,
+  goLogin,
+  disposeFilterSchema
+} = SearchBidModel
+
+const  {
+  SearchBidBaseSchema,
+  SearchBidMoreSchema,
+  searchBidMoreFreeSchema,
+  searchBidMoreVipSchema,
+  doUpdateData
+} =  disposeFilterSchema()
+
+const customMoreSchema = computed(() => {
+  return {
+    vipModuleShow: true,
+    commonConf: {
+      showLabel: false,
+      styleType: 'row'
+    },
+    freeConf: {
+      showRowLabel: true,
+      rowLabelText: '更多筛选:',
+      schema: searchBidMoreFreeSchema.value
+    },
+    vipConf: {
+      schema: searchBidMoreVipSchema.value
+    }
+  }
+})
+
+onMounted(() => {
+  doUpdateData()
+})
+
+function noPower() {
+  $bus.$emit('search:filter:no-power')
+}
+
+function toLogin() {
+  $bus.$emit('bidding:goLogin')
+}
+
+function doChangeFilter() {
+  doQuery()
+}
+
+</script>
+
+<template>
+  <el-collapse-transition>
+    <div class="search-bid-filter">
+      <!--  标准筛选  -->
+      <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"
+          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="true"
+            @clickVipMask="noPower"
+            @clickBaseMask="toLogin"
+            @change="doChangeFilter"
+          ></SelectorWithBasePower>
+        </template>
+      </div>
+    </div>
+  </el-collapse-transition>
+</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: 16px;
+    margin-right: 38px;
+  }
+  // 时间选择器
+  ::v-deep{
+    .j-button-item{
+      padding: 2px 8px;
+      margin-top: 0;
+    }
+    .date-time-container{
+      padding: 0;
+      background: transparent;
+      .date-time-item{
+        width: 126px;
+        &.left::after{
+          background-color: #E0E0E0;
+        }
+      }
+    }
+    .base-power-module.vip-module{
+      margin-left: 0;
+    }
+  }
+}
+</style>

+ 261 - 0
apps/bigmember_pc/src/views/collection/components/search-list-table.vue

@@ -0,0 +1,261 @@
+<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">',
+      '</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>

+ 129 - 0
apps/bigmember_pc/src/views/collection/composables/constant/list-header-actions.js

@@ -0,0 +1,129 @@
+/**
+ * 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: 'edit-tags',
+      icon: 'icon-edit',
+      label: '修改标签'
+    })
+  )
+
+  // 取消操作项
+  list.push(
+    createListHeaderActionItem({
+      key: 'cancel-collect',
+      icon: 'icon-shoucang_weishoucang',
+      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.forEach((item) => {
+    ListHeaderActions[item.key] = item
+  })
+}
+
+// 调用installListHeader安装列表头部操作项
+installListHeader()
+
+// 导出ListHeaderActions
+export { ListHeaderActions }

+ 8 - 0
apps/bigmember_pc/src/views/collection/constant/index.js

@@ -0,0 +1,8 @@
+import { createSearchBidBaseSchema, createSearchBidMoreSchema } from '@/views/collection/constant/search-filters.js'
+
+export function getCreateSearchSchema() {
+  return {
+    createSearchBidBaseSchema,
+    createSearchBidMoreSchema
+  }
+}

+ 257 - 0
apps/bigmember_pc/src/views/collection/constant/search-filters.js

@@ -0,0 +1,257 @@
+import CollectTagSelector from '@/components/filter-items/CollectTagSelector.vue'
+import SearchTimeScopeSelector from '@/components/selector/SearchTimeScopeSelector.vue'
+import BuyerTypeSelector from '@/components/filter-items/BuyerTypeSelector.vue'
+import ContactSelector from '@/components/filter-items/ContactSelector.vue'
+import AmountRangeSelector from '@/components/filter-items/AmountRangeSelector.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 CollectSearchInput from '@/components/filter-items/CollectSearchInput.vue'
+import InfoTypeSelector from '@/components/filter-items/InfoTypeDropdown.vue'
+import { dataCollectTagModel } from '../model/modules/data-collect-tag'
+const { addLabelFn, deleteLabelFn } = dataCollectTagModel({ that: this })
+
+import $bus from '@/utils/bus'
+import { findIndex } from 'lodash'
+
+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 = [
+  'buyerclass',
+  'buyertel',
+  'winnertel'
+]
+
+
+function createSearchBidBaseSchema(conf = {}) {
+  console.log(conf, '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 isInApp = conf.isInApp || false
+  const isVip = conf.isVip || false
+  const tagsList = conf.tagsList || []
+
+  const SearchBidBaseSchema = [
+    {
+      key: 'label',
+      label: '个人标签:',
+      defaultVal: '',
+      _name: 'label',
+      _type: 'component',
+      expand: {
+        component: CollectTagSelector,
+        props: {
+          sourceList: tagsList,
+          showLabel: false
+        },
+        hooks: {
+          addTag: function (val) {
+            addLabelFn(val)
+          },
+          delTag: function (item) {
+            deleteLabelFn(item)
+          }
+        }
+      }
+    },
+    {
+      key: 'selectTime',
+      label: '收藏日期:',
+      defaultVal: '',
+      _name: 'time',
+      _type: 'component',
+      expand: {
+        component: SearchTimeScopeSelector,
+        props: {
+          type: 'collectTime',
+          exactCanHalf: true,
+          showConfirmButton: true,
+          startPlaceholder: '开始日期',
+          endPlaceholder: '结束日期'
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'keyWords',
+      label: '标题正文:',
+      defaultVal: '',
+      _name: 'keywords',
+      _type: 'component',
+      expand: {
+        component: CollectSearchInput,
+        props: {},
+        hooks: {
+          change: function(val) {
+            console.log(val)
+          }
+        }
+      }
+    },
+    {
+      key: 'publishTime',
+      label: '发布时间:',
+      defaultVal: '',
+      _name: 'time',
+      _type: 'component',
+      expand: {
+        component: SearchTimeScopeSelector,
+        props: {
+          type: 'bidPushTime',
+          exactCanHalf: true,
+          showConfirmButton: true,
+          startPlaceholder: '开始日期',
+          endPlaceholder: '结束日期'
+        },
+        hooks: {}
+      }
+    }
+  ]
+
+  return SearchBidBaseSchema
+}
+
+function createSearchBidMoreSchema(filterItems, conf) {
+  const isLogin = conf.isLogin || false
+  const isVip = conf.isVip || false
+
+  let SearchBidMoreSchema = [
+    {
+      key: 'subtype',
+      label: '信息类型',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: InfoTypeSelector,
+        props: {},
+        hooks: {}
+      }
+    },
+    {
+      key: 'regionMap',
+      label: '地区',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: RegionSelector,
+        props: {
+          vip: isVip && isLogin,
+          showCount: false
+        },
+        hooks: {
+          limit: noPower
+        }
+      }
+    },
+    {
+      key: 'industry',
+      label: '行业',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: IndustrySelector,
+        hooks: {}
+      }
+    },
+    {
+      key: 'price',
+      label: '金额区间',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: AmountRangeSelector,
+        hooks: {}
+      }
+    },
+    {
+      key: 'buyerclass',
+      label: '采购单位类型',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: BuyerTypeSelector,
+        hooks: {}
+      }
+    },
+    {
+      key: 'buyertel',
+      label: '采购单位联系方式',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: ContactSelector,
+        props: {
+          source: 'buyer'
+        },
+        hooks: {}
+      }
+    },
+    {
+      key: 'winnertel',
+      label: '中标企业联系方式',
+      defaultVal: '',
+      _name: 'type',
+      _type: 'component',
+      expand: {
+        component: ContactSelector,
+        props: {
+          source: 'winner'
+        },
+        hooks: {}
+      }
+    }
+  ]
+
+  // VIP处理
+  SearchBidMoreSchema.forEach((schema) => {
+    const key = schema.key
+    if (moreFiltersNeedVipKeyList.includes(key)) {
+      schema.vipMark = 1
+    }
+  })
+
+  return SearchBidMoreSchema
+}
+
+export { createSearchBidBaseSchema, createSearchBidMoreSchema }

+ 291 - 0
apps/bigmember_pc/src/views/collection/index.vue

@@ -0,0 +1,291 @@
+<script setup>
+import { ref } from 'vue'
+import SearchFilter from '@/views/collection/components/search-filter.vue'
+import SearchList from '@/views/search/layout/search-list.vue'
+import SearchListTable from '@/views/search/bidding/components/search-list-table.vue'
+import ArticleItem from '@/components/article-item/ArticleItem.vue'
+import Empty from '@/components/common/Empty.vue'
+import CustomDialog from '@/components/dialog/Dialog.vue'
+// 导入业务模型
+import { useSearchBidModel, SearchBidModel } from './model/index'
+// 初始化模型
+useSearchBidModel()
+// 初始化模型 解构业务所需 model \ fn
+const {
+  isLogin,
+  isInApp,
+  isInBI,
+  inInjectBI,
+  isVip,
+  isFree,
+  vt,
+  filterState,
+  inputKeywordsState,
+  listState,
+  activeItemStyleType,
+  searchListProps,
+  doQuery,
+  doListHeaderAction,
+  doChangeAllSelect,
+  doChangeSelect,
+  doChangePageNum,
+  doChangePageSize,
+  toDetail,
+  list,
+  tableList,
+  tableFixedTop,
+  tagToDetail,
+  setExport,
+  exportDialogChange,
+  showDataExportDialog,
+  onClickSingleCollect,
+  vipDialogConf,
+  closeVipDialog,
+  onJoinBid,
+  onAddInfoOfBI,
+  onSingleAddInfo,
+  timeSelectorText,
+  collectElementRef,
+  onFreeTaste,
+  interceptKeywords,
+  getLabelQuery,
+  addLabelQuery,
+  deleteLabelQuery,
+} = SearchBidModel
+
+// 开通超级订阅
+function toBuySvip () {
+  window.open('/swordfish/page_big_pc/free/svip/buy?type=buy')
+}
+
+const {
+  show: showVipDialog,
+  text: vipDialogText,
+  type: vipDialogType
+} = vipDialogConf
+
+// 列表-单条-配置
+const articleRef = ref({
+  bidding: true,
+  detail: true,
+  gray: true,
+  joinBid: !isInBI.value,
+  table: false,
+  collect: !isInBI.value,
+  push: false
+})
+doQuery()
+getLabelQuery()
+console.log(searchListProps, 'searchListProps')
+</script>
+<template>
+  <div class="collection-page">
+    <h2 class="page-title">标讯收藏</h2>
+    <div class="page-filter-container b-rd-8px" v-if="!isInBI">
+      <SearchFilter></SearchFilter>
+    </div>
+    <div class="page-list-container b-rd-8px">
+      <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="isInBI">
+          <div class="all-add bi-batch-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'
+            @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 }"
+              :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>
+              <template #right-handle-container>
+                <div
+                  v-if="inInjectBI"
+                  class="bi-add-button"
+                  @click.prevent.stop="onSingleAddInfo(item)"
+                >
+                  <span>{{ item.isAdd ? '已添加' : '添加' }}</span>
+                </div>
+              </template>
+            </article-item>
+          </div>
+        </template>
+        <template #empty>
+          <empty :mtb60="false"  images="jy-back.png">
+            <div class="hasNoData_tiptext">
+              <p>对不起,没有找到 <span class="highlight-text">{{ timeSelectorText }}</span> 相关匹配的信息,</p>
+              <p>修改时间范围或换个搜索词试试吧</p>
+            </div>
+          </empty>
+        </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="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>
+    <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>
+  </div>
+</template>
+
+<style lang="scss">
+@import '~@/assets/style/component/collect-tags-box.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;
+  }
+}
+</style>
+<style scoped lang="scss">
+.in-app {
+  .collection-page{
+    margin-top:0;
+    width: 100%;
+    padding: 24px;
+  }
+}
+.in-web{
+  .collection-page{
+    margin-top: 24px;
+  }
+}
+.collection-page{
+  width: 1200px;
+  margin: 0 auto;
+  .page-title{
+    padding-bottom: 24px;
+    font-size: 24px;
+    line-height: 36px;
+    color: #1d1d1d;
+  }
+  .page-filter-container{
+    background: #fff;
+  }
+  .page-list-container{
+    position: relative;
+    margin-top: 20px;
+  }
+  .bi-add-button {
+    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;
+  }
+  .bi-batch-add-button{
+    &.all-add {
+      margin-right: 16px;
+      font-size: 14px;
+      color: #1d1d1d;
+      cursor: pointer;
+    }
+  }
+  ::v-deep{
+    .search-header-container{
+      border-bottom: 1px solid #ececec;
+    }
+  }
+}
+</style>

+ 814 - 0
apps/bigmember_pc/src/views/collection/model/base.js

@@ -0,0 +1,814 @@
+import { computed, reactive, ref, toRefs, onMounted,onBeforeMount, onBeforeUnmount, getCurrentInstance } 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,
+  getParam,
+  openLinkInWorkspace,
+  InContainer
+} from '@/utils'
+import $bus from '@/utils/bus'
+// 筛选条件动态组件方法
+import { getCreateSearchSchema } from '@/views/collection/constant/index'
+// API 业务模型
+import useQuickCollectBidModel from '@jy/data-models/modules/quick-collect-bid/model'
+// 扩展业务模型
+import { useSearchFilterModel } from './modules/filter'
+import { useSearchListHeaderActionsModel } from './modules/list-header-actions'
+// 数据导出业务
+import { dataExportActionsModel } from './modules/data-export-actions'
+// 标讯收藏业务
+import { dataCollectActionModel } from './modules/data-collect-actions'
+// 添加业务
+import { dataAddActionsModel } from './modules/data-add-actions'
+// 标签业务
+import { dataCollectTagModel } from './modules/data-collect-tag'
+// 参标业务
+import { joinBidActionsModel } from './modules/join-bid-actions'
+
+
+export default function () {
+  const that = getCurrentInstance().proxy
+
+  const router = useRouter()
+  // 是否是免费用户
+  const isFree = computed(() => {
+    return useStore().getters['user/isFree']
+  })
+  // 企业管理员
+  const isEntAdmin = computed(() => {
+    return useStore().getters['user/isEntAdmin']
+  })
+  // 部门管理员
+  const isDepartmentAdmin = computed(() => {
+    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
+  })
+  // 是否是老用户
+  const isOld = computed (() => {
+    const { isOld } = userPowerInfo.value
+    return isOld
+  })
+
+  // 是否在工作台内
+  // 本地调试,可改为工作台内isInApp = ref(true),  isInWeb = ref(false)
+  const isInApp = ref(InContainer.inApp)
+  const isInWeb = ref(InContainer.inWeb)
+
+  // 是否山川应用嵌入环境 添加操作按钮 (个人年终报告嵌套)
+  const inInjectBI = useRoute().query.report === 'bi'
+
+  // 是否是BI嵌套页面
+  const isInBI = computed(() => {
+    return inInjectBI
+  })
+
+  onMounted(() => {
+    window.addEventListener('scroll', watchScroll)
+  })
+
+
+  // 解构基础业务
+  const APIModel = useQuickCollectBidModel({})
+  /**
+   * 列表相关model
+   */
+  const {
+    list,
+    total,
+    loading,
+    finished,
+    selectIds,
+    listIds,
+    searchResultCount,
+    isSelectSomeCheckbox,
+    selectCheckboxCount,
+    isSelectListAllCheckbox,
+    doToggleItemSelection,
+    doToggleListSelection,
+    doClearAllSelection,
+    doQuery: doRunQuery,
+    getLabelQuery,
+    tagsList
+  } = APIModel
+  console.log(APIModel, 'APIModel')
+ 
+  /**
+   * 筛选v-model数据
+   */
+  const {
+    filterState,
+    getFormatAPIParams: getFormatOfFilter,
+    updateFilterBase
+  } = useSearchFilterModel({ isFree, isInApp })
+  /**
+   * 列表头操作
+   */
+  const {
+    limitActions,
+    headerActions,
+    listItemStyleType,
+    activeItemStyleType,
+    activeHeaderActions,
+    disabledHeaderActions
+  } = useSearchListHeaderActionsModel()
+
+  $bus.$on('bidding:updateInputKeywords', function (obj) {
+    updateInputKeywordsState(obj)
+  })
+
+
+  /**
+   * 筛选条件动态配置处理
+   */
+  const {
+    createSearchBidBaseSchema,
+    createSearchBidMoreSchema
+  } = getCreateSearchSchema()
+  // 处理当前信息类型--展示数据类型
+  const  infoTypeDataType = computed(() =>  {
+    let result = 'all'
+    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,
+        isVip: isVip.value,
+        isInBI: isInBI.value,
+        tagsList: tagsList
+      }
+    })
+
+    // 标准筛选
+    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(searchBidMoreFreeSchema.value, searchBidMoreVipSchema.value, 'base')
+
+
+    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.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 limitFilterHeaderActions =  computed(() => {
+    // BI营销以及个人分析报告不展示以下操作
+    if(!inInjectBI) {
+      // 数据导出
+      limitActions.value['data-export'] = true
+      limitActions.value['refined-list'] = true
+      limitActions.value['table'] = true
+      limitActions.value['edit-tags'] = true
+      limitActions.value['cancel-collect'] = true
+      // 登录后才展示详细列表
+      if(isLogin.value) {
+        limitActions.value['detailed-list'] = 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: 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)
+  }
+
+
+  /**
+   * 列表顶部按钮操作事件统一入口
+   * @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 'edit-tags': {
+        onClickEditTags($event)
+        break
+      }
+      case 'cancel-collect': {
+        onClickCancelCollect($event)
+        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"
+
+    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{
+      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)
+    }
+  }
+
+  /**
+   * 格式化请求参数
+   * @param [params] - 可选值,部分情况会提供,默认会和该函数返回值进行合并
+   */
+
+  function getParams(params = {}) {
+    // 合并所有模型的搜索筛选项
+    const result = Object.assign(
+      {
+        searchGroup: '',
+        // reqType: 'lastNews', // cache:空搜索缓存数据;lastNews:最新数据
+        pageNum: listState.pageNum,
+        pageSize: listState.pageSize
+      },
+      getFormatOfFilter(),
+      params
+    )
+    return result
+  }
+
+  // 搜索前 处理一些事情
+  function beforeSearchSomething (pageNum) {
+    // 列表清空
+    list.value = []
+    listState.total = 0
+    // 列表重新获取时
+    if(!pageNum) {
+      listState.pageNum = 1
+      // 清空已选中数据
+      doClearAllSelection()
+      // 清空表格数据
+      tableList.value = []
+    }
+
+  }
+
+  /**
+   * 统一查询入口
+   * - 拦截 doQuery 进行一些返回值处理
+   * @param [params] - 可选值,默认会和 getParams(params) 返回值进行合并
+   */
+  function doQuery(params = {}, pageNum) {
+    beforeSearchSomething (pageNum)
+    // Ajax请求
+    return doRunQuery(getParams(params)).then((res) => {
+      afterQueryAjax(res)
+    })
+  }
+  // 处理查询请求后的数据以及其他业务
+  function afterQueryAjax (res) {
+
+    const { origin } = res
+
+    listState.pageTotal = origin?.count || 0
+
+    //表格列表数据--只取第一页的前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', 1)
+
+      return item
+    })
+
+  }
+
+  // 组合好的组件格式的筛选条件
+  function packageFilter () {
+    const originParams = Object.assign(
+      filterState.value
+    )
+
+    return originParams
+  }
+
+  /******开通超级订阅弹窗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 fixedTop = ref(false)
+  // 表格顶部操作方法滚动吸顶
+  const tableFixedTop = ref(false)
+
+  function watchScroll () {
+    const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
+    const watchTable = document.querySelector('.page-list-container').offsetTop - 15 // 15为margin-top值
+    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 { onEditTags } = dataCollectTagModel({ that })
+  function onClickEditTags(e) {
+    console.log('修改标签')
+    const config = {
+      total: listState.total,
+      isSelectSomeCheckbox: isSelectSomeCheckbox.value,
+      selectIds: selectIds.value,
+      event: e
+    }
+    onEditTags(config)
+  }
+  /*******修改标签 end ***********/
+
+  /********* 标讯收藏部分start ********/
+  const { onCollect } = dataCollectActionModel({ that })
+
+  // 批量取消标讯收藏
+  function onClickCancelCollect(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回调
+  $bus.$on('bidding:updateListCollectStatus', function () {
+    doQuery({}, 'pageNumChange', listState.pageNum)
+  })
+  /********* 标讯收藏部分end ********/
+
+  // 处理数据列表为空时,需要展示的提示文案包含时间
+  const timeSelectorText = computed(() => {
+    const publishTime = filterState.value.publishTime
+    const split = '_'
+    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 = '近一年'
+    }
+
+    return result
+  })
+
+  /********参标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 ***********/
+
+   /*****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 collectElementRef = ref(null)
+  // 免费用户点免费体验留资
+  function onFreeTaste () {
+    if( collectElementRef.value) {
+      collectElementRef.value.isNeedSubmit('jylab_see500_plus', () => {
+      })
+    }
+  }
+  /*******工作台跳转end***********/
+
+  // 页面初始化
+  function initPage () {
+    // getTagsList()
+  }
+  initPage()
+
+  return {
+    isLogin,
+    isInApp,
+    isInWeb,
+    isInBI,
+    inInjectBI,
+    isFree,
+    isVip,
+    goLogin,
+    list,
+    tableList,
+    searchListProps,
+    filterState,
+    updateFilterBase,
+    listState,
+    activeItemStyleType,
+    disposeFilterSchema,
+    doQuery,
+    doListHeaderAction,
+    doChangeAllSelect,
+    doChangeSelect,
+    doChangePageNum,
+    doChangePageSize,
+    vipDialogConf,
+    closeVipDialog,
+    fixedTop,
+    tableFixedTop,
+    toDetail,
+    tagToDetail,
+    onClickDataExport, // 以下为导出相关
+    setExport,
+    exportDialogChange,
+    showDataExportDialog,
+    onClickSingleCollect, // 收藏
+    onJoinBid,//以下 参标
+    onAddInfoOfBI,
+    onSingleAddInfo,
+    BidrenewalDialogRef,
+    timeSelectorText,
+    collectElementRef,
+    onFreeTaste,
+    getLabelQuery,
+    tagsList
+  }
+}

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

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

+ 57 - 0
apps/bigmember_pc/src/views/collection/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
+  }
+}

+ 94 - 0
apps/bigmember_pc/src/views/collection/model/modules/data-collect-actions.js

@@ -0,0 +1,94 @@
+import { computed, onMounted, ref } from 'vue'
+import { useStore } from '@/store'
+import $bus from '@/utils/bus'
+import { bidCollAction } 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(() => {})
+
+  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(binfo && binfo.length) {
+      const reParams = {
+        baction: 'R',
+        binfo,
+        type,
+        event
+      }
+      ajaxForCollectChange(reParams, function (res) {
+        if (res.data) {
+          // 更新列表收藏状态
+          $bus.$emit('bidding:updateListCollectStatus')
+          that.$toast('已取消收藏', 1000)
+        } else {
+          that.$toast(res.error_msg, 1000)
+        }
+      })
+    }
+  }
+
+  async function ajaxForCollectChange(reParams, callback) {
+    const $ = that.$querySelector.bind(that)
+    const { baction, binfo, type, event } = reParams
+    /*
+      收藏或取消收藏ajax
+      params: {
+        baction: String, //用户行为:R:移除收藏;C:收藏(默认)非必填
+        binfo: Array, // 招标信息数组 必填
+        bid: String, // 招标信息加密后id 必填
+      }
+    */
+    const params = {
+      baction: baction,
+      binfo: binfo
+    }
+    const res = await bidCollAction(params)
+    if(callback) {
+      callback(res)
+      return
+    }
+  }
+
+  return {
+    onCollect
+  }
+}

+ 370 - 0
apps/bigmember_pc/src/views/collection/model/modules/data-collect-tag.js

@@ -0,0 +1,370 @@
+import { computed, onMounted, ref } from 'vue'
+import { useStore } from '@/store'
+import { getEventTarget } from '@/utils/jq-help'
+import $bus from '@/utils/bus'
+import { getBidCollTagList, saveBidCollAddTag, createBidTag } from '@/api/modules/'
+// API 业务模型
+import useQuickCollectBidModel from '@jy/data-models/modules/quick-collect-bid/model'
+// 解构基础业务
+const APIModel = useQuickCollectBidModel({})
+const {
+  getLabelQuery,
+  addLabelQuery,
+  deleteLabelQuery
+} = APIModel
+
+// 是否登录
+const isLogin = computed(() => {
+  return useStore().getters['user/loginFlag']
+})
+
+export function dataCollectTagModel (params) {
+  const { that } = params
+  const bidInfo = ref([])
+
+  onMounted(() => {
+    initCollectEvent()
+  })
+
+  function onEditTags(config) {
+    // 未登录跳转登录
+    if (!isLogin.value) {
+      $bus.$emit('bidding:goLogin')
+      return
+    }
+
+    const { total, isSelectSomeCheckbox, event, selectIds } = config
+
+    // 列表无数据
+    if (!total) return
+    // 无选择标讯
+    if (!isSelectSomeCheckbox) {
+      return that.$toast('尚未选择标讯,请选择')
+    }
+    const binfo = selectIds.map(id => {
+      return {
+        bid: id
+      }
+    })
+    const reParams = {
+      baction: 'R',
+      binfo,
+      event
+    }
+    console.log(bidInfo, event, 'bidInfo')
+    ajaxForEditTagsChange(reParams)
+  }
+
+  async function getLabelFn() {
+    const { data } = await getLabelQuery()
+    console.log(data, 'getLabel')
+  }
+
+  async function addLabelFn(name) {
+    const { success, data } = await addLabelQuery({ name: name })
+    if (success && data) {
+      getLabelQuery()
+    } else {
+      that.$toast(msg)
+    }
+  }
+
+  async function deleteLabelFn(params) {
+    const { name, value } = params
+    const formatParams = {
+      laction: 'D',
+      lids: value,
+      lname: name
+    }
+    const { success, data, msg } = await deleteLabelQuery(formatParams)
+    if (success && data) {
+      that.$toast('标签已删除')
+      getLabelQuery()
+    } else {
+      that.$toast(msg)
+    }
+  }
+
+  async function ajaxForEditTagsChange(reParams, callback) {
+    const $ = that.$querySelector.bind(that)
+    const { event, binfo } = reParams
+    const { top, left } = calcCardTopLeft(event)
+    $('.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
+      })
+    bidInfo.value = binfo
+    window.getUserTags()
+  }
+
+  // 处理收藏弹出的标签框
+  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: bidInfo.value
+        }
+
+        // 执行保存绑定标签操作
+        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) {
+    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
+    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 {
+    onEditTags
+  }
+}

+ 117 - 0
apps/bigmember_pc/src/views/collection/model/modules/data-export-actions.js

@@ -0,0 +1,117 @@
+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)
+    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)
+    // 未登录跳转登录
+    if (!isLogin.value) {
+      $bus.$emit('bidding:goLogin')
+      return
+    } else {
+      toDataExportEvent()
+    }
+  }
+
+  return {
+    dataExport,
+    setExport,
+    exportDialogChange,
+    showDataExportDialog,
+    toPayDataExport
+  }
+}

+ 125 - 0
apps/bigmember_pc/src/views/collection/model/modules/filter.js

@@ -0,0 +1,125 @@
+import { ref } from 'vue'
+import { FilterHistoryViewModel2AjaxModel, infoTypeMapFormat } from '@/utils'
+
+export function useSearchFilterModel(conf) {
+  const { isFree,isInApp, isBidField } = conf
+  // 筛选组件状态
+  const filterBase = ref({
+    label: '',
+    // 收藏日期
+    selectTime: '',
+    // 标题正文关键词
+    keyWords: '',
+    // 发布时间
+    publishTime: '',
+    // 信息类型
+    subtype: {},
+    // 地区
+    regionMap: {},
+    // 行业
+    industry: {},
+    // 金额区间
+    price: '',
+    // 采购单位类型
+    buyerclass: {},
+    // 采购单位联系方式
+    buyertel: '',
+    // 中标企业联系方式
+    winnertel: '',
+    // 其它动态获取的筛选条件
+    _expand: {
+
+    }
+  })
+  const filterState = ref({})
+  filterState.value = filterBase.value
+ // 搜索接口需要的格式化后的数据
+ function getFormatAPIParams (){
+    return getFormatApiBaseParams()
+ }
+  // 格式化招标采购基础筛选条件
+  function getFormatApiBaseParams (expendObj = {}) {
+    const { selectTime, publishTime, regionMap, industry, buyerclass, subtype, _expand } = filterState.value
+    console.log(publishTime, filterState.value, 'publishTime')
+    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 = infoTypeMapFormat(subtype)
+    const rSelectTime = selectTime?.indexOf('_') > -1 ? FilterHistoryViewModel2AjaxModel.formatExactTime(selectTime, '-') :  FilterHistoryViewModel2AjaxModel.formatTime(selectTime, true, '-')
+    const expandTag = {}
+    if(typeof (_expand) === 'object' && Object.keys(_expand).length > 0) {
+      for(let key in _expand) {
+        expandTag[key] = filterState.value[key]
+      }
+    }
+
+    const params = {
+      label: filterState.value.label.toString(),
+      selectTime: rSelectTime,
+      keyWords: filterState.value.keyWords,
+      city,
+      publishTime: rPublishTime,
+      subtype: rSubtype,
+      industry: rIndustry,
+      province: area,
+      city,
+      district,
+      buyerClass: rBuyerClass,
+      price: filterState.value.price,
+      buyerTel: filterState.value.buyertel,
+      winnerTel: filterState.value.winnertel,
+      ...expandTag
+    }
+    return params
+  }
+  // 动态更新筛选条件
+  function updateFilterBase (keyObj = {}) {
+    const {key, value } = keyObj
+
+    if(keyObj && Object.keys(keyObj).length > 0) {
+      filterBase.value = Object.assign(filterBase.value, {
+        [key]: value
+      })
+      filterBase.value._expand = Object.assign({}, {
+        [key]: value
+      })
+      filterState.value = Object.assign(filterBase.value, filterState.value)
+    } else {
+      filterBase.value = Object.assign(filterBase.value, {
+        // 标签
+        label: '',
+        // 收藏时间
+        selectTime: '',
+        // 关键词
+        keyWords: '',
+        // 发布时间
+        publishTime: '',
+        // 信息类型
+        subtype: [],
+        // 地区
+        regionMap: null,
+        // 行业
+        industry: null,
+        // 金额区间
+        price: '',
+        // 采购单位类型
+        buyerclass: null,
+        // 采购单位联系方式
+        buyertel: '',
+        // 中标企业联系方式
+        winnertel: ''
+      })
+      filterState.value = filterBase.value
+    }
+
+  }
+
+  return {
+    filterState,
+    filterBase,
+    getFormatAPIParams,
+    updateFilterBase
+  }
+}

+ 56 - 0
apps/bigmember_pc/src/views/collection/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
+  }
+}

+ 96 - 0
apps/bigmember_pc/src/views/collection/model/modules/list-header-actions.js

@@ -0,0 +1,96 @@
+import { computed, ref } from 'vue'
+import { ListHeaderActions } from '@/views/collection/composables/constant/list-header-actions'
+
+// 列表顶部操作按钮
+const actions = [
+  ['refined-list', 'detailed-list', 'table'],
+  ['data-export'],
+  ['edit-tags'],
+  ['cancel-collect']
+  // ['distribute-bid'],
+  // ['employ-bid']
+]
+
+// 列表 Item 样式风格切换,用于兼容旧参数
+const ItemStyleTypes = {
+  'refined-list': 'S',
+  'detailed-list': 'D',
+  table: 'T'
+}
+
+// 权限列表,过滤操作按钮权限
+const limitActions = ref({
+  'refined-list': false,
+  'detailed-list': false,
+  'table': false,
+  'data-export': false,
+  'edit-tags': false,
+  'cancel-collect': false
+  // 'collect-bid': false,
+  // 'distribute-bid': false,
+  // 'employ-bid': 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
+  }
+}

+ 22 - 3
apps/bigmember_pc/src/views/portrayal/UnitPortrayal.vue

@@ -4,7 +4,7 @@
     :needAd="layoutNeedAd"
     :contentWithState="layoutContentWithState"
   >
-    <div class="unit-portrayal-content" v-loading="loading">
+    <div class="unit-portrayal-content" v-loading="pageLoading">
       <div class="unit-type">
         <div class="u-top-container">
           <h1 class="u-name">{{ info.buyerName }}</h1>
@@ -744,7 +744,8 @@ export default {
         visited: false,
         balance: 0 // 剩余次数
       }, // 画像访问量
-      freeTrial: false,
+      freeTrial: true,
+      freeTrialLoaded: false,
       showFreeOpen: false,
       sendData: {
         type: 'UnitPortrayal',
@@ -831,6 +832,19 @@ export default {
     isFreeOrNoLogin() {
       return this.userInfo.isFree || !this.loginFlag
     },
+    pageLoading () {
+      return this.loading || !this.getTrailLoaded
+    },
+    getTrailLoaded () {
+      const info = this.userInfo
+      // 免费用户 体验过期 浏览过
+      const freeTail = info.isFree && info.freeBuyerPort < 0
+      if (freeTail) {
+        return this.freeTrialLoaded
+      } else {
+        return true
+      }
+    },
     conf7() {
       const power = this.userInfo.power
       if (power) {
@@ -1336,7 +1350,12 @@ export default {
     },
     // 获取免费用户是否体验过该采购单位
     getIsTrail(data) {
-      this.freeTrial = data ? data : false
+      if (typeof data === 'boolean') {
+        this.freeTrial = data
+      } else {
+        this.freeTrial = false
+      }
+      this.freeTrialLoaded = true
     },
     // 等待画像接口请求完成再调用usage接口
     getUse(data) {

+ 1 - 3
apps/bigmember_pc/src/views/portrayal/components/UnitChart.vue

@@ -1010,9 +1010,7 @@ export default {
       if (res.error_code === 0) {
         if (res.data && Object.keys(res.data).length > 0) {
           this.showChart = true
-          if (res.data.onTrial) {
-            this.$emit('isTrial', res.data.onTrial)
-          }
+          this.$emit('isTrial', res.data?.onTrial || false)
           this.initData('', res.data)
         } else {
           this.getSectionChartData('a', newval)

+ 4 - 1
apps/bigmember_pc/src/views/search/bidding/components/search-bid-filter.vue

@@ -62,6 +62,8 @@ if(inBIPropertyIframe) {
 
 // 中国移动定制搜索条件-融创
 async function getCustomInfo () {
+  const _storageKey = 'search_bidding_expandSearchParams'
+  localStorage.removeItem(_storageKey)
   const { error_code:code, data = [] } = await getCMCustomInfo()
   if(code === 0) {
     if (data) {
@@ -76,8 +78,9 @@ async function getCustomInfo () {
           updateFilterBase(par)
         }
       })
+      localStorage.setItem(_storageKey, JSON.stringify(expandSearchParams))
       doUpdateData(data, 'more')
-      doQuery(expandSearchParams, 'firstPage')
+      doQuery({}, 'firstPage')
     }
   }
 }

+ 39 - 15
apps/bigmember_pc/src/views/search/bidding/model/base.js

@@ -663,14 +663,6 @@ export default function () {
     if(activeItemStyleType.value === 'T') {
       restoreListTabActive()
     }
-    // 存储筛选条件
-    if(isInApp.value && isLogin.value && listState.pageNum === 1 && !inBIPropertyIframe) {
-      saveSearchGroupToLocal()
-
-      if(inputKeywordsState.value.input) {
-        saveFilterToLocal()
-      }
-    }
 
     //P271需求--潜客圈进引流需求
     if(!advancedInfo.show && !getShowChart.value && inputKeywordsState.value.input) {
@@ -696,6 +688,14 @@ export default function () {
   }
   // 处理查询请求后的数据以及其他业务
   function afterQueryAjax (res, searchType) {
+    // 存储筛选条件
+    if(isInApp.value && isLogin.value && listState.pageNum === 1 && !inBIPropertyIframe) {
+      saveSearchGroupToLocal()
+
+      if(inputKeywordsState.value.input) {
+        saveFilterToLocal()
+      }
+    }
     // 变更搜索模式
     changeSearchMode(searchType)
 
@@ -857,6 +857,8 @@ export default function () {
    * @param item
    */
   function onSelectedFilter (item) {
+    // 动态插入的存储的筛选条件
+    const _expand = getExpandSearchParams()
     const { isPay } = item
     // 恢复选中状态
     if (isFree?.value && isPay) {
@@ -891,13 +893,13 @@ export default function () {
             notkey: '',
             buyer: '',
             winner: '',
-            agency: ''
+            agency: '',
           })
-          restoreFilter(params, 'saveBack')
+          restoreFilter(params, 'saveBack', {_expand})
           historyFilterDialogVisible.value = false
         })
     } else {
-      restoreFilter(item, 'saveBack')
+      restoreFilter(item, 'saveBack', {_expand})
     }
   }
   function restoreFilter (viewFilter, type, expandFilter) {
@@ -912,6 +914,8 @@ export default function () {
       Object.assign(resultFilter, expandFilter)
     }
     doChangeTab({ key: resultFilter.searchGroup })
+    let  _expand = resultFilter._expand || {}
+
     filterState.value = {
       bidField: isBidField ? 'medical' : '',
       publishTime: resultFilter.publishTime,
@@ -927,7 +931,9 @@ export default function () {
       notkey: resultFilter.notkey,
       buyer: resultFilter.buyer,
       winner: resultFilter.winner,
-      agency: resultFilter.agency
+      agency: resultFilter.agency,
+      ..._expand,
+      _expand: _expand
     }
     inputKeywordsState.value = {
       input: resultFilter.input,
@@ -1318,7 +1324,9 @@ export default function () {
       if(urlKeywords) {
         expandFilter.input = urlKeywords
       }
-      restoreFilter(JSON.parse(params), 'localBack', expandFilter)
+      // 身份切换时候,本地缓存的上次筛选条件有融创等动态拓展筛选条件,但是切换到的身份没有时候,需要移除这些动态筛选条件
+      const jParams = JSON.parse(params)
+      restoreFilter(jParams, 'localBack', expandFilter)
     }
   }
 
@@ -1357,7 +1365,10 @@ export default function () {
           if(isVip.value && !jParams) {
             // 付费用户,优先恢复本地缓存,本地无缓存,则恢复已存筛选第一条数据
             if(Array.isArray(arr) && arr.length > 0) {
-              const expandFilter = {}
+              const _expand = getExpandSearchParams()
+              const expandFilter = {
+                _expand: _expand,
+              }
               if(urlSelectType) {
                 expandFilter.selectType = urlSelectType.split(',')
               }
@@ -1377,6 +1388,16 @@ export default function () {
       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')
@@ -1456,6 +1477,8 @@ export default function () {
   onMounted(() => {
     if(!inBIPropertyIframe) {
       getLastFilter()
+    } else {
+      firstSearch()
     }
     //  P271潜客圈进--客户引流请求
     if(inputKeywordsState.value.input) {
@@ -1534,6 +1557,7 @@ export default function () {
     toggleBlurModeTip,
     doToggleSearchBlurMode,
     recommendCardCircleModel,
-    getWhiteList
+    getWhiteList,
+    storageConfig
   }
 }

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

@@ -33,6 +33,8 @@ export function dataExportActionsModel () {
     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()
@@ -98,6 +100,8 @@ export function dataExportActionsModel () {
   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')

+ 24 - 9
apps/bigmember_pc/src/views/search/bidding/model/modules/filter.js

@@ -39,9 +39,7 @@ export function useSearchFilterModel(conf) {
     // 招标代理机构
     agency: [],
     // 其它动态获取的筛选条件
-    _expand: {
-
-    }
+    _expand: {}
   })
   const filterProperty = ref({
     bidField: 'BIProperty',
@@ -91,6 +89,8 @@ export function useSearchFilterModel(conf) {
   // 格式化招标采购基础筛选条件
   function getFormatApiBaseParams (expendObj = {}) {
     const { publishTime, regionMap, industry, notkey, buyerclass, subtype, _expand } = filterState.value
+    console.log(66666666)
+    console.log(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)
@@ -100,6 +100,7 @@ export function useSearchFilterModel(conf) {
     if(typeof (_expand) === 'object' && Object.keys(_expand).length > 0) {
       for(let key in _expand) {
         expandTag[key] = filterState.value[key]
+        _expand[key] = filterState.value[key]
       }
     }
 
@@ -179,18 +180,31 @@ export function useSearchFilterModel(conf) {
   // 动态更新筛选条件
   function updateFilterBase (keyObj = {}) {
     const {key, value } = keyObj
-
+    const _expand = filterState.value._expand || {}
     if(keyObj && Object.keys(keyObj).length > 0) {
       filterBase.value = Object.assign(filterBase.value, {
         [key]: value
       })
-      filterBase.value._expand = Object.assign({}, {
-        [key]: value
-      })
+      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)
+        filterState.value = Object.assign(filterBase.value, filterState.value, _expandResult)
       }
     } else {
+      // 动态获取存储的筛选条件
+      const expandSearchParams = localStorage.getItem('search_bidding_expandSearchParams')
+      let _expand = {}
+      if(expandSearchParams) {
+        _expand = JSON.parse(expandSearchParams)
+      }
       filterBase.value = Object.assign(filterBase.value, {
         bidField: isBidField ? 'medical' : '',
         // 发布时间
@@ -220,7 +234,8 @@ export function useSearchFilterModel(conf) {
         // 中标企业
         winner: [],
         // 招标代理机构
-        agency: []
+        agency: [],
+        ..._expand
       })
       if(!inBIPropertyIframe) {
         filterState.value = filterBase.value

+ 2 - 2
apps/bigmember_pc/src/views/subscribe/components/key/KeyConfig.vue

@@ -13,7 +13,7 @@
         <img
           @click="showSetKeyDialog = true"
           src="@/assets/images/icon/help.png"
-          class="icon-help-img"
+          class="help-img"
         />
       </div>
       <div class="add-classfily" @click="addClassfilyFn()" v-loading="!setStatus" element-loading-custom-class="prevent-loading">
@@ -206,7 +206,7 @@ export default {
     color: #1d1d1d;
     line-height: 28px;
     border-bottom: 1px solid #ececec;
-    .icon-help-img {
+    .help-img {
       display: inline-block;
       width: 18px;
       height: 18px;

+ 1 - 2
apps/bigmember_pc/vite.config.js

@@ -22,7 +22,6 @@ const baseCDN = {
     'https://cdn-common.jianyu360.com/cdn/lib/echarts/4.8.0/echarts.min.js',
     'https://cdn-common.jianyu360.com/cdn/lib/v-charts/1.19.0/index.min.js',
     // 'https://cdn-common.jianyu360.com/cdn/lib/jquery/3.5.1/jquery.min.js', // 标签上需要添加ignore
-    'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'
   ]
 }
 
@@ -137,7 +136,7 @@ export default defineConfig({
   },
   server: {
     port: 8081,
-    proxy: useServerProxy('web2'),
+    proxy: useServerProxy('web3'),
     host: '0.0.0.0'
   }
 })

+ 8 - 0
apps/mobile/src/router/modules/search.js

@@ -40,6 +40,14 @@ const MiddleSearch = {
       meta: {
         title: '供应搜索'
       }
+    },
+    {
+      path: 'docs',
+      name: 'search-middle-docs',
+      component: () => import('@/views/search/middle/docs/index.vue'),
+      meta: {
+        title: '文档搜索'
+      }
     }
   ]
 }

+ 5 - 1
apps/mobile/src/store/modules/search.js

@@ -20,7 +20,8 @@ export default {
         bidding: [],
         buyer: [],
         company: [],
-        supplier: []
+        supplier: [],
+        docs: []
       },
       { login: true }
     )
@@ -99,6 +100,9 @@ export default {
     },
     supplierSearchHistory(state) {
       return state.searchHistory.supplier
+    },
+    docsSearchHistory(state) {
+      return state.searchHistory.docs
     }
   }
 }

+ 3 - 0
apps/mobile/src/views/search/layout.vue

@@ -158,6 +158,9 @@ export default {
       if (name.indexOf('winner') !== -1) {
         this.topSearch.placeholder = '中标企业名称'
       }
+      if (name.indexOf('docs') !== -1) {
+        this.topSearch.placeholder = '在上亿级文档资料库里搜索文档'
+      }
     }
   }
 }

+ 81 - 0
apps/mobile/src/views/search/middle/docs/index.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="page-search-docs">
+    <history-list
+      v-show="type === 'history' && isLogin"
+      :list="docsSearchHistory"
+      @click="goPage"
+      @delete="deleteList"
+    ></history-list>
+  </div>
+</template>
+
+<script>
+import { HistoryList } from '@/ui'
+import { mapActions, mapGetters } from 'vuex'
+import { openLinkOfOther } from '@/utils'
+
+export default {
+  name: 'SearchMiddleDocs',
+  components: {
+    [HistoryList.name]: HistoryList
+  },
+  inject: {
+    topSearch: {
+      default: () => {}
+    }
+  },
+  data() {
+    return {
+      cacheSearch: '',
+      type: 'history',
+      list: []
+    }
+  },
+  computed: {
+    ...mapGetters('user', ['isLogin']),
+    ...mapGetters('search', ['docsSearchHistory']),
+    getKeys() {
+      return this.topSearch.input.split(' ').filter((v) => v.trim())
+    }
+  },
+  methods: {
+    ...mapActions('search', ['removeHistory', 'setHistory']),
+    clear() {
+      this.type = 'history'
+      this.cacheSearch = ''
+      this.list = []
+    },
+    deleteList() {
+      this.removeHistory({
+        type: 'docs'
+      })
+    },
+    submit() {
+      // 历史记录新增
+      this.setHistory({
+        type: 'docs',
+        item: {
+          label: this.topSearch.input
+        }
+      })
+      if (this.topSearch.input) {
+        return openLinkOfOther(`/page_docs_mobile/search?text=${this.topSearch.input}`)
+      } else {
+        return openLinkOfOther('/page_docs_mobile/home')
+      }
+    },
+    goPage(data) {
+      const keyword = data.label || ''
+      this.topSearch.input = keyword
+      this.$nextTick(() => {
+        // 历史记录新增
+        this.setHistory({
+          type: 'docs',
+          item: data
+        })
+        openLinkOfOther(`/page_docs_mobile/search?text=${data.label}`)
+      })
+    }
+  }
+}
+</script>

+ 11 - 2
apps/mobile/src/views/search/middle/layout.vue

@@ -5,6 +5,7 @@
       v-model="searchType"
       @change="changeType"
       :before-change="beforeTypeChange"
+      :swipe-threshold="4"
     >
       <van-tab
         :title="value.title"
@@ -55,6 +56,9 @@ export default {
         'search-middle-buyer': {
           title: '采购单位搜索'
         },
+        'search-middle-docs': {
+          title: '文档搜索'
+        },
         'search-middle-supplier': {
           title: '供应搜索'
         }
@@ -159,12 +163,14 @@ export default {
 .page-search-tabs {
   ::v-deep {
     .van-tabs__nav {
-      padding: 0 16px;
-      justify-content: space-between;
+      padding: 0 6px;
+      overflow-x: scroll;
+      //justify-content: space-between;
     }
     .van-tab {
       color: $gray_7;
       flex: unset;
+      flex-shrink: 0;
       &.van-tab--active {
         color: $main;
       }
@@ -183,6 +189,9 @@ export default {
       background: linear-gradient(270.04deg, #25beee 0.03%, #2abed1 74.46%);
       border-radius: 1px;
     }
+    .van-tabs__wrap--scrollable .van-tab{
+      padding: 0 6px;
+    }
   }
 }
 .page-search-content {

+ 46 - 0
data/data-models/modules/quick-collect-bid/README.md

@@ -0,0 +1,46 @@
+# useQuickCollectBidModel
+> 标讯收藏业务
+
+::: tip
+该业务模型已集成 API 接口请求。
+:::
+
+## 前置要求
+
+* 使用 `interceptors-data-models.js`,将 service axios 实例中注入
+
+```javascript
+// interceptors-data-models.js
+import service from './interceptors-anti'
+import { injectRequest } from '@jy/data-models'
+
+injectRequest(service)
+```
+
+
+## 业务模型使用
+```javascript
+// 导入标讯收藏业务模型
+import useQuickCollectBidModel from '@jy/data-models/modules/quick-collect/model'
+
+const APIModel = useQuickCollectBidModel({})
+
+```
+
+### useQuickCollectBidModel()
+#### 参数文档
+
+|      参数       |  描述   |                        类型                         |     默认值      |
+|:-------------:|:-----:|:-------------------------------------------------:|:------------:|
+|  type  | 类型 | String |  -  |
+|  getParams  | 类型 | Function |  () => {}  |
+
+#### 返回值
+```
+return {
+  success: success,
+  list: res.data?.list || [],
+  total: res.data?.total || 0,
+  origin: res.data
+}
+```

+ 45 - 0
data/data-models/modules/quick-collect-bid/api/collect-api.js

@@ -0,0 +1,45 @@
+import { useRequest } from '../../../api'
+import qs from 'qs'
+/**
+ * 标讯收藏列表
+ */
+export function ajaxCollectBidList(data) {
+  return useRequest({
+    url: '/publicapply/bidcoll/list',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 获取用户个人标签
+ */
+export function ajaxCollectTagsList() {
+  return useRequest({
+    url: '/publicapply/bidcoll/getLabel',
+    method: 'post'
+  })
+}
+
+/**
+ * 添加标签
+ */
+export function ajaxAddLabel(data) {
+  data = qs.stringify(data)
+  return useRequest({
+    url: '/publicapply/bidcoll/addLabel',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 删除标签
+ */
+export function ajaxDeleteLabel(data) {
+  return useRequest({
+    url: '/publicapply/bidcoll/label',
+    method: 'post',
+    data: data
+  })
+}

+ 1 - 0
data/data-models/modules/quick-collect-bid/index.js

@@ -0,0 +1 @@
+export * from './model'

+ 96 - 0
data/data-models/modules/quick-collect-bid/model/index.js

@@ -0,0 +1,96 @@
+import { ref, computed, toRefs, reactive } from 'vue'
+import { intersection, without } from 'lodash'
+import CollectBidListApi from '../plugins/collect-list'
+import CollectTagsApi from '../plugins/collect-tags'
+
+function useQuickCollectBidModel({ params }) {
+  const useApiModel = new CollectBidListApi(params)
+  const { doQuery } = useApiModel
+  const { list, loading, finished, total } = toRefs(reactive(useApiModel))
+  const selectIds = ref([])
+  const listIds = computed(() => {
+    return list.value.map((item) => item.id || item._id)
+  })
+  const searchResultCount = computed(() => {
+    return total.value > 100000000 ? '超过' + (total.value / 100000000).toFixed(1) + '亿' : total.value
+  })
+
+  const isSelectSomeCheckbox = computed(() => {
+    return selectIds.value.length > 0
+  })
+  const selectCheckboxCount = computed(() => {
+    return selectIds.value.length
+  })
+
+  const isSelectListAllCheckbox = computed(() => {
+    const result = intersection(listIds.value, selectIds.value)
+    if (listIds.value.length === 0) {
+      return false
+    }
+    return result.length === list.value.length
+  })
+
+  /**
+   * 单个item选中、取消操作
+   * @param id
+   * @param [type] - 选中 true \ 取消 false
+   */
+  function doToggleItemSelection(id, type) {
+    if (typeof type !== 'boolean') {
+      type = !selectIds.value.includes(id)
+    }
+    selectIds.value = without(selectIds.value, id)
+    if (type) {
+      selectIds.value.push(id)
+    }
+  }
+
+  /**
+   * 全选当前列表操作
+   * @param [type] - 选中 true \ 取消 false
+   */
+  function doToggleListSelection(type = true) {
+    // 移除ids
+    selectIds.value = without(selectIds.value, ...listIds.value)
+    if (type) {
+      selectIds.value = selectIds.value.concat(listIds.value)
+    }
+  }
+
+  /**
+   * 清空所有选择项
+   */
+  function doClearAllSelection() {
+    selectIds.value = []
+  }
+
+  /**
+   * 获取用户个人标签
+   */
+  const useTagsListApi = new CollectTagsApi()
+  const { getLabelQuery, addLabelQuery, deleteLabelQuery } = useTagsListApi
+  const { tagsList } = toRefs(reactive(useTagsListApi))
+
+  return {
+    list,
+    total,
+    loading,
+    finished,
+    selectIds,
+    listIds,
+    searchResultCount,
+    isSelectSomeCheckbox,
+    selectCheckboxCount,
+    isSelectListAllCheckbox,
+    doToggleItemSelection,
+    doToggleListSelection,
+    doClearAllSelection,
+    doQuery,
+    getLabelQuery,
+    addLabelQuery,
+    deleteLabelQuery,
+    tagsList
+  }
+}
+
+export default useQuickCollectBidModel

+ 46 - 0
data/data-models/modules/quick-collect-bid/plugins/base.js

@@ -0,0 +1,46 @@
+class CollectBidListApiBase {
+  constructor(config = {}) {
+    this.list = []
+    this.loading = false
+    this.finished = false
+    this.total = -1
+    this._getParams = config.getParams || (() => {})
+    // 函数 Hooks
+    this.doQuery = this.runQuery.bind(this)
+  }
+
+  /**
+   * 统一查询 API 入口
+   * @param params - 请求参数
+   * @returns {Promise<{success: boolean, total: number, list: []}>}
+   */
+
+  async runQuery(params) {
+    this.loading = true
+    this.finished = false
+    this.list = []
+    const query = Object.assign({}, params, this._getParams(params))
+    const result = await this.ajaxQuery(query)
+    if (result.success) {
+      this.list = result.list
+      this.total = result.total
+    } else {
+      this.list = []
+      this.total = -1
+    }
+    this.finished = true
+    this.loading = false
+    return result
+  }
+
+  // 需要覆写的API处理逻辑
+  async ajaxQuery(params) {
+    return {
+      success: true,
+      list: [],
+      total: -1
+    }
+  }
+}
+
+export default CollectBidListApiBase

+ 23 - 0
data/data-models/modules/quick-collect-bid/plugins/collect-list.js

@@ -0,0 +1,23 @@
+import CollectBidListApiBase from './base'
+import { ajaxCollectBidList } from '../api/collect-api'
+
+export default class CollectBidListApi extends CollectBidListApiBase {
+  constructor(config) {
+    super(config)
+  }
+
+  /**
+   * 覆写请求
+   */
+  async ajaxQuery(params) {
+    return ajaxCollectBidList(params).then((res) => {
+      let success = res?.error_code === 0
+      return {
+        success: success,
+        list: res.data?.res || [],
+        total: res.data?.count || 0,
+        origin: res.data
+      }
+    })
+  }
+}

+ 82 - 0
data/data-models/modules/quick-collect-bid/plugins/collect-tags.js

@@ -0,0 +1,82 @@
+import { isArray } from 'lodash'
+import { ajaxCollectTagsList, ajaxAddLabel, ajaxDeleteLabel } from '../api/collect-api'
+
+class CollectTagsApi {
+  constructor(config = {}) {
+    this.loading = false
+    this.finished = false
+    this.tagsList = []
+    this.getLabelQuery = this.runQuery.bind(this)
+  }
+
+  /**
+   * 查询 API 入口
+   * @returns {Promise<{success: boolean, tagsList: []}>}
+   */
+
+  async runQuery() {
+    this.loading = true
+    this.finished = false
+    this.tagsList = []
+    const result = await this.ajaxQuery()
+    if (result.success) {
+      this.tagsList = result.list && isArray(result.list) ? result.list.map(item => {
+        return {
+          name: item.lanme,
+          value: item.lid,
+          count: item.count
+        }
+      }).reverse() : []
+    } else {
+      this.tagsList = []
+    }
+    this.finished = true
+    this.loading = false
+    return result
+  }
+
+  async ajaxQuery() {
+    return ajaxCollectTagsList().then((res) => {
+      let success = res?.error_code === 0
+
+      return {
+        success: success,
+        list: res.data || []
+      }
+    })
+  }
+
+  /**
+   * 添加用户标签
+   */
+  async addLabelQuery(params) {
+    return ajaxAddLabel(params).then((res) => {
+      let success = res?.error_code === 0
+      let msg = res?.error_msg
+
+      return {
+        success: success,
+        data: res.data,
+        msg
+      }
+    })
+  }
+
+  /**
+   * 删除用户标签
+   */
+  async deleteLabelQuery(params) {
+    return ajaxDeleteLabel(params).then((res) => {
+      let success = res?.error_code === 0
+      let msg = res?.error_msg
+
+      return {
+        success: success,
+        data: res.data,
+        msg
+      }
+    })
+  }
+}
+
+export default CollectTagsApi

+ 31 - 0
data/data-models/modules/quick-search-history/api/search-history.js

@@ -0,0 +1,31 @@
+import { useRequest } from '../../../api'
+import qs from 'qs'
+
+// 获取搜索历史 
+// type: 1-标讯搜索历史记录  2-企业历史搜索 3-企业历史浏览 4-采购单位历史搜索 5-采购单位历史浏览
+export function ajaxGetSearchHistory(data) {
+  return useRequest({
+    url: `/publicapply/history/get`,
+    method: 'post',
+    data: qs.stringify(data)
+  })
+}
+
+// 清空搜索历史 
+// type: 1-标讯搜索历史记录  2-企业历史搜索 3-企业历史浏览 4-采购单位历史搜索 5-采购单位历史浏览
+export function ajaxClearSearchHistory(data) {
+  return useRequest({
+    url: `/publicapply/history/del`,
+    method: 'post',
+    data: qs.stringify(data)
+  })
+}
+
+// 保存浏览记录(企业画像、采购单位画像)
+export function ajaxSaveViewHistory(data) {
+  return useRequest({
+    url: `/publicapply/history/savePortrait`,
+    method: 'post',
+    data: qs.stringify(data)
+  })
+}

+ 18 - 0
data/data-models/modules/quick-search-history/model/index.js

@@ -0,0 +1,18 @@
+import { toRefs, reactive } from 'vue'
+import SearchHistoryBaseApi from '../plugins/base'
+
+function useSearchHistoryModel(config) {
+  const useApiModel = new SearchHistoryBaseApi(config)
+
+  const { getHistoryQuery, clearHistoryQuery, saveViewHistoryQuery } = useApiModel
+  const { searchHistoryList } = toRefs(reactive(useApiModel))
+
+  return {
+    searchHistoryList,
+    getHistoryQuery,
+    clearHistoryQuery,
+    saveViewHistoryQuery
+  }
+}
+
+export default useSearchHistoryModel

+ 82 - 0
data/data-models/modules/quick-search-history/plugins/base.js

@@ -0,0 +1,82 @@
+import { ajaxGetSearchHistory, ajaxClearSearchHistory, ajaxSaveViewHistory  } from '../api/search-history'
+
+class SearchHistoryBaseApi {
+  constructor(config = {}) {
+    // 搜索历史type
+    this.searchType = [1, 2, 4]
+    // 浏览历史type
+    this.browseType = [3, 5]
+    // 历史搜索列表
+    this.searchHistoryList = []
+    // 历史浏览记录列表
+    this.browseHistoryList = []
+    this._getParams = config.getParams || (() => {})
+    // 函数 Hooks
+    this.getHistoryQuery = this.runQuery.bind(this)
+    this.clearHistoryQuery = this.ajaxClearQuery.bind(this)
+    this.saveViewHistoryQuery = this.ajaxSaveViewQuery.bind(this)
+  }
+
+  /**
+   * 统一查询 API 入口
+   * @param params - 请求参数
+   * @returns {Promise<{success: boolean, search: [], browse: []}>}
+   */
+
+  async runQuery(params) {
+    this.searchHistoryList = []
+    const query = Object.assign({}, params, this._getParams(params))
+    const result = await this.ajaxQuery(query)
+    if (result.success) {
+      this.searchHistoryList = result.search
+      this.browseHistoryList = result.browse
+    } else {
+      this.searchHistoryList = []
+      this.browseHistoryList = []
+    }
+    return result
+  }
+
+  // 需要覆写的API处理逻辑
+  async ajaxQuery(params) {
+    return ajaxGetSearchHistory(params).then((res) => {
+      let success = res?.error_code === 0
+
+      return {
+        success: success,
+        search: res?.data?.search ? res.data.search.reverse() : [],
+        browse: res?.data?.browse ? res.data.browse.reverse() : []
+      }
+    })
+  }
+
+  // 清除搜索历史
+  async ajaxClearQuery(params) {
+    const query = Object.assign({}, params, this._getParams(params))
+    return ajaxClearSearchHistory(query).then((res) => {
+      let success = res?.error_code === 0
+
+      if (success && res.data) {
+        // 清除成功再次获取历史列表
+        this.getHistoryQuery(params)
+      }
+      return {
+        success: success && res?.data
+      }
+    })
+  }
+
+  // 保存企业浏览记录
+  async ajaxSaveViewQuery(params) {
+    const query = Object.assign({}, params, this._getParams(params))
+    return ajaxSaveViewHistory(query).then((res) => {
+      let success = res?.error_code === 0
+
+      return {
+        success: success && res?.data
+      }
+    })
+  }
+}
+
+export default SearchHistoryBaseApi

+ 2 - 2
packages/pc-ui/src/packages/keywords/index.vue

@@ -19,7 +19,7 @@
           <img
             @click="showMatchTipDialog"
             src="../../assets/images/icon/help.png"
-            class="icon-help-img"
+            class="help-img"
           />
         </p>
       </div>
@@ -465,7 +465,7 @@ export default {
     color: #1d1d1d;
     line-height: 28px;
     border-bottom: 1px solid #ececec;
-    .icon-help-img {
+    .help-img {
       display: inline-block;
       width: 18px;
       height: 18px;

+ 25 - 37
pnpm-lock.yaml

@@ -72,6 +72,9 @@ importers:
       '@jy/util':
         specifier: workspace:^
         version: link:../../packages/util
+      '@sentry/vue':
+        specifier: ^7.64.0
+        version: 7.64.0(vue@2.7.16)
       dayjs:
         specifier: ^1.11.7
         version: 1.11.8
@@ -4925,6 +4928,7 @@ packages:
     resolution: {integrity: sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
     requiresBuild: true
     dev: true
     optional: true
@@ -4942,6 +4946,7 @@ packages:
     resolution: {integrity: sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
     requiresBuild: true
     dev: true
     optional: true
@@ -4959,6 +4964,7 @@ packages:
     resolution: {integrity: sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==}
     cpu: [ppc64le]
     os: [linux]
+    libc: [glibc]
     requiresBuild: true
     dev: true
     optional: true
@@ -4967,6 +4973,7 @@ packages:
     resolution: {integrity: sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==}
     cpu: [riscv64]
     os: [linux]
+    libc: [glibc]
     requiresBuild: true
     dev: true
     optional: true
@@ -4975,6 +4982,7 @@ packages:
     resolution: {integrity: sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==}
     cpu: [s390x]
     os: [linux]
+    libc: [glibc]
     requiresBuild: true
     dev: true
     optional: true
@@ -4983,6 +4991,7 @@ packages:
     resolution: {integrity: sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
     requiresBuild: true
     dev: true
     optional: true
@@ -5000,6 +5009,7 @@ packages:
     resolution: {integrity: sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
     requiresBuild: true
     dev: true
     optional: true
@@ -5804,7 +5814,7 @@ packages:
       regenerator-runtime: 0.13.11
       systemjs: 6.14.3
       terser: 5.19.2
-      vite: 4.3.9(sass@1.71.1)(terser@5.19.2)
+      vite: 4.3.9(less@4.1.3)(sass@1.63.2)(terser@5.19.2)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -5847,7 +5857,7 @@ packages:
       vite: ^3.0.0 || ^4.0.0
       vue: ^2.7.0-0
     dependencies:
-      vite: 4.3.9(sass@1.71.1)(terser@5.19.2)
+      vite: 4.3.9(less@4.1.3)(sass@1.63.2)(terser@5.19.2)
       vue: 2.7.16
     dev: true
 
@@ -6239,7 +6249,7 @@ packages:
       '@vue/cli-service': ^3.0.0 || ^4.0.0 || ^5.0.0-0
       eslint: '>=7.5.0'
     dependencies:
-      '@vue/cli-service': 5.0.1(@babel/core@7.22.9)(sass-loader@12.6.0)(vue-template-compiler@2.7.14)(vue@2.7.14)
+      '@vue/cli-service': 5.0.1(@babel/core@7.22.9)(lodash@4.17.21)(sass-loader@12.0.0)(vue-template-compiler@2.6.14)(vue@2.7.16)
       '@vue/cli-shared-utils': 5.0.8
       eslint: 7.32.0
       eslint-webpack-plugin: 3.2.0(eslint@7.32.0)(webpack@5.88.2)
@@ -6259,7 +6269,7 @@ packages:
     peerDependencies:
       '@vue/cli-service': ^3.0.0 || ^4.0.0 || ^5.0.0-0
     dependencies:
-      '@vue/cli-service': 5.0.1(@babel/core@7.22.9)(lodash@4.17.21)(sass-loader@12.0.0)(vue-template-compiler@2.6.14)(vue@2.7.14)
+      '@vue/cli-service': 5.0.1(@babel/core@7.22.9)(lodash@4.17.21)(sass-loader@12.0.0)(vue-template-compiler@2.6.14)(vue@2.7.16)
       '@vue/cli-shared-utils': 5.0.8
     transitivePeerDependencies:
       - encoding
@@ -6270,7 +6280,7 @@ packages:
     peerDependencies:
       '@vue/cli-service': ^3.0.0 || ^4.0.0 || ^5.0.0-0
     dependencies:
-      '@vue/cli-service': 5.0.1(@babel/core@7.22.9)(sass-loader@12.6.0)(vue-template-compiler@2.7.14)(vue@2.7.14)
+      '@vue/cli-service': 5.0.1(@babel/core@7.22.9)(lodash@4.17.21)(sass-loader@12.0.0)(vue-template-compiler@2.6.14)(vue@2.7.16)
       '@vue/cli-shared-utils': 5.0.8
     transitivePeerDependencies:
       - encoding
@@ -6281,7 +6291,7 @@ packages:
     peerDependencies:
       '@vue/cli-service': ^3.0.0 || ^4.0.0 || ^5.0.0-0
     dependencies:
-      '@vue/cli-service': 5.0.1(@babel/core@7.22.9)(lodash@4.17.21)(sass-loader@12.0.0)(vue-template-compiler@2.6.14)(vue@2.7.14)
+      '@vue/cli-service': 5.0.1(@babel/core@7.22.9)(lodash@4.17.21)(sass-loader@12.0.0)(vue-template-compiler@2.6.14)(vue@2.7.16)
     dev: true
 
   /@vue/cli-plugin-vuex@5.0.8(@vue/cli-service@5.0.1):
@@ -6289,7 +6299,7 @@ packages:
     peerDependencies:
       '@vue/cli-service': ^3.0.0 || ^4.0.0 || ^5.0.0-0
     dependencies:
-      '@vue/cli-service': 5.0.1(@babel/core@7.22.9)(sass-loader@12.6.0)(vue-template-compiler@2.7.14)(vue@2.7.14)
+      '@vue/cli-service': 5.0.1(@babel/core@7.22.9)(lodash@4.17.21)(sass-loader@12.0.0)(vue-template-compiler@2.6.14)(vue@2.7.16)
     dev: true
 
   /@vue/cli-service@5.0.1(@babel/core@7.22.9)(lodash@4.17.21)(sass-loader@12.0.0)(vue-template-compiler@2.6.14)(vue@2.7.14):
@@ -7760,8 +7770,8 @@ packages:
     peerDependencies:
       postcss: ^8.1.0
     dependencies:
-      browserslist: 4.23.0
-      caniuse-lite: 1.0.30001591
+      browserslist: 4.21.9
+      caniuse-lite: 1.0.30001517
       fraction.js: 4.2.0
       normalize-range: 0.1.2
       picocolors: 1.0.0
@@ -15688,7 +15698,7 @@ packages:
       vfile-message: 4.0.2
     dev: true
 
-  /vite-node@0.34.6(@types/node@20.4.5):
+  /vite-node@0.34.6(@types/node@20.4.5)(sass@1.63.2):
     resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==}
     engines: {node: '>=v14.18.0'}
     hasBin: true
@@ -15698,28 +15708,6 @@ packages:
       mlly: 1.4.2
       pathe: 1.1.1
       picocolors: 1.0.0
-      vite: 4.5.1(@types/node@20.4.5)(sass@1.71.1)(terser@5.19.2)
-    transitivePeerDependencies:
-      - '@types/node'
-      - less
-      - lightningcss
-      - sass
-      - stylus
-      - sugarss
-      - supports-color
-      - terser
-    dev: true
-
-  /vite-node@0.34.6(@types/node@20.4.5)(sass@1.63.2):
-    resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==}
-    engines: {node: '>=v14.18.0'}
-    hasBin: true
-    dependencies:
-      cac: 6.7.14
-      debug: 4.3.4
-      mlly: 1.6.1
-      pathe: 1.1.2
-      picocolors: 1.0.0
       vite: 4.5.1(@types/node@20.4.5)(sass@1.63.2)
     transitivePeerDependencies:
       - '@types/node'
@@ -15739,8 +15727,8 @@ packages:
     dependencies:
       cac: 6.7.14
       debug: 4.3.4
-      mlly: 1.6.1
-      pathe: 1.1.2
+      mlly: 1.4.2
+      pathe: 1.1.1
       picocolors: 1.0.0
       vite: 4.5.1(@types/node@20.4.5)(sass@1.71.1)(terser@5.19.2)
     transitivePeerDependencies:
@@ -15791,7 +15779,7 @@ packages:
       '@types/eslint': 8.44.1
       eslint: 8.37.0
       rollup: 2.79.1
-      vite: 4.3.9(sass@1.71.1)(terser@5.19.2)
+      vite: 4.3.9(less@4.1.3)(sass@1.63.2)(terser@5.19.2)
     dev: true
 
   /vite-plugin-externals@0.6.2(vite@4.3.9):
@@ -15804,7 +15792,7 @@ packages:
       es-module-lexer: 0.4.1
       fs-extra: 10.1.0
       magic-string: 0.25.9
-      vite: 4.3.9(sass@1.71.1)(terser@5.19.2)
+      vite: 4.3.9(less@4.1.3)(sass@1.63.2)(terser@5.19.2)
     dev: true
 
   /vite-plugin-html-redirect@1.0.4:
@@ -16181,7 +16169,7 @@ packages:
       tinybench: 2.5.1
       tinypool: 0.7.0
       vite: 4.5.1(@types/node@20.4.5)(sass@1.71.1)(terser@5.19.2)
-      vite-node: 0.34.6(@types/node@20.4.5)
+      vite-node: 0.34.6(@types/node@20.4.5)(sass@1.71.1)(terser@5.19.2)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - less