Browse Source

feature:增加招标信息模块

liuzl 2 years ago
parent
commit
88cdb3441f
2 changed files with 846 additions and 0 deletions
  1. 509 0
      src/views/customer/inviteTenders/details.vue
  2. 337 0
      src/views/customer/inviteTenders/index.vue

+ 509 - 0
src/views/customer/inviteTenders/details.vue

@@ -0,0 +1,509 @@
+<!--
+ * @Author: wanglj 471442253@qq.com
+ * @Date: 2022-12-26 09:30:47
+ * @LastEditors: liuzhenlin
+ * @LastEditTime: 2023-06-03 10:51:58
+ * @Description: file content
+ * @FilePath: \订单全流程管理系统\src\views\customer\inviteTenders\details.vue
+-->
+<template>
+  <div class="detail">
+    <div class="side-layout">
+      <div class="info">
+        <div class="title">
+          <p>招标</p>
+          <h3>
+            {{ detail.title }}
+          </h3>
+        </div>
+        <header>
+          <el-descriptions :colon="false" :column="7" direction="vertical">
+            <el-descriptions-item content-class-name="my-content" label="招标产品名称" label-class-name="my-label">
+              {{ detail.productName }}
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="发布招标日期" label-class-name="my-label">
+              {{ parseTime(detail.publishedTime, '{y}-{m}-{d}') }}
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="项目预算" label-class-name="my-label">
+              {{ detail.budget }}
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="客户名称" label-class-name="my-label">
+              {{ detail.cuctName }}
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="信息分类" label-class-name="my-label">
+              {{ bidInfoTypeOptions[detail.infoType] }}
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="中标单位" label-class-name="my-label">
+              {{ detail.bidder }}
+            </el-descriptions-item>
+            <el-descriptions-item content-class-name="my-content" label="创建招标日期" label-class-name="my-label">
+              {{ parseTime(detail.biddingTime, '{y}-{m}-{d}') }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </header>
+        <el-tabs v-model="activeName" @tab-click="handleClick">
+          <el-tab-pane label="跟进记录" name="follow">
+            <ul v-if="followList.length" class="follow">
+              <li v-for="(date, index) in followList" :key="index">
+                <div class="date">
+                  <h2>{{ date.followDay.split('-')[2] }}</h2>
+                  <h3>
+                    {{ date.followDay.split('-').splice(0, 2).join('.') }}
+                  </h3>
+                </div>
+                <ul class="content">
+                  <li v-for="(item, idx) in date.followupList" :key="idx">
+                    <!-- <el-avatar class="user-avatar"
+              :src="avatar" />-->
+                    <div class="text-container">
+                      <vab-icon class="user-avatar" icon="account-circle-fill" />
+                      <div class="text">
+                        <p class="action">
+                          <span>{{ item.createdName }} 跟进({{ formatType(item.followType) }})</span>
+                          <span>
+                            <vab-icon icon="time-line" />
+                            {{ item.followDate }}
+                          </span>
+                        </p>
+                        <p>{{ item.followContent }}</p>
+                        <div class="footer">
+                          <p>
+                            来自客户:
+                            <span>{{ item.custName }}</span>
+                          </p>
+                          <div>
+                            <el-button size="mini" @click="showDetail(item)">
+                              <vab-icon icon="arrow-right-circle-fill" />
+                              详情
+                            </el-button>
+                            <el-button size="mini" @click="showComment(item)">评论({{ item.commentNumber }})</el-button>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                    <transition name="height">
+                      <ul v-if="item.showComment" class="comments">
+                        <li v-for="comment in item.comments" :key="comment.id">
+                          <vab-icon class="user-avatar" icon="account-circle-fill" />
+                          <div class="text">
+                            <p>{{ comment.createdName }}</p>
+                            <p>{{ comment.content }}</p>
+                            <p>{{ comment.createdTime }}</p>
+                          </div>
+                        </li>
+                      </ul>
+                    </transition>
+                  </li>
+                </ul>
+              </li>
+            </ul>
+            <div v-else class="no-follow">暂无跟进记录</div>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </div>
+    <!-- 跟进详情 -->
+    <FollowDetail ref="followDetail" />
+  </div>
+</template>
+
+<script>
+  import follow from '@/api/customer/follow'
+  import to from 'await-to-js'
+  import FollowDetail from '../components/FollowDetail.vue'
+  import bidApi from '@/api/customer/bid'
+
+  export default {
+    name: 'CustomerDetail',
+    components: {
+      FollowDetail,
+    },
+    data() {
+      return {
+        id: '',
+        detail: {
+          custCode: '', //客户编码
+          abbrName: '', //助记名
+          level: '', //客户级别
+          indusTry: '', //客户类型
+          custStatus: '', //客户状态
+          followUpDate: '', //最后跟进时间
+        },
+        activeName: 'follow',
+        followList: [],
+        bidInfoTypeOptions: {},
+      }
+    },
+    mounted() {
+      this.id = this.$route.query.id
+      this.init()
+      this.handleClick({ name: 'follow' })
+      this.getOptions()
+    },
+    methods: {
+      getOptions() {
+        this.getDicts('bid_info_type').then((response) => {
+          this.bidInfoTypeOptions = {}
+          response.data.values.filter((i) => {
+            this.bidInfoTypeOptions[i.key] = i.value
+          })
+        })
+      },
+      async init() {
+        const [err, res] = await to(bidApi.get({ id: parseInt(this.id) }))
+        if (err) return
+        if (res.code == 200) {
+          this.detail = res.data
+        }
+      },
+
+      async handleClick(tab) {
+        let err, res
+        if (tab.name == 'follow') {
+          let params = {
+            targetId: '' + this.id,
+            targetType: '60',
+            DaysBeforeToday: 99999,
+          }
+          ;[err, res] = await to(follow.getListByDay(params))
+          if (err) return
+          this.followList = res.data.list || []
+        }
+      },
+
+      formatType(val) {
+        let str = ''
+        if (val == 10) str = '电话'
+        else if (val == 20) str = '邮件'
+        else if (val == 30) str = '拜访'
+        return str
+      },
+      // 跟进记录详情
+      showDetail(row) {
+        this.$refs.followDetail.init({ ...row })
+      },
+      // 展开评论
+      showComment(row) {
+        if (!row.comments.length) return this.$message.warning('暂无评论')
+        row.showComment = !row.showComment
+        this.$forceUpdate()
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  $base: '.detail';
+  #{$base} {
+    height: calc(100vh - 60px - 12px * 2 - 40px);
+    display: flex;
+    padding: 20px 40px;
+
+    > .el-row {
+      flex: 1;
+      width: 100%;
+
+      > .el-col {
+        height: 100%;
+      }
+    }
+
+    .title {
+      p,
+      h3 {
+        margin: 0;
+      }
+
+      p {
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 22px;
+      }
+
+      h3 {
+        font-size: 24px;
+        font-weight: 500;
+        line-height: 36px;
+        color: #333;
+        display: flex;
+        justify-content: space-between;
+      }
+    }
+
+    header {
+      height: 74px;
+      background: rgba(196, 196, 196, 0.5);
+      border-radius: 4px;
+      display: flex;
+      align-items: center;
+      padding: 0 20px;
+      margin-top: 16px;
+
+      ::v-deep .el-descriptions__body {
+        background: transparent;
+      }
+
+      ::v-deep .my-label {
+        font-size: 14px;
+        font-weight: 600;
+        color: #1d66dc;
+      }
+
+      ::v-deep .my-content {
+        font-size: 14px;
+        font-weight: 600;
+        color: #333;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+    }
+
+    .el-tabs {
+      height: calc(100% - 148px);
+      display: flex;
+      flex-direction: column;
+
+      ::v-deep .el-tabs__content {
+        flex: 1;
+
+        .el-tab-pane {
+          height: 100%;
+
+          .el-descriptions {
+            table-layout: fixed;
+
+            .is-bordered {
+              table-layout: fixed;
+            }
+          }
+        }
+      }
+    }
+
+    .buttons {
+      padding-top: 28px;
+      text-align: right;
+    }
+
+    .records {
+      margin: 0;
+      padding: 10px 20px;
+      list-style: none;
+      height: calc(100% - 60px);
+      overflow-y: auto;
+
+      > li {
+        display: flex;
+
+        & + li {
+          margin-top: 10px;
+        }
+      }
+
+      .date {
+        width: 100px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+
+        h2,
+        h3 {
+          margin: 0;
+        }
+
+        h2 {
+          font-size: 26px;
+          line-height: 32px;
+        }
+      }
+
+      .content {
+        flex: 1;
+        list-style: none;
+
+        li {
+          display: flex;
+
+          & + li {
+            margin-top: 10px;
+          }
+        }
+
+        .user-avatar {
+          font-size: 40px;
+        }
+
+        .text {
+          flex: 1;
+          padding-left: 20px;
+
+          p {
+            font-weight: 500;
+            margin: 0;
+            line-height: 20px;
+
+            span {
+              color: #1d66dc;
+            }
+          }
+
+          p:nth-child(2) {
+            margin-bottom: 10px;
+          }
+
+          .action {
+            font-weight: bold;
+            color: #333;
+          }
+        }
+      }
+    }
+
+    .follow {
+      height: 100%;
+      padding: 10px 20px;
+      overflow: auto;
+
+      > li {
+        display: flex;
+
+        + li {
+          margin-top: 10px;
+        }
+      }
+
+      .date {
+        width: 100px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+
+        h2,
+        h3 {
+          margin: 0;
+        }
+
+        h2 {
+          font-size: 26px;
+          line-height: 32px;
+        }
+      }
+
+      .content {
+        flex: 1;
+        list-style: none;
+
+        > li {
+          border: 1px solid rgb(215, 232, 244);
+          background: rgb(247, 251, 254);
+          border-radius: 4px;
+          padding: 8px;
+          overflow: hidden;
+
+          .text-container {
+            display: flex;
+          }
+
+          .comments {
+            padding-left: 60px;
+            margin-top: 10px;
+            max-height: 190px;
+            overflow: auto;
+
+            li {
+              display: flex;
+              border-top: 1px solid #e3e5e7;
+
+              .text {
+                flex: 1;
+                padding: 0 10px;
+
+                p {
+                  font-weight: 500;
+                  margin: 0;
+                  line-height: 32px;
+                }
+
+                p:first-child {
+                  line-height: 30px;
+                  font-weight: bold;
+                }
+
+                p:last-child {
+                  font-size: 12px;
+                  color: #9499a0;
+                  text-align: right;
+                }
+              }
+            }
+          }
+
+          + li {
+            margin-top: 10px;
+          }
+        }
+
+        .user-avatar {
+          font-size: 40px;
+        }
+
+        .text {
+          flex: 1;
+          padding-left: 20px;
+          padding-right: 10px;
+
+          p {
+            font-weight: 500;
+            margin: 0;
+            line-height: 32px;
+
+            span {
+              color: #1d66dc;
+            }
+          }
+
+          .action {
+            display: flex;
+            justify-content: space-between;
+
+            span:first-child {
+              font-weight: bold;
+              color: #333;
+            }
+          }
+
+          .footer {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+          }
+        }
+      }
+    }
+
+    .no-follow {
+      height: 100%;
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 12px;
+      color: rgba(0, 0, 0, 0.65);
+    }
+  }
+
+  .height-enter-active,
+  .height-leave-active {
+    transition: all 0.5s;
+  }
+
+  .height-enter-to,
+  .height-leave {
+    height: 190px;
+  }
+
+  .height-enter, .height-leave-to /* .fade-leave-active below version 2.1.8 */ {
+    height: 0;
+  }
+</style>

+ 337 - 0
src/views/customer/inviteTenders/index.vue

@@ -0,0 +1,337 @@
+<template>
+  <div class="user-management-container">
+    <div class="side-layout">
+      <div class="tree-side">
+        <span style="font-size: 25px">招标区域</span>
+        <el-tree
+          ref="tree"
+          :data="regionOptions"
+          default-expand
+          :default-expand-all="false"
+          :expand-on-click-node="true"
+          :filter-node-method="filterNode"
+          highlight-current
+          node-key="id"
+          :props="defaultProps"
+          @node-click="handleNodeClick">
+          <span slot-scope="{ node }" class="custom-tree-node">
+            <span>{{ node.label }}</span>
+            <span>
+              <i class="el-icon-more"></i>
+            </span>
+          </span>
+        </el-tree>
+      </div>
+      <div class="tree-table">
+        <vab-query-form>
+          <vab-query-form-top-panel>
+            <el-form :inline="true" :model="queryForm" @submit.native.prevent>
+              <el-form-item>
+                <el-input
+                  v-model.trim="queryForm.productName"
+                  clearable
+                  placeholder="产品名称"
+                  @keyup.enter.native="queryData" />
+              </el-form-item>
+              <el-form-item>
+                <el-input
+                  v-model.trim="queryForm.title"
+                  clearable
+                  placeholder="招标信息标题"
+                  @keyup.enter.native="queryData" />
+              </el-form-item>
+              <el-form-item>
+                <el-input
+                  v-model.trim="queryForm.bidder"
+                  clearable
+                  placeholder="中标单位"
+                  @keyup.enter.native="queryData" />
+              </el-form-item>
+              <el-form-item>
+                <el-button icon="el-icon-search" type="primary" @click="queryData">查询</el-button>
+                <el-button icon="el-icon-refresh-right" @click="reset">重置</el-button>
+              </el-form-item>
+            </el-form>
+          </vab-query-form-top-panel>
+        </vab-query-form>
+        <el-row :span="24">
+          <el-col>
+            <table-tool :columns="columns" :show-columns.sync="showColumns" table-type="collectionTable" />
+          </el-col>
+        </el-row>
+        <el-table
+          :key="tableKey"
+          ref="table"
+          v-loading="listLoading"
+          border
+          :data="list"
+          :height="$baseTableHeight(2)"
+          @selection-change="setSelectRows">
+          <el-table-column
+            v-for="(item, index) in showColumns"
+            :key="index"
+            align="center"
+            :label="item.label"
+            :prop="item.prop"
+            show-overflow-tooltip
+            :sortable="item.sortable"
+            :width="item.width">
+            <template #default="{ row }">
+              <el-button v-if="item.prop === 'title'" style="font-size: 14px" type="text" @click="handleDetail(row)">
+                {{ row.title }}
+              </el-button>
+              <span v-else-if="item.prop === 'infoType'">
+                {{ bidInfoTypeOptions[row.infoType] }}
+              </span>
+              <span v-else>{{ row[item.prop] }}</span>
+            </template>
+          </el-table-column>
+          <template #empty>
+            <el-image class="vab-data-empty" :src="require('@/assets/empty_images/data_empty.png')" />
+          </template>
+        </el-table>
+        <el-pagination
+          background
+          :current-page="queryForm.pageNum"
+          :layout="layout"
+          :page-size="queryForm.pageSize"
+          :total="total"
+          @current-change="handleCurrentChange"
+          @size-change="handleSizeChange" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import bidApi from '@/api/customer/bid'
+  import regionApi from '@/api/base/region'
+  import regionAuthApi from '@/api/base/regionAuth'
+  import TableTool from '@/components/table/TableTool'
+
+  export default {
+    name: 'Distributor',
+    components: { TableTool },
+    data() {
+      return {
+        tableKey: 0,
+        list: [],
+        listLoading: true,
+        layout: 'total, sizes, prev, pager, next, jumper',
+        total: 0,
+        selectRows: '',
+        queryForm: {
+          pageNum: 1,
+          pageSize: 10,
+          productName: '',
+          title: '',
+          bidder: '',
+          custProvinceId: null,
+        },
+        showColumns: [],
+        columns: [
+          {
+            label: '招标信息标题',
+            width: '160px',
+            prop: 'title',
+            sortable: false,
+          },
+          {
+            label: '招标产品名称',
+            width: '160px',
+            prop: 'productName',
+            sortable: false,
+          },
+          {
+            label: '发布招标日期',
+            width: '160px',
+            prop: 'publishedTime',
+            sortable: false,
+          },
+          {
+            label: '项目预算',
+            width: '100px',
+            prop: 'budget',
+            sortable: false,
+          },
+          {
+            label: '客户名称',
+            width: 'auto',
+            prop: 'cuctName',
+            sortable: false,
+          },
+          {
+            label: '信息分类',
+            width: '100px',
+            prop: 'infoType',
+            sortable: false,
+          },
+          {
+            label: '中标单位',
+            width: '100px',
+            prop: 'bidder',
+            sortable: false,
+          },
+          {
+            label: '创建招标日期',
+            width: '160px',
+            prop: 'biddingTime',
+            sortable: false,
+          },
+          {
+            label: '备注',
+            width: 'auto',
+            prop: 'remark',
+            sortable: false,
+            disableCheck: false,
+          },
+        ],
+
+        regionOptions: [],
+        userSalesProvince: undefined,
+        defaultProps: {
+          id: 'id',
+          children: 'children',
+          label: 'regionDesc',
+        },
+        treeDefaultExpandAll: true,
+        bidInfoTypeOptions: {},
+      }
+    },
+    watch: {
+      showColumns: function () {
+        this.tableKey++
+        this.$nextTick(() => this.$refs.table.doLayout())
+      },
+    },
+    activated() {
+      this.fetchData()
+    },
+    created() {
+      this.getOptions()
+      this.fetchData()
+      this.getRegionTree()
+      this.getUserSalesProvince()
+    },
+    methods: {
+      getOptions() {
+        this.getDicts('bid_info_type').then((response) => {
+          this.bidInfoTypeOptions = {}
+          response.data.values.filter((i) => {
+            this.bidInfoTypeOptions[i.key] = i.value
+          })
+        })
+      },
+      async getRegionTree() {
+        const { data: data } = await regionApi.getRegionTree({})
+        this.regionOptions.push(...data.list)
+      },
+      async getUserSalesProvince() {
+        const { data: data } = await regionAuthApi.getUserSalesProvince({})
+        if (data && data.list) {
+          this.regionOptions.unshift(data.list)
+        }
+      },
+      // 筛选节点
+      filterNode(value, data) {
+        if (!value) return true
+        return data[this.defaultProps.label].indexOf(value) !== -1
+      },
+      // 节点单击事件
+      handleNodeClick(data) {
+        if (data.children && data.children.length) {
+          this.queryForm.custProvinceId = data.children.map((item) => item.regionCode)
+        } else {
+          this.queryForm.custProvinceId = [data.regionCode]
+        }
+        this.fetchData()
+      },
+      setSelectRows(val) {
+        this.selectRows = val
+      },
+      handleDelete(row) {
+        if (row.id) {
+          this.$baseConfirm('你确定要删除当前项吗', null, async () => {
+            const { msg } = await bidApi.delete({ ids: [row.id] })
+            this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+            await this.fetchData()
+          })
+        } else {
+          if (this.selectRows.length > 0) {
+            const ids = this.selectRows.map((item) => parseInt(item.id))
+            console.log(ids)
+            this.$baseConfirm('你确定要删除选中项吗', null, async () => {
+              const { msg } = await bidApi.delete({ ids })
+              this.$baseMessage(msg, 'success', 'vab-hey-message-success')
+              await this.fetchData()
+            })
+          } else {
+            this.$baseMessage('未选中任何行', 'error', 'vab-hey-message-error')
+          }
+        }
+      },
+      handleSizeChange(val) {
+        this.queryForm.pageSize = val
+        this.fetchData()
+      },
+      handleCurrentChange(val) {
+        this.queryForm.pageNum = val
+        this.fetchData()
+      },
+      queryData() {
+        this.queryForm.pageNum = 1
+        this.fetchData()
+      },
+      async fetchData() {
+        this.listLoading = true
+        const params = this.queryForm
+        const {
+          data: { list, total },
+        } = await bidApi.list(params)
+        this.list = list
+        this.total = total
+        this.listLoading = false
+        this.tableKey++
+        this.$nextTick(() => this.$refs.table.doLayout())
+      },
+      reset() {
+        this.queryForm = {
+          pageNum: 1,
+          pageSize: 10,
+          distName: '',
+          belongSale: '',
+        }
+        this.fetchData()
+      },
+      //详情
+      handleDetail(row) {
+        this.$router.push({
+          path: '/customer/inviteDetail',
+          query: {
+            id: row.id,
+          },
+        })
+      },
+    },
+  }
+</script>
+
+<style>
+  .el-tree-node:focus > .el-tree-node__content {
+    /*设置选中的样式 */
+    background-color: #dde9ff !important;
+  }
+
+  .el-tree-node__content:hover {
+    /*设置鼠标飘过的颜色 */
+    background: #eaf9ff !important;
+    color: #007bff;
+  }
+
+  .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
+    /*current选中的样式 */
+    color: #4d95fd;
+    font-weight: bold;
+    background-color: #dde9ff !important;
+  }
+</style>