Эх сурвалжийг харах

feat: 新增项目信息详情页

cuiyalong 4 жил өмнө
parent
commit
5bef0916de

+ 18 - 0
src/api/modules/project.js

@@ -19,6 +19,24 @@ export function getFollowProjectList (data) {
   })
 }
 
+export function followProjectAdd (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/follow/project/add',
+    method: 'POST',
+    data
+  })
+}
+
+export function followProjectCancel (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/follow/project/cancel',
+    method: 'POST',
+    data
+  })
+}
+
 export function setFollowRemove30Day (data) {
   data = qs.stringify(data)
   return request({

BIN
src/assets/images/icon/analysis.png


+ 7 - 5
src/components/common/ContentLayout.vue

@@ -3,7 +3,7 @@
     <div class="content-main">
       <slot name="default"></slot>
     </div>
-    <div class="content-right ad-container">
+    <div class="content-right ad-container" :class="{ nothing: adList.length === 0 }">
       <slot name="right">
         <slot name="right-top"></slot>
         <div class="ad-list">
@@ -48,13 +48,11 @@ export default {
         //   s_link: 'https://web2-jytest.jydev.jianyu360.com/swordfish/docs/',
         //   s_pic: 'https://web2-qmxtest.jydev.jianyu360.com/upload/2021/03/24/20210324164127003068k656W.png'
         // }
-        {},
-        {}
       ]
     }
   },
   created () {
-    // this.getAdvertisementList()
+    this.getAdvertisementList()
   },
   methods: {
     async getAdvertisementList () {
@@ -91,12 +89,16 @@ export default {
   .content-right {
     width: 264px;
     margin-left: 16px;
+    &.nothing {
+      width: unset;
+      margin-left: unset;
+    }
     .ad-item-container {
       width: 100%;
       min-height: 120px;
       overflow: hidden;
       border-radius: 4px;
-      background-color: #C4C4C4;
+      // background-color: #C4C4C4;
       &:not(:last-of-type) {
         margin-bottom: 16px;
       }

+ 130 - 0
src/components/time-line/TimeLine.vue

@@ -0,0 +1,130 @@
+<template>
+  <div class="j-steps">
+    <div
+      class="j-step step-line grey"
+      v-for="(item, index) in stepList"
+      :key="index"
+    >
+      <div class="step-items">
+        <div
+          class="step-item step-circle"
+          :data-tip="index === 0 ? '最新' : ''"
+          :class="{
+            text: index === 0
+          }">
+          <span class="step-tag" v-for="(tag, index) in item.tags" :key="index">{{ tag }}</span>
+          <span class="item-time">{{ item.time }}</span>
+        </div>
+        <div class="step-item" @click="linkTo(item)">
+          <span class="item-label" v-html="item.content"></span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'time-line',
+  props: {
+    stepList: {
+      type: Array,
+      default () {
+        return []
+      }
+    }
+  },
+  methods: {
+    linkTo (item) {
+      window.open(`/article/content/${item.s_id}.html`)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.step-tag {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 22px;
+  padding: 0 6px;
+  color: $color-text--highlight;
+  font-size: 12px;
+  line-height: 20px;
+  border: 1px solid $color-text--highlight;
+  border-radius: 5px;
+}
+.step-line {
+  position: relative;
+  &.grey {
+    &::before {
+      background-color: #ECECEC;
+    }
+  }
+  &::before {
+    content: '';
+    position: absolute;
+    top: 20px;
+    left: 0px;
+    width: 1px;
+    height: 100%;
+    background-color: #2ABED1;
+    transform: translate(50%, 0);
+  }
+}
+.step-circle {
+  position: relative;
+  &.text {
+    &::before {
+      @extend .step-tag;
+      left: -44px;
+      width: unset;
+      color: #fff;
+      border: 1px solid $color-text--highlight;
+    }
+  }
+  &::before {
+    content: attr(data-tip);
+    position: absolute;
+    top: 50%;
+    left: -30px;
+    width: 10px;
+    height: 10px;
+    border-radius: 50%;
+    transform: translate(0, -50%);
+    background-color: $color-text--highlight;
+    border: 2px solid #D2F6FC;
+  }
+}
+
+.j-step {
+  display: flex;
+  justify-content: space-between;
+  padding: 12px 0;
+  cursor: pointer;
+  .step-items {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    padding-left: 26px;
+    .step-item {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      line-height: 24px;
+      &:first-of-type {
+        margin-bottom: 10px;
+      }
+    }
+    .step-tag {
+      margin-right: 8px;
+    }
+    .item-time {
+      color: #999;
+      font-size: 14px;
+      line-height: 24px;
+    }
+  }
+}
+</style>

+ 6 - 0
src/router.js

@@ -94,6 +94,12 @@ export default new Router({
       name: 'entportrayal',
       component: () => import('@/views/portrayal/EntPortrayal.vue')
     },
+    // 项目信息
+    {
+      path: '/project/info',
+      name: 'projectInfo',
+      component: () => import('@/views/project/ProjectInfo.vue')
+    },
     // 投标决策分析结果页
     {
       path: '/analysis/result/:ptid/:sourceinfoid',

+ 280 - 0
src/views/project/ProjectInfo.vue

@@ -0,0 +1,280 @@
+<template>
+  <ContentLayout class="project-info">
+    <div class="project-content-container">
+      <div class="project-header">
+        <div class="p-h-top">
+          <div class="p-h-title">{{ projectInfo.projectname }}</div>
+          <div class="p-h-actions" v-if="!isOver">
+            <el-button class="action-button" type="primary" icon="el-icon-jy-ai" @click="$router.push('/bidforecast')">中标企业预测</el-button>
+            <el-button class="action-button" type="primary" icon="el-icon-jy-analysis" @click="$router.push('/bidpolicy')">投标决策分析</el-button>
+          </div>
+        </div>
+        <div class="p-h-bottom">
+          <div class="action-follow" @click="clickFollowButton">
+            <span :class="{
+              'el-icon-jy-heart_solid': projectContent.isfollow,
+              'el-icon-jy-heart_stroke': !projectContent.isfollow
+            }"></span>
+            <span class="text">{{ projectContent.isfollow ? '已关注' : '关注项目' }}</span>
+          </div>
+        </div>
+      </div>
+      <div class="project-content">
+        <el-card class="project-content">
+          <div slot="header" class="p-h-title">项目摘要</div>
+          <div class="p-c-table">
+            <div class="p-c-row">
+              <div class="p-c-column">地区</div>
+              <div class="p-c-column">{{ projectInfo.area }} {{ projectInfo.city }}</div>
+            </div>
+            <div class="p-c-row">
+              <div class="p-c-column">招标代理机构</div>
+              <div class="p-c-column">{{ projectInfo.agency ? projectInfo.agency : ' - ' }}</div>
+            </div>
+            <div class="p-c-row">
+              <div class="p-c-column">采购单位</div>
+              <div class="p-c-column highlight-text">{{ projectInfo.buyer ? projectInfo.buyer : ' - ' }}</div>
+            </div>
+            <div class="p-c-row">
+              <div class="p-c-column">采购联系人</div>
+              <div class="p-c-column">{{ projectInfo.buyerperson ? projectInfo.buyerperson : ' - ' }}</div>
+            </div>
+            <div class="p-c-row">
+              <div class="p-c-column">采购电话</div>
+              <div class="p-c-column">{{ projectInfo.buyertel ? projectInfo.buyertel : ' - ' }}</div>
+            </div>
+          </div>
+        </el-card>
+        <el-card class="project-content">
+          <div slot="header" class="p-h-title">项目公告</div>
+          <div class="p-c-main">
+            <TimeLine :stepList="timeLineList" />
+          </div>
+        </el-card>
+      </div>
+    </div>
+  </ContentLayout>
+</template>
+
+<script>
+import { Card, Button } from 'element-ui'
+import ContentLayout from '@/components/common/ContentLayout.vue'
+import TimeLine from '@/components/time-line/TimeLine.vue'
+import { getFollowProjectDetail, followProjectAdd, followProjectCancel } from '@/api/modules/'
+import { replaceKeyword, dateFormatter } from '@/utils'
+
+export default {
+  name: 'projectInfo',
+  components: {
+    [Card.name]: Card,
+    [Button.name]: Button,
+    ContentLayout,
+    TimeLine
+  },
+  data () {
+    return {
+      sid: '', // 信息id
+      fid: '', // 关注id
+      projectContent: {
+        isfollow: false, // 是否关注
+        bidopentime: 1601082000, // 开标时间
+        remind: false, // 是否提醒 1提醒 0不提醒
+        remindtime: '' // 提醒时间
+      },
+      projectInfo: {
+        agency: '', // 招标代理机构
+        area: '', // 项目省份
+        city: '', // 项目城市
+        projectcode: '', // 项目code
+        projectname: '', // 项目名称
+        bidopentime: 0, // 开标时间
+        buyer: '', // 采购单位
+        buyerperson: '', // 采购单位联系人
+        buyertel: '', // 采购电话
+        list: [
+          // {
+          //   s_id: '', // 信息id
+          //   s_title: '', // 信息
+          //   bidamount: 0, // 中标金额
+          //   budget: 0, // 预算
+          //   l_publishtime: 0, // 发布时间
+          //   s_toptype: '', // 一级类别
+          //   s_subtype: '' // 二级类别(若一级类别为空,则展示二级)
+          // }
+        ]
+      }
+    }
+  },
+  computed: {
+    isOver () {
+      return this.projectContent.bidopentime * 1000 < +new Date()
+    },
+    timeLineList () {
+      return this.projectInfo.list.map(item => {
+        const s = {
+          s_id: item.s_id,
+          tags: []
+        }
+        if (item.l_publishtime) {
+          s.time = dateFormatter(item.l_publishtime * 1000, 'yyyy-MM-dd')
+        }
+        if (item.s_title) {
+          s.content = replaceKeyword(item.s_title, this.projectInfo.projectname, `<span class="highlight-text">${this.projectInfo.projectname}</span>`)
+        }
+        if (item.s_subtype) {
+          s.tags.push(item.s_subtype)
+        }
+        if (item.s_toptype) {
+          s.tags.push(item.s_toptype)
+        }
+        return s
+      })
+    }
+  },
+  created () {
+    const query = this.$route.query
+    this.sid = query.sid
+    this.fid = query.fid
+    this.getFollowPInfo()
+  },
+  methods: {
+    async getFollowPInfo () {
+      const { sid, fid } = this
+      const { data, error_code: code } = await getFollowProjectDetail({ sid, fid })
+      if (code === 0 && data) {
+        Object.assign(this.projectContent, data)
+        Object.assign(this.projectInfo, data.projectInfo)
+        this.filterData()
+      }
+    },
+    async clickFollowButton () {
+      const { sid, fid } = this
+      const { isfollow } = this.projectContent
+      if (isfollow) {
+        const { data, error_code: code } = await followProjectCancel({ sid, fid })
+        if (code === 0 && data) {
+          this.projectContent.isfollow = false
+        }
+      } else {
+        const { data, error_code: code } = await followProjectAdd({ sid })
+        if (code === 0 && data) {
+          this.projectContent.isfollow = true
+          this.fid = data
+          this.$router.replace({ path: this.$route.path, query: { sid: this.sid, fid: this.fid } })
+        }
+      }
+    },
+    filterData () {
+      const getAreas = ['北京', '天津', '重庆', '上海']
+      if (getAreas.includes(this.projectInfo.area)) {
+        this.projectInfo.area = ''
+      } else {
+        this.projectInfo.area = `${this.projectInfo.area}省`
+      }
+      console.log(this.timeLineList)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.project-info {
+  width: 1200px;
+}
+@include diy-icon('ai', 20, 20);
+@include diy-icon('analysis', 20, 20);
+@include diy-icon('heart_solid', 20, 20);
+@include diy-icon('heart_stroke', 20, 20);
+::v-deep {
+  .el-button+.el-button {
+    margin-left: 20px;
+  }
+  .el-card__header {
+    padding: 24px 40px 16px;
+    border: none;
+  }
+  .el-card__body {
+    padding: 0 40px 32px;
+  }
+}
+.action-button {
+  height: 36px;
+  padding: 4px 16px;
+  display: flex;
+  align-items: center;
+  background-color: $color-text--highlight;
+  border-color: $color-text--highlight;
+}
+$border-color: #ececec;
+.p-c-table {
+  .p-c-row {
+    display: flex;
+    border-top: 1px solid $border-color;
+    border-bottom: 1px solid $border-color;
+    &:not(:first-of-type) {
+      border-top: none;
+    }
+  }
+  .p-c-column {
+    padding: 12px 16px;
+    border-left: 1px solid $border-color;
+    border-right: 1px solid $border-color;
+    line-height: 24px;
+    font-size: 14px;
+    &:nth-of-type(1) {
+      width: 180px;
+      color: #686868;
+      background-color: #F9FAFB;
+    }
+    &:nth-of-type(2) {
+      flex: 1;
+    }
+    &:not(:first-of-type) {
+      border-left: none;
+    }
+  }
+}
+.project-header {
+  padding: 18px 40px;
+  background-color: #fff;
+  box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
+  .p-h-top {
+    display: flex;
+    justify-content: space-between;
+    .p-h-title {
+      font-size: 24px;
+      color: #171826;
+      line-height: 36px;
+    }
+    .p-h-actions {
+      margin-left: 46px;
+      display: flex;
+      justify-content: space-between;
+    }
+  }
+  .p-h-bottom {
+    margin-top: 12px;
+    display: flex;
+    justify-content: flex-end;
+    .action-follow {
+      display: flex;
+      align-items: center;
+      cursor: pointer;
+      .text {
+        margin-left: 4px;
+        color: #686868;
+        font-size: 14px;
+        line-height: 24px;
+      }
+    }
+  }
+}
+.project-content {
+  margin: 16px auto;
+  .p-h-title {
+    color: #1D1D1D;
+    font-size: 18px;
+    line-height: 28px;
+  }
+}
+</style>