瀏覽代碼

feat: 详细信息列表相关组件封装

cuiyalong 2 年之前
父節點
當前提交
c755b3abdf

+ 227 - 0
src/components/article-item/ProjectItem.vue

@@ -0,0 +1,227 @@
+<template>
+  <div class="project-item">
+    <div class="project-item-hd">
+      <div class="project-item-hd-left pointer" @click="clickTitle">
+        <div class="project-name ellipsis" v-html="calcTitle"></div>
+      </div>
+      <div class="project-item-hd-right">
+        <slot name="hd-right"></slot>
+      </div>
+    </div>
+    <div class="project-item-bd">
+      <div class="tag-list">
+        <span
+          v-for="(tag, index) in tagList"
+          :key="index"
+          class="tag">
+          {{ tag.value }}
+        </span>
+      </div>
+      <div class="description">
+        <p class="p-bd-d-item-list" v-if="buyer || budget">
+          <span class="p-bd-d-item" v-if="buyer">
+            <i class="p-bd-d-item-label">采购单位:</i>
+            <el-link
+              :underline="false"
+              type="primary"
+              @click.prevent.stop="goPortrayal(buyer, 'buyer')">{{ buyer }}</el-link>
+          </span>
+          <span class="p-bd-d-item">
+            <i class="p-bd-d-item-label">预算金额:</i>
+            <em>{{ budget ? formatMoney(budget) : '--' }}</em>
+          </span>
+        </p>
+        <p class="p-bd-d-item-list" v-if="winners.length">
+          <span class="p-bd-d-item">
+            <i class="p-bd-d-item-label">中标单位:</i>
+            <el-link
+              data-dot=","
+              :class="{ 'dot-suffix': i !== winners.length - 1 }"
+              :underline="false"
+              :disabled="!w.id"
+              type="primary"
+              v-for="(w, i) in winners"
+              :key="i"
+              @click.prevent.stop="goPortrayal(w.id, 'winner')">
+              {{ w.name }}
+            </el-link>
+          </span>
+          <span class="p-bd-d-item">
+            <i class="p-bd-d-item-label">中标金额:</i>
+            <em>{{ bidAmount ? formatMoney(bidAmount) : '--' }}</em>
+          </span>
+        </p>
+      </div>
+    </div>
+    <div class="project-item-ft"></div>
+  </div>
+</template>
+
+<script>
+import { Link } from 'element-ui'
+import { dateFromNow, replaceKeyword } from '@/utils/'
+import { formatMoney } from '@/utils/'
+
+export default {
+  name: 'ProjectItem',
+  components: {
+    [Link.name]: Link
+  },
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    // 匹配高亮的关键词
+    matchKeys: {
+      type: Array,
+      default () {
+        return [
+          // 'xxx'
+        ]
+      }
+    },
+    tagList: {
+      type: Array,
+      default () {
+        return [
+          // {
+          //   value: '北京'
+          // },
+          // {
+          //   value: '成交'
+          // }
+        ]
+      }
+    },
+    budget: {
+      type: Number,
+      default: 0
+    },
+    bidAmount: {
+      type: Number,
+      default: 0
+    },
+    buyer: {
+      type: String,
+      default: '内蒙古中弘紫晶科技有限责任公司'
+    },
+    winners: {
+      type: Array,
+      default () {
+        return [
+          // {
+          //   name: '中国电子科技集团公司电子科学研究院',
+          //   id: 'x1'
+          // },
+          // {
+          //   name: '广州赛宝联睿信息科技有限公司',
+          //   id: 'x2'
+          // }
+        ]
+      }
+    }
+  },
+  computed: {
+    calcTitle () {
+      const hightLightedTitle = replaceKeyword(this.title, this.matchKeys, ['<span class="highlight-text">', '</span>'])
+      return `${hightLightedTitle}`
+    }
+  },
+  methods: {
+    dateFromNow,
+    formatMoney,
+    clickTitle () {
+      this.$emit('clickTitle')
+    },
+    goPortrayal (id, type) {
+      let goLink = ''
+      if (type === 'winner') {
+        goLink = `/ent_portrait/${id}`
+      } else if (type === 'buyer') {
+        goLink = `/unit_portrayal/${id}`
+      }
+      if (id && goLink) {
+        window.open(`/swordfish/page_big_pc${goLink}`)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.project-item {
+  padding: 16px 20px;
+  color: #1D1D1D;
+  &-hd {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  &-bd {
+    margin-top: 8px;
+  }
+
+  &:hover {
+    background-color: #f7f9fc;
+  }
+}
+.project-name {
+  font-size: 16px;
+  line-height: 24px;
+}
+.project-item-hd-left {
+  flex: 1;
+  max-width: 600px;
+}
+.project-item-hd-right {
+  font-size: 12px;
+  line-height: 18px;
+  color: #999;
+}
+
+.tag-list {
+  display: flex;
+  align-items: center;
+  .tag {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 1px 6px;
+    color: #686868;
+    font-size: 12px;
+    line-height: 18px;
+    background: #f5f5fb;
+    border-radius: 4px;
+    border: 1px solid #ececec;
+    &:not(:last-of-type) {
+      margin-right: 8px;
+    }
+  }
+}
+
+.description {
+  margin-top: 16px;
+  font-size: 14px;
+  line-height: 22px;
+}
+
+.p-bd-d-item {
+  margin-right: 20px;
+}
+.p-bd-d-item-label {
+  color: #686868;
+}
+
+.dot-suffix {
+  position: relative;
+  padding-right: 16px;
+  &::after {
+    content: attr(data-dot);
+    position: absolute;
+    right: 0;
+    top: 50%;
+    transform: translate(0,-50%);
+  }
+}
+</style>

+ 154 - 0
src/components/common/AssociationInput.vue

@@ -0,0 +1,154 @@
+<template>
+  <!-- 联想组件 -->
+  <div class="association-container">
+    <el-input
+      class="association-input"
+      :value="value"
+      :placeholder="placeholder"
+      :suffix-icon="suffixIcon"
+      :clearable="clearable"
+      @focus="preSearch.focus=true"
+      @blur="onBlur"
+      @input="onInput"
+    ></el-input>
+    <div class="pre-search-list" v-show="preSearchListShow" @mouseout="preSearch.hover=false" @mouseover="preSearch.hover=true">
+      <div
+        class="pre-search-item ellipsis"
+        v-for="(item,index) in preSearch.list"
+        :key="index"
+        @click="selectSearchItem(item)"
+        >{{ item.value }}</div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Input } from 'element-ui'
+import { debounce } from 'lodash'
+
+export default {
+  name: 'AssociationInput',
+  components: {
+    [Input.name]: Input
+  },
+  props: {
+    value: {
+      type: String,
+      default: ''
+    },
+    fetchAsync: {
+      type: Function
+    },
+    clearable: {
+      type: Boolean,
+      default: false
+    },
+    suffixIcon: {
+      type: String,
+      default: 'el-icon-search'
+    },
+    placeholder: {
+      type: String,
+      default: ''
+    }
+  },
+  data () {
+    return {
+      preSearch: {
+        hover: false,
+        focus: false,
+        list: []
+      }
+    }
+  },
+  computed: {
+    preSearchListShow () {
+      return this.value.trim().length >= 2 && this.preSearch.list.length && (this.preSearch.focus || this.preSearch.hover)
+    }
+  },
+  methods: {
+    onInput (e) {
+      this.$emit('input', e)
+      this.preSearchList(e)
+    },
+    onBlur (e) {
+      this.$emit('blur', e)
+      this.$nextTick(() => {
+        this.preSearch.focus = false
+      })
+    },
+    preSearchList: debounce(async function (e) {
+      const params = {
+        value: this.value
+      }
+      if (params.value.length < 2) return
+
+      let list = []
+      if (this.fetchAsync) {
+        try {
+          const cbArr = await this.fetchAsync(params)
+          if (Array.isArray(cbArr)) {
+            list = cbArr
+          }
+        } catch (error) {
+          console.log(error)
+        }
+      }
+      this.preSearch.list = list
+    }, 300),
+    selectSearchItem (item) {
+      this.$emit('select', item)
+      // 选择后马上关闭备选列表
+      this.$nextTick(() => {
+        this.preSearch.focus = false
+        this.preSearch.hover = false
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+  .el-input__inner {
+    height: 30px;
+    line-height: 30px;
+    border-color: #e0e0e0;
+  }
+  .el-input__icon {
+    line-height: 30px;
+  }
+}
+.association-input {
+  width: 326px;
+}
+.association-container {
+  position: relative;
+  display: inline-block;
+}
+.pre-search-list {
+  padding: 10px 0;
+  position: absolute;
+  z-index: 6;
+  top: 36px;
+  width: 100%;
+  background: #fff;
+  box-shadow: 0 0 10px rgb(0,0,0,0.1);
+  border-radius: 4px;
+  overflow: hidden;
+}
+.pre-search-item {
+  padding: 0 20px;
+  width: 100%;
+  font-size: 14px;
+  line-height: 34px;
+  color: #606266;
+  box-sizing: border-box;
+  transition: all .3s;
+  cursor: pointer;
+}
+.pre-search-item:hover {
+  color: #1d1d1d;
+  background-color: #ececec;
+}
+</style>

+ 400 - 0
src/components/report-data/ProjectTopTable.vue

@@ -0,0 +1,400 @@
+<template>
+  <!-- 项目金额排行榜 -->
+  <div class="proRank project-top-30">
+    <div class="proRank_head">
+      {{ title }}
+    </div>
+    <div class="proRank_table">
+      <el-table
+        v-if="type=='amount'"
+        :data="renderList"
+        header-cell-class-name="header-cell-class"
+        border
+        style="width: 100%">
+        <el-table-column
+          prop="index"
+          label="序号"
+          header-align="center"
+          align="center"
+          width="60">
+          <template slot-scope="scope">
+            <TopNumber :num="scope.row.index" />
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="projectname"
+          label="项目名称"
+          header-align="center">
+          <template slot-scope="scope">
+            <el-link :underline="false" class="ellipsis-5" @click="toProjectDetail(scope.row.sourceinfoid)">{{ scope.row.projectname || '--' }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="buyer"
+          header-align="center"
+          label="采购单位">
+          <template slot-scope="scope">
+            <el-link v-if="scope.row.buyer" :underline="false" @click="toOtherPage(scope.row.buyer, 'buyer')">{{ scope.row.buyer || '--' }}</el-link>
+            <template v-else>{{ scope.row.buyer || '--' }}</template>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="winner"
+          header-align="center"
+          label="中标单位">
+          <template slot-scope="scope">
+            <template
+              v-for="(w, i) in formatWinnerList(scope.row.winner, scope.row.id)"
+            >
+              <el-link
+                :underline="false"
+                class="inline"
+                :key="w.id"
+                :disabled="!w.id"
+                @click="toOtherPage(w.id, 'winner')">
+                {{ w.name || '--' }}
+              </el-link>
+              <template v-if="i !== formatWinnerList(scope.row.winner, scope.row.id).length - 1">,</template>
+            </template>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="budget"
+          width="90"
+          header-align="center"
+          align="right"
+          label="预算金额(万元)">
+          <template slot-scope="scope">
+            {{ scope.row.budget || '--' }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="bidamount"
+          width="90"
+          header-align="center"
+          align="right"
+          label="中标金额(万元)">
+          <template slot-scope="scope">
+            {{ scope.row.bidamount || '--' }}
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-table
+        v-if="type=='bidamount'"
+        :data="renderList"
+        header-cell-class-name="header-cell-class"
+        border
+        style="width: 100%">
+        <el-table-column
+          prop="index"
+          label="序号"
+          header-align="center"
+          align="center"
+          width="60">
+          <template slot-scope="scope">
+            <TopNumber :num="scope.row.index" />
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="projectname"
+          label="项目名称"
+          header-align="center">
+          <template slot-scope="scope">
+            <el-link :underline="false" @click="toProjectDetail(scope.row.projectId)">{{ scope.row.projectname || '--' }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          label="所在地">
+          <template slot-scope="scope">
+            {{scope.row.area || '--'}} {{ scope.row.city }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          width="90"
+          header-align="center"
+          align="right"
+          label="项目规模(万元)">
+          <template slot-scope="scope">
+            {{ scope.row.bidamount || '--' }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="bidamount"
+          width="90"
+          header-align="center"
+          align="right"
+          label="中标金额(万元)">
+          <template slot-scope="scope">
+            {{ scope.row.bidamount || '--' }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="buyer"
+          header-align="center"
+          label="采购单位">
+          <template slot-scope="scope">
+            <el-link v-if="scope.row.buyer" :underline="false" @click="toOtherPage(scope.row.buyer, 'buyer')">{{ scope.row.buyer || '--' }}</el-link>
+            <template v-else>{{ scope.row.buyer || '--' }}</template>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="winner"
+          header-align="center"
+          label="中标单位">
+          <template slot-scope="scope">
+            <el-link
+              :underline="false"
+              :disabled="!scope.row.winner_id"
+              @click="toOtherPage(scope.row.winner_id, 'winner')">{{ scope.row.winner || '--' }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="winner"
+          header-align="center"
+          label="成交时间">
+          <template slot-scope="scope">
+            {{ scope.row.jgtime || '--' }}
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-table
+        v-if="type=='followEnt'"
+        :data="renderList"
+        header-cell-class-name="header-cell-class"
+        border
+        style="width: 100%">
+        <el-table-column
+          prop="index"
+          label="序号"
+          header-align="center"
+          align="center"
+          width="60">
+          <template slot-scope="scope">
+            <TopNumber :num="scope.row.index" />
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="企业名称"
+          header-align="center">
+          <template slot-scope="scope">
+            <el-link :underline="false" @click="toOtherPage(scope.row.id, 'winner')">{{ scope.row.entname || '--' }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          width="114"
+          label="注册省市">
+          <template slot-scope="scope">
+            {{ scope.row.area || '--'}} {{ scope.row.city }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          label="中标项目">
+          <template slot-scope="scope">
+            <el-link :underline="false" @click="toProjectDetail(scope.row.sourceinfoid)">{{ scope.row.projectname || '--' }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          width="128"
+          align="center"
+          label="中标日期">
+          <template slot-scope="scope">
+            {{ dateFormatter(scope.row.jgtime*1000, 'yyyy-MM-dd') || '--' }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="bidamount"
+          width="124"
+          header-align="center"
+          align="right"
+          label="中标金额(万元)">
+          <template slot-scope="scope">
+            {{ scope.row.bidamount || '--' }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <div class="proRank_footer">
+      <el-pagination
+        background
+        popper-class="pagination-custom-select"
+        layout="prev, pager, next, sizes, jumper"
+        :page-size="listState.pageSize"
+        :current-page="listState.pageNum"
+        @current-change="onCurrentChange"
+        :total="listState.total"
+        :page-sizes="[5, 10, 50, 100]"
+        :show-confirm-btn="true"
+        @size-change="onSizeChange"
+        >
+      </el-pagination>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Table, Link, TableColumn, Pagination } from 'element-ui'
+import TopNumber from './TopNumber.vue'
+import { dateFormatter } from '@/utils/'
+
+export default {
+  name: 'projectRankTopTable',
+  components: {
+    TopNumber,
+    [Table.name]: Table,
+    [Link.name]: Link,
+    [TableColumn.name]: TableColumn,
+    [Pagination.name]: Pagination
+  },
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    type: {
+      type: String,
+      default: '',
+      validator (value) {
+        return ['amount', 'bidamount', 'winner', 'followEnt'].includes(value)
+      }
+    },
+    // 数据一次性传30条。前端分页
+    data: {
+      type: Array,
+      default () {
+        return []
+      }
+    }
+  },
+  data () {
+    return {
+      listState: {
+        pageNum: 1,
+        pageSize: 10,
+        total: 0
+      }
+    }
+  },
+  computed: {
+    dataList () {
+      if (Array.isArray(this.data)) {
+        return this.data.map((d, index) => {
+          return {
+            ...d,
+            index: index + 1
+          }
+        })
+      } else {
+        return []
+      }
+    },
+    renderList () {
+      if (Array.isArray(this.dataList)) {
+        const { pageNum, pageSize } = this.listState
+        const start = (pageNum - 1) * pageSize
+        const end = pageNum * pageSize
+        return this.dataList.slice(start, end)
+      } else {
+        return []
+      }
+    }
+  },
+  watch: {
+    data: {
+      immediate: true,
+      handler (n) {
+        if (Array.isArray(n)) {
+          this.listState.total = n.length
+        } else {
+          this.listState.total = 0
+        }
+      }
+    }
+  },
+  methods: {
+    dateFormatter,
+    resetListState () {
+      Object.assign(this.listState, this.$options.data().listState)
+    },
+    onSizeChange (p) {
+      this.listState.pageSize = p
+    },
+    onCurrentChange (p) {
+      this.listState.pageNum = p
+    },
+    toProjectDetail (id) {
+      if (id) {
+        window.open(`/swordfish/page_big_pc/pro_follow_detail?sid=${id}`)
+      }
+    },
+    formatWinnerList (winners, ids) {
+      let winnerArr = []
+      let idArr = []
+      if (winners) {
+        winnerArr = winners.split(',')
+      }
+      if (ids) {
+        idArr = ids.split(',')
+      }
+      return winnerArr.map((w, index) => {
+        return {
+          name: w,
+          id: idArr[index]
+        }
+      })
+    },
+    toOtherPage (id, type) {
+      let goLink = ''
+      if (type === 'winner') {
+        goLink = `/ent_portrait/${id}`
+      } else if (type === 'buyer') {
+        goLink = `/unit_portrayal/${id}`
+      }
+      if (id && goLink) {
+        window.open(`/swordfish/page_big_pc${goLink}`)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+  .ellipsis-5.el-link {
+    @include ellipsis(5);
+  }
+}
+.inline {
+  display: inline;
+}
+.project-top-30 {
+  padding: 32px 0;
+  width: 100%;
+  ::v-deep {
+    .header-cell-class {
+      font-size: 14px;
+      line-height: 22px;
+      color: #686868;
+      background-color: #F9FAFB;
+    }
+    .el-table__cell {
+      font-size: 14px;
+      line-height: 22px;
+    }
+  }
+  .proRank_head{
+    padding: 32px 0 16px;
+    font-size: 18px;
+    color: #1d1d1d;
+    line-height: 28px;
+  }
+  .proRank_footer {
+    margin-top: 16px;
+    display: flex;
+    justify-content: flex-end;
+  }
+}
+</style>

+ 51 - 0
src/components/report-data/TopNumber.vue

@@ -0,0 +1,51 @@
+<template>
+  <div class="top-number-container flex-r-c center">
+    <span
+      class="top-number"
+      :class="{
+        red: num === 1,
+        'deep-orange': num === 2,
+        'light-orange': num === 3
+      }"
+    >{{num}}</span>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'TopNumber',
+  props: {
+    num: {
+      type: Number,
+      default: 1
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.top-number{
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 20px;
+  height: 20px;
+  border-radius: 2px;
+  font-size: 14px;
+  line-height: 16px;
+  color: #9B9CA3;
+  background-color: transparent;
+}
+.red {
+  background: #FB483D;
+  color: #fff;
+}
+.deep-orange {
+  background: #FF9F40;
+  color: #fff;
+}
+.light-orange {
+  background: #F1D090;
+  color: #fff;
+}
+</style>

+ 128 - 0
src/views/reportData/components/AssociationInput.vue

@@ -0,0 +1,128 @@
+<template>
+  <!-- 联想组件 -->
+  <AssociationInput
+    :value="value"
+    :placeholder="placeholder"
+    @input="onInput"
+    @blur="onBlur"
+    @select="handleSelect"
+    :fetchAsync="fetchListAsync"
+  >
+  </AssociationInput>
+</template>
+
+<script>
+import AssociationInput from '@/components/common/AssociationInput.vue'
+import { getBidAssociation, getBuyerAssociation } from '@/api/modules/'
+import { debounce } from 'lodash'
+
+export default {
+  name: 'AssociationEntAndBuyer',
+  components: {
+    AssociationInput
+  },
+  props: {
+    value: {
+      type: String,
+      default: ''
+    },
+    type: {
+      type: String,
+      default: 'ent',
+      validator (value) {
+        return ['ent', 'buyer'].includes(value)
+      }
+    },
+    placeholder: {
+      type: String,
+      default: ''
+    }
+  },
+  methods: {
+    async fetchListAsync ({ value }) {
+      const params = {
+        name: value.trim()
+      }
+      if (params.name.length < 2) return
+
+      let associationList = []
+      if (this.type === 'ent') {
+        const { data } = await getBidAssociation(params)
+        if (data && Array.isArray(data.list)) {
+          associationList = data.list.map(t => {
+            return {
+              value: t.name
+            }
+          })
+        }
+      } else if (this.type === 'buyer') {
+        const { data } = await getBuyerAssociation(params)
+        if (data && Array.isArray(data.list)) {
+          associationList = data.list.map(t => {
+            return {
+              value: t
+            }
+          })
+        }
+      }
+
+      return associationList
+    },
+    onInput (e) {
+      this.$emit('input', e)
+    },
+    onBlur: debounce(function (e) {
+      this.$emit('blur', e)
+    }, 300),
+    handleSelect (item) {
+      this.onInput(item.value)
+      this.$emit('select')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+  .el-input__inner {
+    height: 30px;
+    line-height: 30px;
+    border-color: #e0e0e0;
+  }
+  .el-input__icon {
+    line-height: 30px;
+  }
+}
+.association-input {
+  width: 326px;
+}
+.association-container {
+  position: relative;
+  display: inline-block;
+}
+.pre-search-list {
+  padding: 10px 0;
+  position: absolute;
+  z-index: 6;
+  top: 36px;
+  width: 100%;
+  background: #fff;
+  box-shadow: 0 0 10px rgb(0,0,0,0.1);
+  border-radius: 4px;
+  overflow: hidden;
+}
+.pre-search-item {
+  padding: 0 20px;
+  width: 100%;
+  font-size: 14px;
+  line-height: 34px;
+  color: #606266;
+  box-sizing: border-box;
+  transition: all .3s;
+  cursor: pointer;
+}
+.pre-search-item:hover {
+  color: #1d1d1d;
+  background-color: #ececec;
+}
+</style>

+ 408 - 0
src/views/reportData/components/ProjectDetailsList.vue

@@ -0,0 +1,408 @@
+<template>
+  <section class="project-details">
+    <header class="project-details-header pd-lr">
+      <div class="filter-list line-1">
+        <SubscribeClassListCascader
+          class="filter-item"
+          :options="subscribeClassList"
+          v-model="filters.subscribe"
+          @change="onFilterChange"
+          ref="subscribeKey" />
+        <AreaCityCascader
+          class="filter-item"
+          v-model="filters.area"
+          @change="onFilterChange"
+          :showAreaMap="subscribeAreaMap"
+          ref="areaCity" />
+        <BuyerClassCascader
+          class="filter-item"
+          v-model="filters.buyerClass"
+          @change="onFilterChange"
+          :showOptions="subscribeBuyerClass"
+          ref="buyerClass" />
+        <IndustryCascader
+          class="filter-item"
+          @change="onFilterChange"
+          v-model="filters.industry"
+          ref="industry" />
+      </div>
+      <div class="filter-list line-2">
+        <div class="filter-item">
+          <span class="filter-item-label">中标单位:</span>
+          <AssociationInput
+            class="filter-item-input"
+            v-model.trim="filters.winner"
+            placeholder="请输入中标单位名称"
+            type="ent" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-item-label">采购单位:</span>
+          <AssociationInput
+            class="filter-item-input"
+            v-model.trim="filters.buyerName"
+            placeholder="请输入采购单位名称"
+            type="buyer" />
+        </div>
+      </div>
+    </header>
+    <main class="project-details-main">
+      <div class="project-details-actions">
+        <div class="project-details-actions-left">
+          <template v-if="searchTotal > 0">共 <span class="highlight-text">{{ listState.total }}</span> 个项目</template>
+        </div>
+        <div class="project-details-actions-right no-select">
+          <el-radio-group class="sort-group" v-model="filters.sort" size="small">
+            <el-radio-button
+              v-for="item in conf.sortList"
+              :label="item.value"
+              :key="item.value"
+            >{{ item.name }}</el-radio-button>
+          </el-radio-group>
+        </div>
+      </div>
+      <div class="project-details-list" v-loading="listState.loading">
+        <ProjectItem
+          class="project-details-item pd-lr"
+          v-for="project in listState.list"
+          :buyer="project.buyer"
+          :title="project.name"
+          :budget="project.budget"
+          :bidAmount="project.bidAmount"
+          :tagList="project.tagList"
+          :winners="project.winners"
+          @clickTitle="toProjectDetail(project)"
+          :key="project.id">
+          <template #hd-right>
+            本{{reportTypeText}}项目更新时间:{{ project.lastTime ? dateFormatter(project.lastTime * 1000, 'yyyy-MM-dd') : '-' }}
+          </template>
+        </ProjectItem>
+        <Empty v-show="listState.list.length === 0 && listState.loaded">暂无数据</Empty>
+      </div>
+    </main>
+    <div class="project-details-footer">
+      <div class="pagination-container pd-lr" v-show="listState.total > 0">
+        <el-pagination
+          background
+          popper-class="pagination-custom-select"
+          layout="prev, pager, next, sizes, jumper"
+          :page-size="listState.pageSize"
+          :current-page="listState.pageNum"
+          @current-change="onCurrentChange"
+          :total="searchTotal"
+          :page-sizes="[5, 10, 50, 100]"
+          :show-confirm-btn="true"
+          @size-change="onSizeChange"
+        ></el-pagination>
+        <div class="bottom-tip-text" v-show="listState.total > 5000">为您展示前{{ conf.listShowMaxCount }}条,可细化筛选条件查看更多信息</div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script>
+import { Input, RadioGroup, RadioButton, Table, TableColumn, Pagination } from 'element-ui'
+import SubscribeClassListCascader from '@/components/selector-cascader/SubscribeClassListCascader.vue'
+import AreaCityCascader from '@/components/selector-cascader/AreaCityCascader.vue'
+import BuyerClassCascader from '@/components/selector-cascader/BuyerClassCascader.vue'
+import IndustryCascader from '@/components/selector-cascader/IndustryCascader.vue'
+import AssociationInput from '@/views/reportData/components/AssociationInput.vue'
+import ProjectItem from '@/components/article-item/ProjectItem.vue'
+import Empty from '@/components/common/Empty'
+import { getWeekMonthReportProjectList } from '@/api/modules'
+import { formatMoney } from '@/utils/'
+import { debounce } from 'lodash'
+
+export default {
+  name: 'ProjectDetails',
+  components: {
+    [Input.name]: Input,
+    [RadioGroup.name]: RadioGroup,
+    [RadioButton.name]: RadioButton,
+    [Table.name]: Table,
+    [TableColumn.name]: TableColumn,
+    [Pagination.name]: Pagination,
+    SubscribeClassListCascader,
+    AreaCityCascader,
+    BuyerClassCascader,
+    IndustryCascader,
+    AssociationInput,
+    ProjectItem,
+    Empty
+  },
+  props: {
+    start: {
+      type: Number,
+      default: 0,
+      required: true
+    },
+    end: {
+      type: Number,
+      default: 0,
+      required: true
+    },
+    reportType: {
+      type: String,
+      default: 'week',
+      validator (value) {
+        return ['week', 'month'].includes(value)
+      }
+    },
+    subscribeClassList: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    subscribeBuyerClass: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    subscribeAreaMap: {
+      type: Object,
+      default () {
+        return {}
+      }
+    }
+  },
+  data () {
+    return {
+      conf: {
+        sortList: [
+          {
+            name: '项目更新时间由晚到早',
+            value: 0
+          },
+          {
+            name: '项目金额由大到小',
+            value: 1
+          }
+        ],
+        listShowMaxCount: 5000
+      },
+      filters: {
+        subscribe: [],
+        area: {},
+        buyerClass: [],
+        industry: {},
+        winner: '',
+        sort: 0,
+        buyerName: ''
+      },
+      listState: {
+        loaded: false,
+        loading: false,
+        pageNum: 1,
+        pageSize: 10,
+        total: 0,
+        list: []
+      }
+    }
+  },
+  computed: {
+    reportTypeText () {
+      const map = {
+        week: '周',
+        month: '月'
+      }
+      return map[this.reportType]
+    },
+    searchTotal () {
+      const { total } = this.listState
+      const { listShowMaxCount } = this.conf
+      return total > listShowMaxCount ? listShowMaxCount : total
+    }
+  },
+  watch: {
+    'filters.winner': function () {
+      this.onFilterChange()
+    },
+    'filters.buyerName': function () {
+      this.onFilterChange()
+    },
+    'filters.sort': function () {
+      this.onFilterChange()
+    }
+  },
+  created () {
+    this.doSearch()
+  },
+  methods: {
+    onFilterChange: debounce(function () {
+      this.doSearch()
+    }, 500),
+    resetListState () {
+      const r = this.$options.data().listState
+      delete r.list
+      Object.assign(this.listState, r)
+    },
+    doSearch () {
+      this.resetListState()
+      this.getList()
+    },
+    onSizeChange (p) {
+      this.listState.pageSize = p
+      this.doSearch()
+    },
+    onCurrentChange (p) {
+      this.listState.pageNum = p
+      this.getList()
+    },
+    getFilters () {
+      const { subscribe, area, buyerClass, industry, winner, buyerName, sort } = this.filters
+      const subscribeList = subscribe.map(item => item.s_item)
+      let industryArr = []
+      for (const key in industry) {
+        industryArr = industryArr.concat(industry[key])
+      }
+      return {
+        items: subscribeList,
+        sort,
+        area,
+        buyerClass,
+        industry: industryArr,
+        buyer: buyerName,
+        winner
+      }
+    },
+    async getList () {
+      const filters = this.getFilters()
+      const params = {
+        pageNum: this.listState.pageNum,
+        pageSize: this.listState.pageSize,
+        start: this.start,
+        end: this.end,
+        ...filters
+      }
+      try {
+        this.listState.loading = true
+        // const { listShowMaxCount } = this.conf
+        const { data, error_code: code } = await getWeekMonthReportProjectList(params)
+        if (code === 0 && data && Array.isArray(data.list)) {
+          this.listState.list = data.list.map(item => {
+            // 整理标签
+            item.tagList = [
+              {
+                value: item.area === '其它' ? '' : item.area
+              },
+              {
+                value: item?.bidStatus === '其它' ? '' : item?.bidStatus
+              },
+              {
+                value: item?.buyerClass === '其它' ? '' : item?.buyerClass
+              },
+              {
+                // 有中标金额取中标金额,没有取预算,预算没有置空
+                value: formatMoney(item?.bidAmount || item?.budget)
+              }
+            ].filter(v => v.value)
+            // 整理中标企业
+            if (Array.isArray(item.winner)) {
+              const winners = item.winner.map((w, index) => {
+                let id = null
+                if (Array.isArray(item.winnerId)) {
+                  id = item.winnerId[index]
+                }
+                return {
+                  name: w,
+                  id
+                }
+              })
+              item.winners = winners.filter(w => w.name)
+            }
+            return item
+          })
+          this.listState.total = data.total
+        } else {
+          this.listState.list = []
+          this.listState.total = 0
+        }
+      } catch (error) {
+        console.log(error)
+      } finally {
+        this.listState.loading = false
+        this.listState.loaded = true
+      }
+    },
+    toProjectDetail (item) {
+      if (item.id) {
+        window.open(`/swordfish/page_big_pc/pro_follow_detail?sid=${item.id}`)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.pd-lr {
+  padding-left: 40px;
+  padding-right: 40px;
+}
+.project-details {
+  padding: 32px 0;
+  background-color: #fff;
+  &-header {
+    margin-bottom: 46px;
+  }
+}
+.project-details-actions {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 40px;
+  margin-bottom: 16px;
+}
+.project-details-list {
+  min-height: 420px;
+}
+
+.filter-list {
+  display: flex;
+  align-items: center;
+  .filter-item-label {
+    margin-right: 8px;
+    font-size: 14px;
+    line-height: 22px;
+  }
+  &.line-1 {
+    .filter-item {
+      &:not(:last-of-type) {
+        margin-right: 8px;
+      }
+    }
+  }
+  &.line-2 {
+    margin-top: 16px;
+    .filter-item {
+      &:not(:last-of-type) {
+        margin-right: 32px;
+      }
+    }
+  }
+}
+.project-details-footer {
+  text-align: right;
+  .bottom-tip-text {
+    margin-top: 8px;
+    padding: 0 5px;
+    font-size: 12px;
+    line-height: 18px;
+    color: #9B9CA3;
+  }
+}
+.sort-group {
+  ::v-deep {
+    .el-radio-button__inner {
+      padding: 8px 10px;
+      font-size: 14px;
+      width: 160px;
+    }
+  }
+}
+::v-deep {
+  .el-radio-button__inner {
+    color: #1d1d1d;
+  }
+}
+</style>