Browse Source

feat: 合并dev,解决冲突

yangfeng 4 years ago
parent
commit
10a1189379

+ 0 - 2
public/index.html

@@ -18,8 +18,6 @@
       <script src="https://web2-jytest.jydev.jianyu360.com/js/jquery-3.2.1.min.js?v=6302"></script>
       <script src="https://web2-jytest.jydev.jianyu360.com/js/jquery.cookie.js"></script>
       <script src="https://web2-jytest.jydev.jianyu360.com/js/bootstrap.min.js"></script>
-      <script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@2.1.266/build/pdf.min.js"></script>
-      <script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@2.1.266/web/pdf_viewer.js"></script>
 
       <link href='https://web2-jytest.jydev.jianyu360.com/css/reset.css?v=6302' rel="stylesheet" type="text/css"/>
       <link href='https://web2-jytest.jydev.jianyu360.com/pccss/reset_pc.css' rel="stylesheet" type="text/css"/>

+ 24 - 0
src/App.vue

@@ -6,12 +6,36 @@
 
 <script>
 // @ is an alias to /src
+import { getUserPower } from '@/api/modules'
+import { mapState, mapMutations } from 'vuex'
 export default {
   components: {},
   data () {
     return {
       cashViews: []
     }
+  },
+  created () {
+    this.getUserRoot()
+  },
+  computed: {
+    ...mapState({
+      user: state => state.user.info
+    })
+  },
+  methods: {
+    ...mapMutations('user', [
+      'setUserInfo',
+      'setUserPower'
+    ]),
+    getUserRoot () {
+      getUserPower().then(res => {
+        if (res.error_code === 0) {
+          this.setUserInfo(res.data)
+          this.setUserPower(res.data.power)
+        }
+      })
+    }
   }
 }
 </script>

+ 50 - 0
src/api/modules/chart.js

@@ -40,3 +40,53 @@ export function getEntChart (data) {
     data
   })
 }
+
+// 企业画像-企业基本信息*
+export function getEntForm (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/portrait/ent/detail',
+    method: 'post',
+    data
+  })
+}
+
+// 企业情报历史数据接口
+export function getEntChangeList (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/follow/ent/entChangeList',
+    method: 'post',
+    data
+  })
+}
+
+// 采购单位画像-中标动态
+export function getNewMsg (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/portrait/winner/getNewMsg',
+    method: 'post',
+    data
+  })
+}
+
+// 添加关注的企业*
+export function setFollowEnt (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/follow/ent/addFollow',
+    method: 'post',
+    data
+  })
+}
+
+// 取消关注的企业*
+export function setCancelEnt (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/follow/ent/delFollow',
+    method: 'post',
+    data
+  })
+}

+ 10 - 0
src/api/modules/forecast.js

@@ -30,3 +30,13 @@ export function getResultDetail (data) {
     data: data
   })
 }
+
+// 我关注的企业列表
+export function getFollowtList (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/follow/ent/list',
+    method: 'post',
+    data: data
+  })
+}

+ 32 - 5
src/components/forecast/ForLayout.vue

@@ -13,15 +13,16 @@
       </div>
     </div>
     <div class="for_main">
-      <slot name="main" :list="list"></slot>
+      <slot name="main"></slot>
     </div>
   </div>
 </template>
 
 <script>
 import { Icon } from 'element-ui'
+import { mapState } from 'vuex'
 import SearchInput from '@/components/input/SearchInput.vue'
-import { getProjectList } from '@/api/modules'
+import { getProjectList, getFollowtList } from '@/api/modules'
 export default {
   name: 'j-container',
   components: {
@@ -30,16 +31,42 @@ export default {
   },
   data () {
     return {
-      list: []
+      list: [],
+      type: 0 // 0没有搜索过,1搜索过
     }
   },
+  computed: {
+    ...mapState({
+      forcast: state => state.forcast.list
+    })
+  },
+  created () {
+    this.type = 0
+    this.getMyFollowList()
+  },
   methods: {
     getRecovery (data) {
-      console.log(data)
+      this.type = 1
+      // 中标预测、投标决策分析列表
       getProjectList({ pName: data.text }).then(res => {
         console.log(res)
         if (res.error_code === 0) {
-          this.list = res.data
+          // 中标企业预测
+          this.$store.commit('forcast/setList', res.data)
+          this.$store.commit('forcast/setType', this.type)
+          // 投标决策分析
+          this.$store.commit('forcast/setPolicyList', res.data)
+          // 企业情报
+          this.$store.commit('forcast/setEntFollowSearch', res.data)
+        }
+      })
+    },
+    // 我关注的企业列表
+    getMyFollowList () {
+      getFollowtList().then(res => {
+        console.log(res)
+        if (res.error_code === 0 && res.data) {
+          this.$store.commit('forcast/setEntFollowList', res.data)
         }
       })
     }

+ 2 - 0
src/components/forecast/ForLimit.vue

@@ -208,12 +208,14 @@ export default {
             line-height: 24px;
           }
           .pur_com_ipt{
+            padding: 6px 15px;
             width: 550px;
             height: 36px;
             border: 1px solid #e0e0e0;
             border-radius: 5px;
           }
           .pur_bud_ipt{
+            padding: 6px 15px;
             width: 240px;
             height: 36px;
             border: 1px solid #e0e0e0;

+ 36 - 17
src/components/forecast/ForeCast.vue

@@ -2,8 +2,8 @@
   <div class="listData">
     <div class="listData_title">{{ title }}</div>
     <!-- 中标企业预测、投标决策分析 -->
-    <ul class="listData_ul" v-show="type=='bidfor'">
-      <li class="list_li" v-for="(item, index) in mydata" :key="index" @click="goForcast(item.s_id)">
+    <ul class="listData_ul" v-if="type=='bidfor'">
+      <li class="list_li" v-for="(item, index) in listState.list.slice((listState.pageNum - 1) * listState.pageSize, listState.pageNum * listState.pageSize)" :key="index" @click="goForcast(item.s_id)">
         <div class="list_name">{{item.projectname}}</div>
         <div class="list_unit">
           <div class="pur_unit">
@@ -18,31 +18,31 @@
       </li>
     </ul>
     <!-- 企业情报 -->
-    <ul class="listData_ul" v-show="type=='entintel'">
-      <li class="list_li" v-for="(item, index) in 10" :key="index">
-        <div class="list_name">北京市工商行政管理局朝阳分局</div>
+    <ul class="listData_ul" v-if="type=='entintel'">
+      <li class="list_li" v-for="(item, index) in listState.list" :key="index">
+        <div class="list_name">{{item.s_entname}}</div>
         <div class="list_unit">
           <div class="pur_unit">
             <span class="unit_label">成立日期:</span>
-            <span class="unit_name entname">2014-03-04</span>
+            <span class="unit_name entname">{{item.l_establishdate?item.l_establishdate:''}}</span>
           </div>
           <div class="pur_unit">
             <span class="unit_label">注册资本:</span>
-            <span class="unit_name entname">135000万元</span>
+            <span class="unit_name entname">{{item.f_capital}}万元</span>
           </div>
           <div class="pur_unit">
             <span class="unit_label">企业地址:</span>
-            <span class="unit_name entname">浙江 杭州</span>
+            <span class="unit_name entname">{{item.s_area}}{{item.s_city}}</span>
           </div>
           <div class="pur_unit">
             <span class="unit_label">企业联系方式:</span>
-            <span class="unit_name entname">0531-88888888</span>
+            <span class="unit_name entname">{{item.s_phone}}</span>
           </div>
         </div>
       </li>
     </ul>
     <!-- 企业情报搜索结果组件 -->
-    <ul class="listData_ul" v-show="type=='entintelRes'">
+    <ul class="listData_ul" v-if="type=='entintelRes'">
       <li class="list_li res_li" v-for="(item, index) in 10" :key="index">
         <div class="list_name">
           <div class="list_name_left">
@@ -57,7 +57,7 @@
       </li>
     </ul>
     <!-- 潜在项目预测 -->
-    <ul class="listData_ul" v-show="type=='potential'">
+    <ul class="listData_ul" v-if="type=='potential'">
       <li class="list_li poten_li" v-for="(item, index) in this.listState.list" :key="index">
         <div class="list_name">
           <div class="list_name_left">
@@ -81,7 +81,7 @@
       </li>
     </ul>
     <!-- 潜在项目预测近似项目列表 -->
-    <ul class="listData_ul" v-show="type=='potensimilar'">
+    <ul class="listData_ul" v-if="type=='potensimilar'">
       <li class="list_li potensimilar_li" v-for="(item, index) in 10" :key="index">
         <div class="list_unit">
           <div class="pur_unit">
@@ -111,6 +111,7 @@
         :current-page="listState.pageNum"
         :page-size="listState.pageSize"
         :total="listState.total"
+        @current-change="onPageChange"
       >
       </el-pagination>
     </div>
@@ -120,9 +121,8 @@
 <script>
 import { Pagination } from 'element-ui'
 // import { getPushList } from '@/api/modules/'
-// potenObj 潜在项目预测返回数据
 export default {
-  props: ['type', 'title', 'potenObj', 'mydata'],
+  props: ['type', 'title', 'mydata', 'myDataObj'],
   name: 'listData',
   components: {
     [Pagination.name]: Pagination
@@ -134,21 +134,40 @@ export default {
         loading: false,
         pageNum: 1, // 当前页
         pageSize: 10, // 每页多少条数据
-        total: 100, // 一共多少条数据
+        total: 0, // 一共多少条数据
         list: [] // 查询请求返回的数据
       }
     }
   },
   created () {
-    this.listState.list = this.mydata
-    this.listState.total = this.mydata.length
+    this.initData(this.mydata)
+    window.data = this.$data
+  },
+  watch: {
+    mydata (newVal, oldVal) {
+      this.initData(newVal)
+    },
+    myDataObj (newVal, oldVal) {
+      this.entInitData(newVal)
+    }
   },
   methods: {
+    initData (list) {
+      this.listState.list = list
+      this.listState.total = list.length
+    },
+    entInitData (obj) {
+      this.listState.list = obj.list
+      this.listState.total = obj.total
+    },
     goForcast (id) {
       this.$router.push({
         name: 'bidforlimit',
         params: { id: id }
       })
+    },
+    onPageChange (p) {
+      this.listState.pageNum = p
     }
   }
 }

+ 29 - 551
src/components/selector/AreaSelector.vue

@@ -1,504 +1,50 @@
 <template>
   <selector-card
-    class="area-selector card"
+    class="area-selector"
+    :cardType="selectorType"
     @onConfirm="onConfirm"
-    @onCancel="onCancel"
-  >
-    <div slot="header">选择区域</div>
-    <div class="selector-content" v-loading="loading">
-      <div class="search-container">
-        <el-input v-model.trim="searchContent" placeholder="搜索" prefix-icon="el-icon-search"></el-input>
-      </div>
-      <div class="select-list scrollbar" ref="selectList">
-        <div v-for="(item, key) in provinceListMap" :key="key" class="select-group-container">
-          <div class="index-anchor" :id="key" :data-index="key" v-if="key !== '#'">{{ key }}</div>
-          <el-collapse-transition v-for="(province, ii) in item" :key="ii*2">
-            <div class="select-group tab-content global" v-if="province.name == '全国'">
-              <button
-                class="j-button-item global"
-                :class="{
-                  active: province.selectedState,
-                  [province.id]: true
-                }"
-                @click="changeCityState(province, '#')"
-              >{{ province.name }}</button>
-            </div>
-            <div class="select-group" v-else>
-              <div class="tab" :class="province.id">
-                <div class="tab-name-container" @click="clickCheckbox(province)">
-                  <div class="j-checkbox" :class="province.selectedState"></div>
-                  <span class="tab-name">{{ province.name }}</span>
-                </div>
-                <span style="flex: 1; height: 100%;" @click="changeExpandState($event, province)"></span>
-                <span
-                  v-if="province.canExpanded"
-                  @click="changeExpandState($event, province)">
-                  <i
-                    class="el-icon-arrow-down"
-                    :class="{
-                      rotate180: province.expanded
-                    }"></i>
-                </span>
-              </div>
-              <el-collapse-transition>
-                <div v-show="province.expanded" class="tab-content">
-                  <div class="content-list">
-                    <button v-for="(city, iii) in province.children" :key="iii*3"
-                      class="j-button-item"
-                      :class="{
-                        active: city.selected,
-                        [city.id]: true
-                      }"
-                      :disabled="!city.canSelected"
-                      @click="changeCityState(province, city)"
-                      >{{ city.city }}</button>
-                  </div>
-                </div>
-              </el-collapse-transition>
-            </div>
-          </el-collapse-transition>
-        </div>
-      </div>
-    </div>
+    @onCancel="onCancel">
+    <div slot="header" v-if="selectorType === 'card'" key="header">选择区域</div>
+    <div slot="header" class="s-header" v-if="selectorType === 'line'" key="header">选择区域:</div>
+    <AreaSelectorContent
+      ref="areaSelectorContent"
+      :selectorType="selectorType"
+      :initCityMap="initCityMap"
+      @onChange="onChange"
+    />
   </selector-card>
 </template>
 
 <script>
-import { Input, Icon } from 'element-ui'
-import 'element-ui/lib/theme-chalk/base.css'
-import CollapseTransition from 'element-ui/lib/transitions/collapse-transition'
 import SelectorCard from '@/components/selector/SelectorCard.vue'
-import chinaMapJSON from '@/assets/js/china_area.js'
-import { provinceListMapExp } from '@/assets/js/selector.js'
-import { debounce, getRandomString } from '@/utils/'
+import AreaSelectorContent from '@/components/selector/AreaSelectorContent.vue'
 export default {
-  name: 'area-selector-card',
+  name: 'area-selector',
   components: {
-    [Input.name]: Input,
-    [Icon.name]: Icon,
-    [CollapseTransition.name]: CollapseTransition,
-    SelectorCard
+    SelectorCard,
+    AreaSelectorContent
   },
   props: {
+    selectorType: {
+      type: String,
+      default: 'card'
+    },
     // 初始化城市数据
     // 刚进入页面需要被选中的城市数据
     initCityMap: {
       type: Object,
       default () {
-        return {
-          // '北京': [],
-          // '安徽': [],
-          // '广东': [
-          //     '揭阳市',
-          //     '茂名市',
-          //     '韶关市'
-          // ],
-          // '河北': [
-          //     '邯郸市',
-          //     '秦皇岛市',
-          //     '保定市'
-          // ],
-          // '福建': [
-          //     '福州市',
-          //     '厦门市',
-          //     '宁德市'
-          // ],
-          // '重庆': []
-        }
+        return {}
       }
     }
   },
-  data () {
-    return {
-      searchContent: '',
-      loading: false,
-      // 省份与字母IndexBar对照表
-      provinceListMapExp,
-      provinceListMap: {
-        // A: [
-        //     {
-        //         name: '安徽',
-        //         expanded: false,
-        //         canExpanded: true,
-        //         selectedState: '',
-        //         children: []
-        //     }
-        // ]
-      },
-      // indexBar数据
-      indexList: [],
-      provinceExp: {
-        name: '',
-        // 展开状态
-        expanded: false,
-        // 是否可以展开
-        canExpanded: false,
-        // 选中状态: half(半选)、checked(全选)、''(未选中)、checkeddisabled(全选不能点击)、nonedisabled(未选不能点击)
-        selectedState: '',
-        children: [],
-        id: ''
-      }
-    }
-  },
-  watch: {
-    initCityMap (newVal, oldVal) {
-      this.setCitySelected(newVal)
-    },
-    searchContent: debounce(function (newVal, oldVal) {
-      const search = newVal
-      const { findP } = this.getProvinceWithString(search)
-      const id = findP[0] && findP[0].id
-      if (id) {
-        if (findP[0].canExpanded) {
-          findP[0].expanded = true
-        }
-        this.$nextTick(() => {
-          const wrapper = document.querySelector('.area-selector.card')
-          this.$refs.selectList.scrollTop = wrapper.querySelector(`.${id}`).offsetTop
-          // document.querySelector(`.${id}`).scrollIntoView() // 兼容性有问题
-        })
-      }
-    }, 300)
-  },
-  created () {
-    this.initIndexBarAndAreaMap()
-    this.provinceListMap['#'][0].selectedState = 'checked'
-    this.setCitySelected(this.initCityMap)
-  },
+  created () {},
   methods: {
-    changeLoadingState (s) {
-      this.loading = s
-    },
-    // 整理城市数据列表(并初始化indexBar数据)
-    initIndexBarAndAreaMap () {
-      // 整理数据得到indexListMap(),同时获得indexList
-      const provinceListMap = {}
-      const indexList = []
-      for (const key in this.provinceListMapExp) {
-        const areaArr = []
-        indexList.push(key)
-        this.provinceListMapExp[key].forEach(pName => {
-          const provinceExp = JSON.parse(JSON.stringify(this.provinceExp))
-
-          provinceExp.name = pName
-          provinceExp.id = `ap-${getRandomString(8).toLowerCase()}`
-
-          if (pName !== '全国') {
-            const cities = this.getCitiesFromJSONMap(pName)
-            // console.log(pName, cities)
-            // 筛选掉直辖市和特别行政区(台湾省也不不需要展开)
-            if (cities.ProRemark === '省份' || cities.ProRemark === '自治区') {
-              if (cities.ProID === 32) {
-                provinceExp.children = []
-                provinceExp.canExpanded = false
-              } else {
-                cities.city.forEach(c => {
-                  // 将市区重组成一个新的对象
-                  return provinceExp.children.push({
-                    city: c.name,
-                    selected: false,
-                    canSelected: true,
-                    id: `ac-${getRandomString(8).toLowerCase()}`
-                  })
-                })
-              }
-            } else {
-              provinceExp.children = []
-              provinceExp.canExpanded = false
-            }
-          }
-
-          provinceExp.canExpanded = provinceExp.children.length !== 0
-          areaArr.push(provinceExp)
-        })
-
-        provinceListMap[key] = areaArr
-      }
-
-      this.provinceListMap = provinceListMap
-      this.indexList = indexList
-
-      // 给provinceListMap赋值
-      for (const k in provinceListMap) {
-        this.$set(this.provinceListMap, k, provinceListMap[k])
-      }
-    },
-    // 循环chinaMapJSON,找到对应省下面对应的市
-    getCitiesFromJSONMap (provinceName) {
-      return chinaMapJSON.find(item => item.name.indexOf(provinceName) !== -1)
-    },
-    // 输入字符串,找到其所在省份
-    getProvinceWithString (s = '') {
-      // 找是否有省份相同的
-      const findP = [] // 匹配到的省份数组
-      const findC = [] // 匹配到的市数组
-      if (s) {
-        for (const key in this.provinceListMap) {
-          const item = this.provinceListMap[key]
-          for (let i = 0; i < item.length; i++) {
-            if (item[i].name.includes(s)) {
-              findP.push(item[i])
-            }
-            if (Array.isArray(item[i].children) && item[i].children.length !== 0) {
-              item[i].children.forEach(city => {
-                if (city.city.includes(s)) {
-                  findP.push(item[i])
-                  findC.push(city)
-                }
-              })
-            }
-          }
-        }
-      }
-      return {
-        findP,
-        findC
-      }
-    },
-    // 控制城市盒子展开和收起
-    changeExpandState (e, province) {
-      if (!province.canExpanded) return
-      province.expanded = !province.expanded
-    },
-    // 城市选择按钮点击事件
-    // 根据城市的选择情况判断省份的选择情况
-    changeCityState (province, city) {
-      if (city === '#') {
-        return this.setCitySelected()
-      }
-      // 全国置为空
-      this.provinceListMap['#'][0].selectedState = ''
-      city.selected = !city.selected
-
-      // 判断省份的选择状态
-      let count = 0
-      const cityLength = province.children.length
-      if (cityLength) {
-        province.children.forEach(v => {
-          // 前提是可点击的
-          if (v.canSelected && v.selected) {
-            count++
-          }
-        })
-      } else {
-        // 直辖市或自治区
-        province.canExpanded = false
-        province.expanded = false
-      }
-
-      // 选中状态: half(半选)、checked(全选)、''(未选中)、checkeddisabled(全选不能点击)、nonedisabled(未选不能点击)
-      if (count === 0) {
-        province.selectedState = ''
-      } else if (count < cityLength) {
-        province.selectedState = 'half'
-      } else if (count === cityLength) {
-        province.selectedState = 'checked'
-      } else {
-        province.selectedState = ''
-      }
-
-      const pState = this.checkAllProvinceState()
-      // 如果所有省份被全选,则取消所有选中,让全国选中
-      if (pState.allSelected || pState.noSelected) {
-        this.setCitySelected()
-      }
-    },
-    // 省份checkbox点击事件
-    clickCheckbox (province) {
-      const state = province.selectedState
-      if (state === 'checkeddisabled' || state === 'nonedisabled') return
-
-      // 全国置为空
-      this.provinceListMap['#'][0].selectedState = ''
-      if (state === '' || state === 'half') {
-        province.children.forEach(v => (v.selected = true))
-        province.selectedState = 'checked'
-      } else {
-        province.children.forEach(v => (v.selected = false))
-        province.selectedState = ''
-      }
-
-      const pState = this.checkAllProvinceState()
-      // 如果所有省份被全选,则取消所有选中,让全国选中
-      if (pState.allSelected || pState.noSelected) {
-        this.setCitySelected()
-      }
-    },
-    // 检查是否所有省份按钮被全选中
-    // 全部被全选->返回true
-    checkAllProvinceState () {
-      const stateArr = []
-      for (const key in this.provinceListMap) {
-        this.provinceListMap[key].forEach(item => {
-          if (item.name !== '全国') {
-            if (item.selectedState === '') {
-              stateArr.push('checked')
-            } else if (item.selectedState === 'checked') {
-              stateArr.push('unchecked')
-            } else {
-              stateArr.push('other')
-            }
-          }
-        })
-      }
-      // 统计不同状态的个数
-      const counter = {
-        checked: 0,
-        unchecked: 0,
-        other: 0
-      }
-      for (let i = 0; i < stateArr.length; i++) {
-        const k = stateArr[i]
-        if (counter[k]) {
-          counter[k] += 1
-        } else {
-          counter[k] = 1
-        }
-      }
-      // console.log(counter)
-      return {
-        state: stateArr,
-        allSelected: counter.checked === stateArr.length,
-        noSelected: counter.unchecked === stateArr.length
-      }
-    },
-    // 初始化选中城市数据
     setCitySelected (data) {
-      // 设置全国
-      if (!data || Object.keys(data).length === 0) {
-        // 其他全部设置不选中,全国设置选中
-        for (const key in this.provinceListMap) {
-          this.provinceListMap[key].forEach(item => {
-            item.selectedState = ''
-            item.children.forEach(iitem => {
-              iitem.selected = false
-            })
-            if (item.name === '全国') {
-              item.selectedState = 'checked'
-            }
-          })
-        }
-      } else {
-        // 先将所有城市选择取消
-        this.setCitySelected()
-        // 设置某几个省份被选中
-        for (const key in this.provinceListMap) {
-          this.provinceListMap[key].forEach(item => {
-            const selectCityArr = data[item.name]
-            if (Array.isArray(selectCityArr)) {
-              if (selectCityArr.length === 0) {
-                // 全省被选中
-                item.children.forEach(iitem => {
-                  iitem.selected = true
-                })
-                item.selectedState = 'checked'
-              } else {
-                // 省份中的某些市被选中
-                item.children.forEach(iitem => {
-                  if (selectCityArr.indexOf(iitem.city) !== -1) {
-                    iitem.selected = true
-                  }
-                })
-                item.selectedState = 'half'
-              }
-            }
-
-            if (item.name === '全国') {
-              item.selectedState = ''
-            }
-          })
-        }
-      }
+      return this.$refs.areaSelectorContent.setCitySelected(data)
     },
-    // 获取当前选中城市数据
     getSelectedCity () {
-      const counter = {}
-      // 判断是否全国被选中
-      if (this.provinceListMap['#'][0].selectedState === 'checked') {
-        return counter
-      }
-
-      // 全国没有被选中,排除循环全国
-      for (const key in this.provinceListMap) {
-        if (key === '#') continue
-        this.provinceListMap[key].forEach(item => {
-          // 当前省份下被选中的城市数量
-          const selectedCityArr = []
-          const cityTotalCount = item.children.length
-          item.children.forEach(iitem => {
-            if (iitem.selected && iitem.canSelected) {
-              selectedCityArr.push(iitem.city)
-            }
-          })
-          // 计算出当前省份下的城市是否被全选了
-          if (cityTotalCount === selectedCityArr.length && item.selectedState === 'checked') {
-            // 城市被全选
-            counter[item.name] = []
-          } else {
-            if (selectedCityArr.length !== 0) {
-              counter[item.name] = selectedCityArr
-            }
-          }
-        })
-      }
-
-      return counter
-    },
-    // 统计城市分布数量
-    getCityCount () {
-      const selectedCount = {
-        // 全国被选中时,country为1
-        country: 1,
-        province: 0,
-        city: {
-          // 一共选了多少个城市
-          totalCount: 0,
-          // 分布在几个省份
-          pCount: 0
-        }
-      }
-
-      const selected = this.getSelectedCity()
-      if (Object.keys(selected).length === 0) {
-        // 全国
-      } else {
-        selectedCount.country = 0
-        for (const p in selected) {
-          if (selected[p].length === 0) {
-            selectedCount.province++
-          } else {
-            selectedCount.city.pCount++
-            selected[p].forEach(() => {
-              selectedCount.city.totalCount++
-            })
-          }
-        }
-      }
-
-      const tipText = {
-        p: selectedCount.province === 0 ? '' : selectedCount.province + '个省',
-        c: selectedCount.city.totalCount === 0 ? '' : selectedCount.city.totalCount + '个市',
-        s: selectedCount.city.pCount === 1 ? '' : '(分布在' + selectedCount.city.pCount + '个省内)',
-        text: ''
-      }
-
-      if (selectedCount.province === 1) {
-        tipText.text = '全国'
-      } else {
-        let dot = ''
-        if (selectedCount.city.totalCount !== 0 && selectedCount.province !== 0) {
-          dot = '、'
-        }
-        if (selectedCount.city.totalCount === 0 || selectedCount.city.totalCount === 1) {
-          tipText.s = ''
-        }
-        tipText.text = tipText.p + dot + tipText.c + tipText.s
-      }
-
-      return {
-        data: selectedCount,
-        zh: tipText
-      }
+      return this.$refs.areaSelectorContent.getSelectedCity()
     },
     onCancel () {
       this.$emit('onCancel')
@@ -506,86 +52,18 @@ export default {
     onConfirm () {
       const selectedCity = this.getSelectedCity()
       this.$emit('onConfirm', selectedCity)
+    },
+    onChange (city) {
+      this.$emit('onChange', city)
     }
   }
 }
 </script>
 
 <style lang="scss" scoped>
-  .j-checkbox {
-    width: 18px;
-    height: 18px;
-    border-radius: 50%;
-    border: 1px solid #e0e0e0;
-    cursor: pointer;
-    &.checked {
-      border: 0;
-      background: url('~@/assets/images/icon/checked.png') no-repeat;
-      background-size: 20px;
-      &[disabled] {
-        background: url('~@/assets/images/icon/checked_disabled.png') no-repeat;
-        background-size: 100%;
-      }
-    }
-    &.half {
-      border: 0;
-      background: url('~@/assets/images/icon/checked-half.png') no-repeat;
-      background-size: 20px;
-    }
-  }
-
-  .j-button-item {
-    &.global {
-      padding: 6px 8px;
-      height: 24px;
-      line-height: 24px;
-      font-weight: 700;
-      color: inherit;
-      border-color: rgba(0,0,0,.05);
-    }
-  }
-
-  [class^=el-icon-] {
-    transition: transform 0.2s ease;
-  }
-  .rotate180 {
-    transform: rotate(180deg);
-  }
-
-  .j-button-item {
-    border-color: transparent;
-  }
-
-  .select-group {
-    font-size: 14px;
-    &.global {
-      padding: 0 18px;
-    }
-    .tab {
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-      padding: 0 18px;
-      height: 40px;
-      border-bottom: 1px solid rgba(0,0,0,.05);
-      cursor: pointer;
-    }
-    .tab-name-container {
-      display: flex;
-      align-items: center;
-      .tab-name {
-        margin-left: 10px;
-        font-weight: bold;
-      }
-    }
-    .tab-content {
-      padding: 0 18px;
-      // border-bottom: 1px solid rgba(0,0,0,.05);
-      .content-list {
-        display: flex;
-        flex-wrap: wrap;
-        padding: 2px 0;
-      }
+  .s-line {
+    .s-header {
+      line-height: 36px;
     }
   }
 </style>

+ 947 - 0
src/components/selector/AreaSelectorContent.vue

@@ -0,0 +1,947 @@
+<template>
+  <div class="selector-content" v-if="selectorType === 'card'" key="s-content">
+    <div class="search-container">
+      <el-input v-model.trim="searchContent" placeholder="搜索" prefix-icon="el-icon-search"></el-input>
+    </div>
+    <div class="select-list scrollbar" ref="selectList">
+      <div v-for="(item, key) in provinceListMap" :key="key" class="select-group-container">
+        <div class="index-anchor" :id="key" :data-index="key" v-if="key !== '#'">{{ key }}</div>
+        <el-collapse-transition v-for="(province, ii) in item" :key="ii*2">
+          <div class="select-group tab-content global" v-if="province.name == '全国'">
+            <button
+              class="j-button-item global"
+              :class="{
+                active: province.selectedState,
+                [province.id]: true
+              }"
+              @click="changeCityState(province, '#')"
+            >{{ province.name }}</button>
+          </div>
+          <div class="select-group" v-else>
+            <div class="tab" :class="province.id">
+              <div class="tab-name-container" @click="clickCheckbox(province)">
+                <div class="j-checkbox" :class="province.selectedState"></div>
+                <span class="tab-name">{{ province.name }}</span>
+              </div>
+              <span style="flex: 1; height: 100%;" @click="changeExpandState($event, province)"></span>
+              <span
+                v-if="province.canExpanded"
+                @click="changeExpandState($event, province)">
+                <i
+                  class="el-icon-arrow-down"
+                  :class="{
+                    rotate180: province.expanded
+                  }"></i>
+              </span>
+            </div>
+            <el-collapse-transition>
+              <div v-show="province.expanded" class="tab-content">
+                <div class="content-list">
+                  <button v-for="(city, iii) in province.children" :key="iii*3"
+                    class="j-button-item"
+                    :class="{
+                      active: city.selected,
+                      [city.id]: true
+                    }"
+                    :disabled="!city.canSelected"
+                    @click="changeCityState(province, city)"
+                    >{{ city.city }}</button>
+                </div>
+              </div>
+            </el-collapse-transition>
+          </div>
+        </el-collapse-transition>
+      </div>
+    </div>
+  </div>
+  <div class="selector-content" v-else-if="selectorType === 'line'" key="s-content">
+    <div class="selected-list">
+      <el-tag
+        type="plain"
+        closable
+        v-for="tag in selectedTagList"
+        :key="tag"
+        @close="tagClose(tag)"
+      >{{ tag }}</el-tag>
+    </div>
+    <div class="select-list" ref="selectList">
+      <div class="index-item" :data-index="key" :ref="'index-item-' + key" v-for="(item, key) in provinceListMap" :key="key">
+        <span class="index-bar" v-if="key !== '#'">{{ key }}</span>
+        <div
+          class="province-item"
+          v-for="(province, ii) in item"
+          :class="{
+            expand: province.expanded && province.canExpanded
+          }"
+          :key="ii*2"
+          @click="changeExpandStateForLine($event, province)"
+        >{{ province.name }}</div>
+      </div>
+      <div class="city-list" ref="cityList" v-show="expandedCitiesShow">
+        <div class="city-list-content">
+          <div
+            class="city-item"
+            :class="{
+              active: expandedProvince.selectedState === 'checked'
+            }"
+            @click="clickProvinceInCityListForLine(expandedProvince)"
+          >{{ expandedProvince.name }}</div>
+          <div
+            class="city-item"
+            :class="{
+              active: city.selected
+            }"
+            v-for="(city, iii) in expandedProvince.children"
+            :key="iii"
+            @click="changeCityStateForLine(expandedProvince, city)"
+          >{{ city.city }}</div>
+        </div>
+        <div class="city-list-footer">
+          <button class="confirm" @click="confirmCitySelected">确定</button>
+          <button class="cancel" @click="cancelCitySelected">取消</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Input, Icon, Tag } from 'element-ui'
+import 'element-ui/lib/theme-chalk/base.css'
+import CollapseTransition from 'element-ui/lib/transitions/collapse-transition'
+import chinaMapJSON from '@/assets/js/china_area.js'
+import { provinceListMapExp } from '@/assets/js/selector.js'
+import { debounce, getRandomString } from '@/utils/'
+export default {
+  name: 'area-selector-content',
+  components: {
+    [Input.name]: Input,
+    [Icon.name]: Icon,
+    [Tag.name]: Tag,
+    [CollapseTransition.name]: CollapseTransition
+  },
+  props: {
+    selectorType: {
+      type: String,
+      default: 'card' // card/line
+    },
+    // 初始化城市数据
+    // 刚进入页面需要被选中的城市数据
+    initCityMap: {
+      type: Object,
+      default () {
+        return {
+          // '北京': [],
+          // '安徽': [],
+          // '广东': [
+          //     '揭阳市',
+          //     '茂名市',
+          //     '韶关市'
+          // ],
+          // '河北': [
+          //     '邯郸市',
+          //     '秦皇岛市',
+          //     '保定市'
+          // ],
+          // '福建': [
+          //     '福州市',
+          //     '厦门市',
+          //     '宁德市'
+          // ],
+          // '重庆': []
+        }
+      }
+    }
+  },
+  data () {
+    return {
+      searchContent: '',
+      // 省份与字母IndexBar对照表
+      provinceListMapExp,
+      provinceListMap: {
+        // A: [
+        //     {
+        //         name: '安徽',
+        //         expanded: false,
+        //         canExpanded: true,
+        //         selectedState: '',
+        //         children: []
+        //     }
+        // ]
+      },
+      // indexBar数据
+      indexList: [],
+      provinceExp: {
+        name: '',
+        // 展开状态
+        expanded: false,
+        // 是否可以展开
+        canExpanded: false,
+        // 选中状态: half(半选)、checked(全选)、''(未选中)、checkeddisabled(全选不能点击)、nonedisabled(未选不能点击)
+        selectedState: '',
+        children: [],
+        id: ''
+      },
+      // line状态下,当前被展开省的省份列表
+      expandedProvince: {
+        children: []
+      },
+      selectedCity: {},
+      selectedTagList: ['全国']
+    }
+  },
+  computed: {
+    expandedCitiesShow () {
+      if (!this.expandedProvince) return false
+      return this.expandedProvince.children.length
+    }
+  },
+  watch: {
+    initCityMap (newVal, oldVal) {
+      this.setCitySelected(newVal)
+    },
+    searchContent: debounce(function (newVal, oldVal) {
+      const search = newVal
+      const { findP } = this.getProvinceWithString(search)
+      const id = findP[0] && findP[0].id
+      if (id) {
+        if (findP[0].canExpanded) {
+          findP[0].expanded = true
+        }
+        this.$nextTick(() => {
+          const wrapper = document.querySelector('.area-selector.s-card')
+          this.$refs.selectList.scrollTop = wrapper.querySelector(`.${id}`).offsetTop
+          // document.querySelector(`.${id}`).scrollIntoView() // 兼容性有问题
+        })
+      }
+    }, 300)
+  },
+  created () {
+    this.initIndexBarAndAreaMap()
+    this.provinceListMap['#'][0].selectedState = 'checked'
+    this.setCitySelected(this.initCityMap)
+    window.data = this
+  },
+  methods: {
+    // 整理城市数据列表(并初始化indexBar数据)
+    initIndexBarAndAreaMap () {
+      // 整理数据得到indexListMap(),同时获得indexList
+      const provinceListMap = {}
+      const indexList = []
+      for (const key in this.provinceListMapExp) {
+        const areaArr = []
+        indexList.push(key)
+        this.provinceListMapExp[key].forEach(pName => {
+          const provinceExp = JSON.parse(JSON.stringify(this.provinceExp))
+
+          provinceExp.name = pName
+          provinceExp.id = `ap-${getRandomString(8).toLowerCase()}`
+
+          if (pName !== '全国') {
+            const cities = this.getCitiesFromJSONMap(pName)
+            // console.log(pName, cities)
+            // 筛选掉直辖市和特别行政区(台湾省也不不需要展开)
+            if (cities.ProRemark === '省份' || cities.ProRemark === '自治区') {
+              if (cities.ProID === 32) {
+                provinceExp.children = []
+                provinceExp.canExpanded = false
+              } else {
+                cities.city.forEach(c => {
+                  // 将市区重组成一个新的对象
+                  return provinceExp.children.push({
+                    city: c.name,
+                    selected: false,
+                    canSelected: true,
+                    id: `ac-${getRandomString(8).toLowerCase()}`
+                  })
+                })
+              }
+            } else {
+              provinceExp.children = []
+              provinceExp.canExpanded = false
+            }
+          }
+
+          provinceExp.canExpanded = provinceExp.children.length !== 0
+          areaArr.push(provinceExp)
+        })
+
+        provinceListMap[key] = areaArr
+      }
+
+      this.provinceListMap = provinceListMap
+      this.indexList = indexList
+
+      // 给provinceListMap赋值
+      for (const k in provinceListMap) {
+        this.$set(this.provinceListMap, k, provinceListMap[k])
+      }
+    },
+    // 循环chinaMapJSON,找到对应省下面对应的市
+    getCitiesFromJSONMap (provinceName) {
+      return chinaMapJSON.find(item => item.name.indexOf(provinceName) !== -1)
+    },
+    // 输入字符串,找到其所在省份
+    getProvinceWithString (s = '') {
+      // 找是否有省份相同的
+      const findP = [] // 匹配到的省份数组
+      const findC = [] // 匹配到的市数组
+      if (s) {
+        for (const key in this.provinceListMap) {
+          const item = this.provinceListMap[key]
+          for (let i = 0; i < item.length; i++) {
+            if (item[i].name.includes(s)) {
+              findP.push(item[i])
+            }
+            if (Array.isArray(item[i].children) && item[i].children.length !== 0) {
+              item[i].children.forEach(city => {
+                if (city.city.includes(s)) {
+                  findP.push(item[i])
+                  findC.push(city)
+                }
+              })
+            }
+          }
+        }
+      }
+      return {
+        findP,
+        findC
+      }
+    },
+    // 控制城市盒子展开和收起(card)
+    changeExpandState (e, province) {
+      if (!province.canExpanded) return
+      province.expanded = !province.expanded
+    },
+    // 控制城市盒子展开和收起(line)
+    changeExpandStateForLine (e, province) {
+      if (province.name === this.expandedProvince.name) return
+      // 循环,将其他全部置为false
+      for (const key in this.provinceListMap) {
+        this.provinceListMap[key].forEach(item => {
+          item.expanded = false
+        })
+      }
+      this.expandedProvince = {
+        children: []
+      }
+      province.expanded = true
+      console.log(province, province.name)
+      // 省份数据与原数据分离(点击确定覆盖原数据,点击取消则不保存数据)
+      this.expandedProvince = JSON.parse(JSON.stringify(province))
+      // 如果直接点击直辖市
+      if (province.children.length === 0) {
+        if (province.name === '全国') {
+          this.setCitySelected()
+        } else {
+          this.provinceListMap['#'][0].selectedState = ''
+          this.expandedProvince.selectedState = 'checked'
+        }
+        this.confirmCitySelected()
+      } else {
+        this.moveTheCityContainer(e)
+      }
+    },
+    moveTheCityContainer (e) {
+      // 距离最近一个定位元素的高度6,44,80. 一个公差为36的等差数列
+      const offsetTop = e.target.offsetTop
+      const lineFirstDom = this.getLineFirstDom()
+      const selectList = this.$refs.selectList
+      const cityList = this.$refs.cityList
+      // 如果页面上cityList插入中间(除了插入到末尾),则都会产生误差,所以需要根据情况减去其高度
+      let correct = cityList.getBoundingClientRect().height
+      // 最后一行则不减去correct
+      if (cityList.getBoundingClientRect().top > 36 * lineFirstDom.length + 6) {
+        correct = 0
+      }
+      const line = Math.floor((offsetTop - correct) / 36) + 1
+      if (line >= lineFirstDom.length) {
+        selectList.appendChild(cityList) // 往列表末尾插入元素
+      } else if (line <= 1) {
+        selectList.insertBefore(cityList, lineFirstDom[1])
+      } else {
+        selectList.insertBefore(cityList, lineFirstDom[line])
+      }
+      // 如果点击的是第一行,则要求插入下一行的前面
+      // absolute定位下可以使用
+      // this.$nextTick(() => {
+      //   const selectList = this.$refs.selectList
+      //   const cityList = this.$refs.cityList
+      //   if (cityList) {
+      //     cityList.style.top = `${e.target.getBoundingClientRect().top - selectList.getBoundingClientRect().top + 26}px`
+      //   }
+      // })
+    },
+    getLineFirstDom () {
+      if (this.selectorType !== 'line') return
+      const indexTopList = []
+      const lineFirstDom = [
+        ...this.$refs[`index-item-${this.indexList[0]}`]
+      ]
+      this.indexList.forEach(item => {
+        const ref = this.$refs[`index-item-${item}`]
+        if (ref && ref[0]) {
+          indexTopList.push(ref[0].getBoundingClientRect().top)
+        }
+      })
+      for (let i = 0; i < indexTopList.length; i++) {
+        if (indexTopList[i + 1] > indexTopList[i]) {
+          lineFirstDom.push(
+            ...this.$refs[`index-item-${this.indexList[i + 1]}`]
+          )
+        }
+      }
+      return lineFirstDom
+    },
+    // 城市选择按钮点击事件(card)
+    // 根据城市的选择情况判断省份的选择情况
+    changeCityState (province, city) {
+      if (city === '#') {
+        return this.setCitySelected()
+      }
+      // 全国置为空
+      this.provinceListMap['#'][0].selectedState = ''
+      city.selected = !city.selected
+
+      // 判断省份的选择状态
+      let count = 0
+      const cityLength = province.children.length
+      if (cityLength) {
+        province.children.forEach(v => {
+          // 前提是可点击的
+          if (v.canSelected && v.selected) {
+            count++
+          }
+        })
+      } else {
+        // 直辖市或自治区
+        province.canExpanded = false
+        province.expanded = false
+      }
+
+      // 选中状态: half(半选)、checked(全选)、''(未选中)、checkeddisabled(全选不能点击)、nonedisabled(未选不能点击)
+      if (count === 0) {
+        province.selectedState = ''
+      } else if (count < cityLength) {
+        province.selectedState = 'half'
+      } else if (count === cityLength) {
+        province.selectedState = 'checked'
+      } else {
+        province.selectedState = ''
+      }
+
+      const pState = this.checkAllProvinceState()
+      // 如果所有省份被全选,则取消所有选中,让全国选中
+      if (pState.allSelected || pState.noSelected) {
+        this.setCitySelected()
+      }
+    },
+    // 城市选择按钮点击事件(card)
+    // 根据城市的选择情况判断省份的选择情况
+    changeCityStateForLine (province, city) {
+      this.provinceListMap['#'][0].selectedState = ''
+      province.selectedState = ''
+      city.selected = !city.selected
+      // 判断省份的选择状态
+      let count = 0
+      const cityLength = province.children.length
+      if (cityLength) {
+        province.children.forEach(v => {
+          // 前提是可点击的
+          if (v.canSelected && v.selected) {
+            count++
+          }
+        })
+      }
+      if (count === cityLength) {
+        // line状态下 ,城市全部选中,则只选中省份即可
+        province.selectedState = 'checked'
+        province.children.forEach(item => (item.selected = false))
+      }
+    },
+    // 省份checkbox点击事件(card)
+    clickCheckbox (province) {
+      const state = province.selectedState
+      if (state === 'checkeddisabled' || state === 'nonedisabled') return
+
+      // 全国置为空
+      this.provinceListMap['#'][0].selectedState = ''
+      if (state === '' || state === 'half') {
+        province.children.forEach(v => (v.selected = true))
+        province.selectedState = 'checked'
+      } else {
+        province.children.forEach(v => (v.selected = false))
+        province.selectedState = ''
+      }
+
+      const pState = this.checkAllProvinceState()
+      // 如果所有省份被全选,则取消所有选中,让全国选中
+      if (pState.allSelected || pState.noSelected) {
+        this.setCitySelected()
+      }
+    },
+    // 省份点击事件(城市列表中的省份按钮)(line)
+    clickProvinceInCityListForLine (province) {
+      const state = province.selectedState
+      province.children.forEach(v => (v.selected = false))
+      if (state === 'checked') {
+        province.selectedState = ''
+      } else {
+        province.selectedState = 'checked'
+      }
+    },
+    // 检查是否所有省份按钮被全选中
+    // 全部被全选->返回true
+    checkAllProvinceState () {
+      const stateArr = []
+      for (const key in this.provinceListMap) {
+        this.provinceListMap[key].forEach(item => {
+          if (item.name !== '全国') {
+            if (item.selectedState === '') {
+              stateArr.push('checked')
+            } else if (item.selectedState === 'checked') {
+              stateArr.push('unchecked')
+            } else {
+              stateArr.push('other')
+            }
+          }
+        })
+      }
+      // 统计不同状态的个数
+      const counter = {
+        checked: 0,
+        unchecked: 0,
+        other: 0
+      }
+      for (let i = 0; i < stateArr.length; i++) {
+        const k = stateArr[i]
+        if (counter[k]) {
+          counter[k] += 1
+        } else {
+          counter[k] = 1
+        }
+      }
+      // console.log(counter)
+      return {
+        state: stateArr,
+        allSelected: counter.checked === stateArr.length,
+        noSelected: counter.unchecked === stateArr.length
+      }
+    },
+    // 初始化选中城市数据(card/line共用)
+    setCitySelected (data) {
+      // 设置全国
+      if (!data || Object.keys(data).length === 0) {
+        // 其他全部设置不选中,全国设置选中
+        for (const key in this.provinceListMap) {
+          this.provinceListMap[key].forEach(item => {
+            item.selectedState = ''
+            item.children.forEach(iitem => {
+              iitem.selected = false
+            })
+            if (item.name === '全国') {
+              item.selectedState = 'checked'
+            }
+          })
+        }
+      } else {
+        // 先将所有城市选择取消
+        this.setCitySelected()
+        // 设置某几个省份被选中
+        for (const key in this.provinceListMap) {
+          this.provinceListMap[key].forEach(item => {
+            const selectCityArr = data[item.name]
+            if (Array.isArray(selectCityArr)) {
+              if (selectCityArr.length === 0) {
+                // 全省被选中
+                if (this.selectorType === 'line') {
+                  // line状态下,全省被选中,则其下城市不需要被选中
+                } else {
+                  item.children.forEach(iitem => {
+                    iitem.selected = true
+                  })
+                }
+
+                item.selectedState = 'checked'
+              } else {
+                // 省份中的某些市被选中
+                item.children.forEach(iitem => {
+                  if (selectCityArr.indexOf(iitem.city) !== -1) {
+                    iitem.selected = true
+                  }
+                })
+                item.selectedState = 'half'
+              }
+            }
+
+            if (item.name === '全国') {
+              item.selectedState = ''
+            }
+          })
+        }
+      }
+    },
+    // 获取当前选中城市数据
+    getSelectedCity () {
+      const counter = {}
+      // 判断是否全国被选中
+      if (this.provinceListMap['#'][0].selectedState === 'checked') {
+        return counter
+      }
+
+      // 全国没有被选中,排除循环全国
+      for (const key in this.provinceListMap) {
+        if (key === '#') continue
+        this.provinceListMap[key].forEach(item => {
+          // 当前省份下被选中的城市数量
+          const selectedCityArr = []
+          const cityTotalCount = item.children.length
+          item.children.forEach(iitem => {
+            if (iitem.selected && iitem.canSelected) {
+              selectedCityArr.push(iitem.city)
+            }
+          })
+          if (this.selectorType === 'line') {
+            // 先看是否有城市被选,再看是否省份被选
+            if (selectedCityArr.length) {
+              counter[item.name] = selectedCityArr
+            } else {
+              if (item.selectedState === 'checked') {
+                counter[item.name] = []
+              }
+            }
+          } else {
+            // 计算出当前省份下的城市是否被全选了
+            if (cityTotalCount === selectedCityArr.length && item.selectedState === 'checked') {
+              // 城市被全选
+              counter[item.name] = []
+            } else {
+              if (selectedCityArr.length !== 0) {
+                counter[item.name] = selectedCityArr
+              }
+            }
+          }
+        })
+      }
+      return counter
+    },
+    confirmCitySelected () {
+      // 统计时候有城市被选中了
+      let count = 0
+      const cityLength = this.expandedProvince.children.length
+      if (cityLength) {
+        this.expandedProvince.children.forEach(v => {
+          // 前提是可点击的
+          if (v.canSelected && v.selected) {
+            count++
+          }
+        })
+      }
+      
+      if (this.expandedProvince.selectedState !== 'checked' && cityLength !== 0 && count === 0) {
+        return
+      }
+      // 替换赋值
+      for (const key in this.provinceListMap) {
+        if (key === '#') continue
+        const res = this.provinceListMap[key].find(item => {
+          if (item.name === this.expandedProvince.name) {
+            Object.assign(item, this.expandedProvince)
+          }
+          return item.name === this.expandedProvince.name
+        })
+        if (res) {
+          break
+        }
+      }
+      this.selectedCity = this.getSelectedCity()
+      this.getSelectedTagList(this.selectedCity)
+      this.cancelCitySelected()
+      this.$emit('onChange', this.selectedCity)
+    },
+    cancelCitySelected () {
+      for (const key in this.provinceListMap) {
+        this.provinceListMap[key].forEach(item => {
+          item.expanded = false
+        })
+      }
+      this.expandedProvince = {
+        children: []
+      }
+    },
+    getSelectedTagList (v) {
+      if (Object.keys(v).length === 0) {
+        const arr = ['全国']
+        this.selectedTagList = arr
+        return
+      }
+      const privinceArr = []
+      let cityArr = []
+      for (const key in v) {
+        const item = v[key]
+        if (Array.isArray(item)) {
+          if (item.length === 0) {
+            privinceArr.push(key)
+          } else {
+            cityArr = cityArr.concat(item)
+          }
+        }
+      }
+      this.selectedTagList = privinceArr.concat(cityArr)
+    },
+    tagClose (name) {
+      if (name === '全国') {
+        this.selectedTagList = []
+        return
+      }
+      for (const key in this.selectedCity) {
+        const index = this.selectedCity[key].indexOf(name)
+        if (name === key) {
+          delete this.selectedCity[key]
+          break
+        } else if (index !== -1) {
+          this.selectedCity[key].splice(index, 1)
+          if (this.selectedCity[key].length === 0) {
+            delete this.selectedCity[key]
+          }
+        }
+      }
+      this.setCitySelected(this.selectedCity)
+      this.confirmCitySelected()
+    },
+    // 统计城市分布数量
+    getCityCount () {
+      const selectedCount = {
+        // 全国被选中时,country为1
+        country: 1,
+        province: 0,
+        city: {
+          // 一共选了多少个城市
+          totalCount: 0,
+          // 分布在几个省份
+          pCount: 0
+        }
+      }
+
+      const selected = this.getSelectedCity()
+      if (Object.keys(selected).length === 0) {
+        // 全国
+      } else {
+        selectedCount.country = 0
+        for (const p in selected) {
+          if (selected[p].length === 0) {
+            selectedCount.province++
+          } else {
+            selectedCount.city.pCount++
+            selected[p].forEach(() => {
+              selectedCount.city.totalCount++
+            })
+          }
+        }
+      }
+
+      const tipText = {
+        p: selectedCount.province === 0 ? '' : selectedCount.province + '个省',
+        c: selectedCount.city.totalCount === 0 ? '' : selectedCount.city.totalCount + '个市',
+        s: selectedCount.city.pCount === 1 ? '' : '(分布在' + selectedCount.city.pCount + '个省内)',
+        text: ''
+      }
+
+      if (selectedCount.province === 1) {
+        tipText.text = '全国'
+      } else {
+        let dot = ''
+        if (selectedCount.city.totalCount !== 0 && selectedCount.province !== 0) {
+          dot = '、'
+        }
+        if (selectedCount.city.totalCount === 0 || selectedCount.city.totalCount === 1) {
+          tipText.s = ''
+        }
+        tipText.text = tipText.p + dot + tipText.c + tipText.s
+      }
+
+      return {
+        data: selectedCount,
+        zh: tipText
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .s-card {
+    .j-checkbox {
+      width: 18px;
+      height: 18px;
+      border-radius: 50%;
+      border: 1px solid #e0e0e0;
+      cursor: pointer;
+      &.checked {
+        border: 0;
+        background: url('~@/assets/images/icon/checked.png') no-repeat;
+        background-size: 20px;
+        &[disabled] {
+          background: url('~@/assets/images/icon/checked_disabled.png') no-repeat;
+          background-size: 100%;
+        }
+      }
+      &.half {
+        border: 0;
+        background: url('~@/assets/images/icon/checked-half.png') no-repeat;
+        background-size: 20px;
+      }
+    }
+
+    [class^=el-icon-] {
+      transition: transform 0.2s ease;
+    }
+    .rotate180 {
+      transform: rotate(180deg);
+    }
+
+    .j-button-item {
+      border-color: transparent;
+    }
+
+    .select-group {
+      font-size: 14px;
+      &.global {
+        padding: 0 18px;
+      }
+      .tab {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 0 18px;
+        height: 40px;
+        border-bottom: 1px solid rgba(0,0,0,.05);
+        cursor: pointer;
+      }
+      .tab-name-container {
+        display: flex;
+        align-items: center;
+        .tab-name {
+          margin-left: 10px;
+          font-weight: bold;
+        }
+      }
+      .tab-content {
+        padding: 0 18px;
+        // border-bottom: 1px solid rgba(0,0,0,.05);
+        .content-list {
+          display: flex;
+          flex-wrap: wrap;
+          padding: 2px 0;
+        }
+      }
+    }
+  }
+
+  .s-line {
+    .el-tag--plain {
+      color: $color-text--highlight;
+      border-color: $color-text--highlight;
+      .el-tag__close {
+        color: $color-text--highlight;
+        &:hover {
+          color: #fff;
+          background-color: $color-text--highlight;
+        }
+      }
+    }
+    .s-header {
+      line-height: 30px;
+    }
+    .el-tag {
+      margin: 4px 6px;
+      height: 30px;
+    }
+    .selector-content {
+      .selected-list {}
+      .select-list {
+        position: relative;
+        display: flex;
+        flex-wrap: wrap;
+        .index-item {
+          margin: 6px 0;
+          .index-bar {
+            margin-left: 10px;
+            margin-right: 5px;
+            color: #999;
+          }
+        }
+        .province-item {
+          display: inline-block;
+          padding: 4px 8px;
+          border-radius: 4px;
+          cursor: pointer;
+          &:hover {
+            color: $color-text--highlight;
+          }
+          &.active {
+            color: #fff;
+            background-color: $color-text--highlight;
+          }
+          &.expand {
+            background-color: #f5f5fb;
+            border: 1px solid #e0e0e0;
+            border-bottom-color: transparent;
+            border-bottom-left-radius: 0;
+            border-bottom-right-radius: 0;
+            position: relative;
+            z-index: 2;
+            // transform: translateZ(0px);
+          }
+        }
+      }
+      .city-list {
+        margin-top: -7px;
+        padding: 12px 20px;
+        width: 100%;
+        background-color: #f5f5fb;
+        border-radius: 4px;
+        border: 1px solid #E0E0E0;
+        &.absolute {
+          position: absolute;
+          top: 0;
+          left: 0;
+        }
+        .city-item {
+          display: inline-block;
+          margin: 0 4px 4px;
+          padding: 4px 8px;
+          border-radius: 4px;
+          cursor: pointer;
+          &.active {
+            color: #fff;
+            background-color: $color-text--highlight;
+          }
+        }
+        .city-list-footer {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          margin-top: 12px;
+          button {
+            padding: 4px 16px;
+            font-size: 14px;
+            line-height: 18px;
+            color: #1D1D1D;
+            background-color: #fff;
+            cursor: pointer;
+            border-radius: 4px;
+            border: 1px solid #E0E0E0;
+            &.confirm {
+              margin-right: 15px;
+              color: #fff;
+              background-color: #2CB7CA;
+              border-color: #2CB7CA;
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 1 - 1
src/components/selector/IndustrySelector.vue

@@ -286,7 +286,7 @@ export default {
           giveId = level1.id
           return level1.id
         } else {
-          level1.children.find(level2 => {
+          return level1.children.find(level2 => {
             if (level2.name.includes(s)) {
               giveId = level1.id
               return level2

+ 106 - 75
src/components/selector/SelectorCard.vue

@@ -1,12 +1,18 @@
 <template>
-  <div class="selector-card">
+  <div
+    class="selector-card"
+    :class="{
+      's-card': cardType === 'card',
+      's-line': cardType === 'line',
+    }"
+    >
     <div class="selector-card-header">
       <slot name="header"></slot>
     </div>
     <div class="selector-card-content scrollbar">
       <slot name="default"></slot>
     </div>
-    <div class="selector-card-footer">
+    <div class="selector-card-footer" v-if="cardType === 'card'">
       <slot name="footer">
         <el-button type="primary" class="confirm" @click="onConfirm">保存</el-button>
         <el-button class="cancel" @click="onCancel">取消</el-button>
@@ -22,8 +28,11 @@ export default {
   components: {
     [Button.name]: Button
   },
-  data () {
-    return {}
+  props: {
+    cardType: {
+      type: String,
+      default: 'card'
+    }
   },
   methods: {
     onCancel () {
@@ -37,85 +46,58 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-  .selector-card::v-deep {
-    // 输入框公共样式
-    .el-input__inner {
-      padding-left: 42px;
-      background-color: #f7f7f7;
-      border-radius: 22px;
-    }
-    .search-container {
-      margin: 20px auto;
-      width: 360px;
-      .el-icon-search {
-        margin-left: 6px;
-        font-size: 18px;
-      }
-    }
-
-    // 子组件按钮公共样式
-    .j-button-item {
-      display: flex;
-      align-items: center;
-      margin: 6px 5px;
-      padding: 2px 6px;
-      line-height: 20px;
-      border-radius: 4px;
-      font-size: 14px;
-      text-align: center;
-      color: #606266;
-      background-color: #fff;
-      border: 1px solid rgba(0,0,0,.05);
-      cursor: pointer;
-      &.hover:hover {
-        color: #2abed1;
-      }
-      &.active {
-        color: #2abed1;
-        border-color: #2abed1;
-      }
-    }
-
-    // 子组件卡片布局样式
-    .selector-content {
-      display: flex;
-      flex-direction: column;
-      margin: 0 auto;
-      width: 400px;
-      height: 100%;
-      border-radius: 5px;
-      border: 1px solid rgba(0,0,0,.05);
-    }
-
-    // 搜索框下的列表
-    .select-list {
+  .selector-card {
+    display: flex;
+    color: #1d1d1d;
+    background-color: #fff;
+    .selector-card-content {
       position: relative;
+      display: flex;
       flex: 1;
-      overflow-y: scroll;
     }
-
-    // indexBar样式
-    .index-anchor {
-      margin-top: 10px;
-      padding: 0 20px;
-      height: 30px;
-      line-height: 30px;
-      background-color: #f5f5fb;
-      text-align: left;
+    &::v-deep {
+      // 子组件按钮公共样式
+      .j-button-item {
+        display: flex;
+        align-items: center;
+        margin: 6px 5px;
+        padding: 2px 6px;
+        line-height: 20px;
+        border-radius: 4px;
+        font-size: 14px;
+        text-align: center;
+        color: #606266;
+        background-color: #fff;
+        border: 1px solid rgba(0,0,0,.05);
+        cursor: pointer;
+        &.global {
+          padding: 6px 8px;
+          height: 24px;
+          line-height: 24px;
+          font-weight: 700;
+          color: inherit;
+          border-color: rgba(0,0,0,.05);
+        }
+        &.hover:hover {
+          color: #2abed1;
+        }
+        &.active {
+          color: #2abed1;
+          border-color: #2abed1;
+        }
+      }
     }
   }
 
-  .selector-card {
-    display: flex;
+  .selector-card.s-card {
+    width: 460px;
+    height: 582px;
     flex-direction: column;
     align-items: center;
     justify-content: space-between;
-    width: 460px;
-    height: 582px;
-    color: #1d1d1d;
-    background-color: #fff;
     box-shadow: 0 0 28px rgb(0 0 0 / 16%);
     border-radius: 5px;
+
     .selector-card-header,
     .selector-card-content,
     .selector-card-footer {
@@ -132,10 +114,7 @@ export default {
       border-radius: 5px 5px 0 0;
     }
     .selector-card-content {
-      position: relative;
-      display: flex;
       align-items: center;
-      flex: 1;
       overflow: hidden;
     }
     .selector-card-footer {
@@ -161,5 +140,57 @@ export default {
         margin: 0 20px;
       }
     }
+
+    &::v-deep {
+      // 输入框公共样式
+      .el-input__inner {
+        padding-left: 42px;
+        background-color: #f7f7f7;
+        border-radius: 22px;
+      }
+      .search-container {
+        margin: 20px auto;
+        width: 360px;
+        .el-icon-search {
+          margin-left: 6px;
+          font-size: 18px;
+        }
+      }
+
+      // 子组件卡片布局样式
+      .selector-content {
+        display: flex;
+        flex-direction: column;
+        margin: 0 auto;
+        width: 400px;
+        height: 100%;
+        border-radius: 5px;
+        border: 1px solid rgba(0,0,0,.05);
+      }
+
+      // 搜索框下的列表
+      .select-list {
+        position: relative;
+        flex: 1;
+        overflow-y: scroll;
+      }
+
+      // indexBar样式
+      .index-anchor {
+        margin-top: 10px;
+        padding: 0 20px;
+        height: 30px;
+        line-height: 30px;
+        background-color: #f5f5fb;
+        text-align: left;
+      }
+    }
+  }
+
+  .selector-card.s-line {
+    padding: 12px 40px;
+    .selector-card-header {
+      margin-right: 10px;
+    }
   }
 </style>

+ 43 - 0
src/store/forcast.js

@@ -0,0 +1,43 @@
+import { defaultLocalPageData } from '@/utils/'
+export default {
+  namespaced: true,
+  state: () => ({
+    // 中标企业预测
+    list: defaultLocalPageData('bigmember-BID_DATA', []),
+    historyList: defaultLocalPageData('bigmember-BID_DATA', []),
+    // 投标决策分析
+    policyList: defaultLocalPageData('bigmember-POLICY_DATA', []),
+    historyPolicyList: defaultLocalPageData('bigmember-POLICY_DATA', []),
+    // 企业情报-搜索
+    entList: [],
+    // 企业情报-我关注的项目
+    entObject: {},
+    type: 0
+  }),
+  mutations: {
+    // 中标企业预测
+    setList (state, list) {
+      state.list = list
+      state.historyList = list.concat(state.historyList)
+      localStorage.setItem('bigmember-BID_DATA', JSON.stringify(state.historyList))
+    },
+    setType (state, type) {
+      state.type = type
+    },
+    // 投标决策分析
+    setPolicyList (state, list) {
+      state.policyList = list
+      state.historyPolicyList = list.concat(state.historyPolicyList)
+      localStorage.setItem('bigmember-POLICY_DATA', JSON.stringify(state.historyPolicyList))
+    },
+    setEntFollowSearch (state, list) {
+      state.entList = list
+    },
+    // 企业情报-我关注的项目
+    setEntFollowList (state, obj) {
+      state.entObject = obj
+    }
+  },
+  actions: {},
+  getters: {}
+}

+ 3 - 1
src/store/index.js

@@ -2,6 +2,7 @@ import Vue from 'vue'
 import Vuex from 'vuex'
 
 import user from './user'
+import forcast from './forcast'
 
 Vue.use(Vuex)
 
@@ -13,6 +14,7 @@ export default new Vuex.Store({
   actions: {},
   getters: {},
   modules: {
-    user
+    user,
+    forcast
   }
 })

+ 10 - 2
src/store/user.js

@@ -1,9 +1,17 @@
 export default {
   namespaced: true,
   state: () => ({
-    power: []
+    power: [],
+    info: {}
   }),
-  mutations: {},
+  mutations: {
+    setUserInfo (state, info) {
+      state.info = info
+    },
+    setUserPower (state, power) {
+      state.power = power
+    }
+  },
   actions: {},
   getters: {}
 }

+ 3 - 0
src/utils/globalFunctions.js

@@ -481,3 +481,6 @@ export function bSort (arr, value) {
   }
   return arr
 }
+export function defaultLocalPageData (key, defaultValues = {}) {
+  return localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key) || '') : defaultValues
+}

+ 19 - 3
src/views/bid-forecast/BidForecast.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="bid-forcast">
     <forLayOut>
-      <span class="icon_ai el-icon-arrow-down" slot="icon"></span>
+      <span class="icon_ai" slot="icon"></span>
       <span class="bidfor_text" slot="proname">中标企业预测</span>
       <img class="bidfor_img" src="@/assets/images/item_7.png" alt="" slot="bidImg">
-      <template v-slot:main="data">
-        <ForeCast type="bidfor" title="预测历史" slot="main" :mydata="data.list"></ForeCast>
+      <template v-slot:main>
+        <ForeCast type="bidfor" :title="type==1?'近似项目':'预测历史'" slot="main" :mydata="forcast"></ForeCast>
       </template>
     </forLayOut>
   </div>
@@ -14,11 +14,18 @@
 <script>
 import forLayOut from '@/components/forecast/ForLayout.vue'
 import ForeCast from '@/components/forecast/ForeCast.vue'
+import { mapState } from 'vuex'
 export default {
   name: 'bid-forcast',
   components: {
     ForeCast,
     forLayOut
+  },
+  computed: {
+    ...mapState({
+      forcast: state => state.forcast.list,
+      type: state => state.forcast.type
+    })
   }
 }
 </script>
@@ -26,5 +33,14 @@ export default {
 <style lang="scss" scoped>
 .bid-forcast{
   width: 100%;
+  .icon_ai{
+    display: flex;
+    align-items: center;
+    margin-right: 4px;
+    width: 32px;
+    height: 32px;
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAPOSURBVHgB7Zz/ddowEMe/9PX/thPEG5QN6k7QbFB3AtIJAhOETmB3gjQTJBuETGBvQDe4nJD9AsIg6wdCxPq8p+eHsYT9tXR3OtkAiUQikYiWCSKAiD7zJucy5XLFJWtLR9NuV1xexHYymazwnhGicLnl8kh21FxKLjneE+KCHEQ5JlaBS4YvIONyT6elvkih+KRnXNYUjpJLhtghaWdKOg911CKRHFLPdF5Er71GbJAUp6Z4+AlLvMdBJGOaZ+zGMTHwnWOnJxjyAf65Q3ziCIQHzWCIV4H4BGa8KRAnomffwxBvQ6y9OzXiZ8FDbT70YJ896BaXwcxkqHkRiOR8qIAdT1wqvE1ITfjf1v1nUEcMtbA3k+znVs9bbYigsiYzsq36SzIjQwhIxjy25EpbNwZ1a8fzmCMEZH7nei+wbUv0oqFzNleB1kOuz4cN+gE7FuoO9i7CpvxBGMTNyHUHOQlEchxnMKfBYcNaIRzaeZprD8phx1PbW/bg/Q2kZwvBV90BrgJ9gx07w4v2PcoCYZjqDjhHD3poe8k2O3FJO6kMkZQXdujq2AHWApFMbWYwZ9nTTtFjMP8iDF9wCsgu39Pn2ruk2r2yX+fyXd18R37sOq16EDdqm9JQbU+ONztwTTKXtCGwyz+IsUAkU5g3MKfBvmtXM31quxUuCXJLpZY9bamst3tRe9zjgfaCDLGPMEN4mwx2qK47R3+8M1X2L2Afbw3BTw6L5NqWLSbpiL7f7lsd8dKDdL89yAaRDOTmsGcJN07l8rWx1lAj/QiZaLKhsVlNUKggk2O+0QqktUHce1zsjkB17UJobYgP+YjLRhSx5XrC5fvOBD7ABXJLhgn6vFI5sO5ccy4+bJB2VOiGWAY3VtuzdpK2rBhYd0a7gWMDu7z1IapDGYVtdAKJMeoy9qfKXZoNr7qxeUX3gfZzTxntZgFymOHH8PNJiIlkTfbUJHPNJdlRtvXrA23PudyRGYNjn8ELh9yoMKwZ5J0SiSbx2daznZtfPLwqnBoWraLLozS5xlM8vBA7RtnKsQm06MlmHmVMAjUmDy10jEkgq4WAsQjU2HqtsQhkvYw0BoEal5hnDAI5Jf7HIJBTNtNVoAZxszKNe1RcBaoQN87L104CtXfnN+LlBY442yAWSSTkQz2NYUoDR3w/J51DpkE+KV9334XG6vWD4JB8y/Ac5HBkjOkOI5JAGpJAGpJAGpJAGkIJ1OA8NHAkiECBn33uqFznYUGhsC/61uTpbZ6gf25Cchm6gHy/I4N/xDK5eGJjOWTdPZFIJBIXzivEv8lElEfm8AAAAABJRU5ErkJggg==) no-repeat;
+    background-size: contain;
+  }
 }
 </style>

+ 20 - 2
src/views/bid-policy/BidPolicy.vue

@@ -1,10 +1,12 @@
 <template>
   <div class="bid-policy">
     <forLayOut>
-      <span class="icon_ai el-icon-arrow-down" slot="icon"></span>
+      <span class="icon_toubiao" slot="icon"></span>
       <span class="bidfor_text" slot="proname">投标决策分析</span>
       <img class="bidfor_img" src="@/assets/images/item_5.png" alt="" slot="bidImg">
-      <ForeCast type='bidfor' title="分析历史" slot="main"></ForeCast>
+      <template v-slot:main>
+        <ForeCast type="bidfor" :title="type==1?'近似项目':'分析历史'" slot="main" :mydata="forcast"></ForeCast>
+      </template>
     </forLayOut>
   </div>
 </template>
@@ -12,11 +14,18 @@
 <script>
 import forLayOut from '@/components/forecast/ForLayout.vue'
 import ForeCast from '@/components/forecast/ForeCast.vue'
+import { mapState } from 'vuex'
 export default {
   name: 'bid-policy',
   components: {
     ForeCast,
     forLayOut
+  },
+  computed: {
+    ...mapState({
+      forcast: state => state.forcast.policyList,
+      type: state => state.forcast.type
+    })
   }
 }
 </script>
@@ -24,5 +33,14 @@ export default {
 <style lang="scss" scoped>
 .bid-policy{
   width: 100%;
+  .icon_toubiao{
+    display: flex;
+    align-items: center;
+    margin-right: 4px;
+    width: 32px;
+    height: 32px;
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAATeSURBVHgB7Zs/cNRGFMa/Y9KlcZFxm4OaDKZLFznuCSlSuAqeoU/SkkJyQwv0zEDnFtzDHR1UNmM6ZrijhAYXmPbxHrsL8iGtVrurlezRb+bN3p20u0/fvv0jrQ4YGRkZGRksE/QEEW1wkrFdYVtjm+pUONZ2yPaWbT6ZTA5x3mFRMrY7bB+oPQu2B1IGzht8UTfYZhSPhZSJsw6piJlRdyzYpjiLkOpKqchxVmBn16jbqKljRkOPJnGQVNj3xYIiixRtmteOzaCm6z5Zsm3ysmCJCMQU6ICTDQyDQxboKiJwARFgce5gOOIIG9qn/iG1xhkqGQIJ7mLsxAL9jzt1LNmucnc7hidBXYzU+mOK4TJl+xcBeEcQiyM3lgcYtkCCRM9F3ygKiaDrGL44gjTkDXgSItA/6I852yOoMcaFP5ASUivmvihW/DhwzPczPPCNoAz9sMtjSWG+6NXyrmPeP+GBr0BpQ1ZxSpwSroPvFXjgK9AUaakTR/gbbmRIBaWlsPiRtyjnA1JA6nlPKgrEEcewhq4hvxnsrs63oT+7UCCuOILXTNa1QLOKMoqGPIWl/pz8GaRARU05RZvzdZ6cwkgiUNsxaGYpq0goDiEV1H7jL7eUVaQQh1LNYtrhGbXHJlJmOZZTHGZIBbnPRM4i1dSTUzzuIhWkdkx9cRKJ4oojXEcqSA3UPi8gGHKkFSfd+FO6iIcURo404ggPkBoK62aGHN2LI2TwJGhXg9TMkCEMeTL4ku03dHPHLS9fbcKTUIEyqO3mISPb0HN4ErTtoyt+hOHyMEQcIcbGoTxCeM32E4bFEhFeYgjem9f7TX9heOzEeMPDWSCOlHW2+2wXV4/pMP4Pw2G3qmvpa4i/XVUSZ58sS3Zqfs6TgsLin7kGZ5EaI4gLW+fkNtu6/ukS/3az6lz9YN11G6YLah/us8/b+HYNW64iWQWqEMdwjY9tVeXRDu7AfTsmBlLXjkWca5xsr/zsJFLtLGYRp8wtdupVTf4pJ7LEz9Atc1gGZFJj5j1L/iect/b4hZpCXcQR/qeKQVsQh/UKVqJpifgsoYTZbBDndkM51kiaVBTqKk6Ze+zkE9sJpN6MF0dCX9Wbsz2GWgQeW+qTIUDGyh/hRmUkTVYK9RHHsMcV7DWdROpPLPJsxvXey/yp5RnUfdW8KYMekLfRnu9EWhXoFie/wp8jqGh675pBC2b+7VPmizBtFnu6gSVKf4E/pxp6VaB9hCPi7HMlMcpypjRTuXapOt6z71+XMauD9BHCkVa8qRdlW+gYruOy1IV2442N5+UvVYP075xcgrrQdf05BIkoCdmjNl3PBvsoQkjESAP4jJeGE7Z3bAv9WXx8UT7B6W5e920jlqSX4Sfcc3agadq1+SHCyDjpM8ZI7xAh3uj0xKXBfoADuiCxV9pRibK2r9dKCzXOcg1+nHDdspzwEWivblFrw/dxxye0Q8SRVfcCgXAZTznx2eM6gQe+Ar1pcW40cQw+IvnW7yWQ7nIuLRJdnJIPbURq06CnCHmi+LHheGfiGFqI5NW9hBCBlpZjnYtjcBTJ248Qgd7V/J5MHIODSN7rrxCBqvp1cnEMDSL1Mgattkpv4hgsIqWPIL3oMsvy3sUxVIi0H+sWxwtSOx4xbhKjIj7pW6SRkZGRkfPKZ+LYRHOLukmdAAAAAElFTkSuQmCC) no-repeat;
+    background-size: contain;
+  }
 }
 </style>

+ 20 - 3
src/views/ent-intel/EntIntel.vue

@@ -1,11 +1,13 @@
 <template>
   <div class="ent-intel">
     <forLayOut>
-      <span class="icon_ai el-icon-arrow-down" slot="icon"></span>
+      <span class="icon_ent" slot="icon"></span>
       <span class="bidfor_text" slot="proname">企业情报</span>
       <img class="bidfor_img" src="@/assets/images/item_2.png" alt="" slot="bidImg">
-      <ForeCast type="entintelRes" title="近似企业" slot="main"></ForeCast>
-      <!-- <ForeCast type="entintel" title="我关注的企业" slot="main"></ForeCast> -->
+      <!-- <ForeCast type="entintelRes" title="近似企业" slot="main"></ForeCast> -->
+      <template v-slot:main>
+        <ForeCast type="entintel" title="我关注的企业" slot="main" :myDataObj="forcast"></ForeCast>
+      </template>
     </forLayOut>
   </div>
 </template>
@@ -13,11 +15,17 @@
 <script>
 import forLayOut from '@/components/forecast/ForLayout.vue'
 import ForeCast from '@/components/forecast/ForeCast.vue'
+import { mapState } from 'vuex'
 export default {
   name: 'ent-intel',
   components: {
     ForeCast,
     forLayOut
+  },
+  computed: {
+    ...mapState({
+      forcast: state => state.forcast.entObject
+    })
   }
 }
 </script>
@@ -25,5 +33,14 @@ export default {
 <style lang="scss" scoped>
 .ent-intel{
   width: 100%;
+  .icon_ent{
+    display: flex;
+    align-items: center;
+    margin-right: 6px;
+    width: 36px;
+    height: 36px;
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAADfUlEQVR4Xu2bz4uOURTHv99Qo/wBdixmQSxMsVA2yoIoKWWjzGaiLCh2RkbGQlEslAmh7FAWCsXOQlFmoRCK3SztjEwdnbpTj+n58d57z3tf4z13NVP33vN9P/d77/Pe5z2H8NZKgM6nnYAD6nCIA3JAeYeIO8gd5A7KI+AOyuPnZ5A7yB2URyDXQSIyAuA4gMMANgDQ/1PaDwAfATwBcJvkXMwkIrIWwBEA+wBsAbAmZnyl7wKAWQD3AcyQnG+bp/UMEpF1AJ4FMIl6aoepqGkAl0iq4MYmIisBnAj9UxenaX5dsN0kvzd1aAQUnPOuD3CqWhT+gaZVDBoeBNdYLlB1LoU01qShDZCu2tV+qarMe4vkRF0cEbkDYLyAhtMkr9TFaQP0BsDWAuI0hNr8eTWWiOwH8LhQ/FmSY7GAfgPQ/V+ivSa5fQmgkgs0T3J1LCApQaYSY5TkV/1fREYBfC4Zn2TtbmrbYqUBjZO8FwDp4/yuA/qbwCTJiwHQmfBYL8ZoOThoiuT5AOgcgKlidAA4oA7aDqggoFcAJkjqN9CemojoHe4mgB0tA2K2mLkGSwdtjIGzCCRA+mAEyFyDJaARkr96sk6lU7hX/TQCZK7BEtAxkjMJgI4CuGEEyFyDJSB9PfEwvNvplZOeQQc7ri4xZ5C5BktAvUKJ7RcDKHbuzv4OqOBjvnM1Eju4gzrAOSAHlLi3wjB30LA4yPwelOAgcw2Wj3nze1ACIHMNloDM70EJgMw1WAIyvwclADLXYAnI/B4UAE2TPKt/i8gFAJMth7q5BktAeQ/z5tHfAOwEsCLkA+hPP8XacgBUDEZdIAf0H1xW3UEDJeAOysPvZ5A7yB2UR8AdlMcv5QzSH/mss0rzPkX/Ri+QXFX7BbIppohohqvmIw9De0tyWyygUwAuDwMdACdJXosFpNur33nS/wL/tDzp8NphPYCnfU4mHyQkhbOHpL5JqG2d5VAhK0MTD7RWQ8+kUqnB/QKnZRAKRms1rmfVauQqDHUWWoSyKSRPLRai5EythShawvACwCcAc131HjnBOh2UM3nd2JADfSiUGPT6UkxXXNOCH5H8Yq2pbb7igKpiQtbZXgC7AGwGoG7TpqVS74NLXpJU1wykDRTQQD5xZFAH1AHMATmgyD21pLs7yB3kDsoj4A7K4+dnkDvIHZRHwB2Ux+8P+R7bWOxBzR4AAAAASUVORK5CYII=) no-repeat;
+    background-size: contain;
+  }
 }
 </style>

+ 164 - 6
src/views/portrayal/EntPortrayal.vue

@@ -2,16 +2,28 @@
   <div class="ent-portrayal">
     <div class="ent-header">
       <div class="name">讯飞智元信息科技有限公司</div>
-      <div></div>
+      <div class="ent_follow" @click="setFollow()">
+        <span :class="follow.classActive"></span>
+        <span class="follow_text">关注</span>
+      </div>
     </div>
     <div class="ent-content">
       <el-tabs v-model="activeName" @tab-click="handleClick">
         <el-tab-pane label="企业信息" name="1">
-          <div style="padding-top:32px;">
-            企业信息
-          </div>
+          <EntForm></EntForm>
+          <EntHistoryForm></EntHistoryForm>
         </el-tab-pane>
         <el-tab-pane label="中标信息" name="2">
+          <div class="pro_info">
+            <ul class="pro_info_ul">
+              <li class="pro_list" v-for="(item, index) in proData" :key="index">
+                <div class="pro_li_con">{{item.count}}</div>
+                <div class="pro_li_label">{{item.label}}</div>
+              </li>
+            </ul>
+            <div class="pro_info_tip">数据统计范围:{{dateRange.start}}-{{dateRange.end}}</div>
+          </div>
+          <ProActive></ProActive>
           <ent-chart :active="activeName" v-on:entInfo="getEntInfo"></ent-chart>
         </el-tab-pane>
       </el-tabs>
@@ -20,17 +32,34 @@
 </template>
 <script>
 import EntChart from './components/EntChart'
+import EntForm from './components/EntForm'
+import EntHistoryForm from './components/EntHistoryForm'
+import ProActive from './components/ProActive'
 import { Tabs, TabPane } from 'element-ui'
+import { moneyUnit, dateFormatter } from '@/utils'
+import { setFollowEnt, setCancelEnt } from '@/api/modules'
 export default {
   name: 'ent-portrayal',
   components: {
     [Tabs.name]: Tabs,
     [TabPane.name]: TabPane,
-    EntChart
+    EntChart,
+    EntForm,
+    EntHistoryForm,
+    ProActive
   },
   data () {
     return {
-      activeName: '1'
+      activeName: '1',
+      proData: [],
+      dateRange: { // 数据统计范围
+        start: 0,
+        end: 0
+      },
+      follow: {
+        classActive: 'icon_heart_gray',
+        text: '关注'
+      }
     }
   },
   computed: {},
@@ -42,6 +71,51 @@ export default {
     },
     getEntInfo (data) {
       console.log(data)
+      this.proData = [
+        {
+          label: '项目数量',
+          count: data.project_count + '个'
+        },
+        {
+          label: '项目金额',
+          count: moneyUnit(data.bidamount_count)
+        },
+        {
+          label: '项目省份',
+          count: data.area_count + '个'
+        },
+        {
+          label: '项目客户',
+          count: data.buyer_count + '个'
+        }
+      ]
+      this.dateRange.start = dateFormatter(data.timeRange.start * 1000, 'yyyy/MM/dd')
+      this.dateRange.end = dateFormatter(data.timeRange.end * 1000, 'yyyy/MM/dd')
+    },
+    // 关注
+    setFollow () {
+      if (this.follow.text === '关注') {
+        // entId: decodeURIComponent(getParam('eId'))
+        setFollowEnt({ entId: 'ABCZnZoYylYMywvGWBkcwc4CzMZQTRmYUJ9KCgwOS9FfFNwcjMoNwkwOGNmaHAoKFxTDRg%253D' }).then(res => {
+          console.log(res)
+          if (res.error_code === 0) {
+            if (res.data === 'success') {
+              this.follow.classActive = 'icon_heart_red'
+              this.follow.text = '已关注'
+            }
+          }
+        })
+      } else {
+        setCancelEnt({ entId: 'ABCZnZoYylYMywvGWBkcwc4CzMZQTRmYUJ9KCgwOS9FfFNwcjMoNwkwOGNmaHAoKFxTDRg%253D' }).then(res => {
+          console.log(res)
+          if (res.error_code === 0) {
+            if (res.data === 'success') {
+              this.follow.classActive = 'icon_heart_gray'
+              this.follow.text = '关注'
+            }
+          }
+        })
+      }
     }
   }
 }
@@ -51,8 +125,38 @@ export default {
   width: 920px;
   margin: 32px auto;
   .ent-header{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
     padding: 32px 40px;
     background: #fff;
+    .ent_follow{
+      display: flex;
+      cursor: pointer;
+      .icon_heart_gray{
+        margin-right: 4px;
+        display: flex;
+        width: 18px;
+        height: 18px;
+        background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAADSElEQVRYR+2X30sUURTHv2d2tnywnoW2MAoSIjKot6CihwqMknowcLZsd6YVg4IIetuWiHoI8qElnRnTZhc0MDQKqreCHoICEwQNlKK2fyCClJ2dE3dTWHXn16rgg/N6z4/P/Z57zr1DWGcfrTMebAD5VWRDoRUrpD+xjsDBJQBHwRQTAYkwBeZXkkQDiYTytTJJ78DALrIjKWJuAVEjM2QQFwC8ixB1JxLKuBeUa8my2Wz9pk1bH4PQ7hFgFoT7hR/TdzOZjN3b298kReQxAHVuPgwMFed+q11dXX+q2VQFEjDRzVteE+hw2WlejVLJnnGciCzL0l6H+ToRmsvrhOHCj+kLDQ2NMTkamQToI4jzpSLGJalkE0X3MHEbAWfL4YAPxbnfp6pBVQXSzdwgAW3M+ENw2lX14otquzGM/A0G3ycSZfkPBTTKmUzHbDV7XbdOgGiYCPUA8mpSUZbaLQPq68vtdxhfyjtx+KSmxd961Vzvy52Dg6FKKFE+Nx/DeHoGJI2W40t8VLscf19puwxIN61+Al1iYFRLKq1+XSHWK6HsIu/r7IxPePkZhvUSRC0MHtCS8Q5PINO0vjGoEUxtqtr+LAiQsDHN/HFmp0lV41k/H7EBYgwz+LuWjO/0VsjIFYX8QXbql9htXdf7m0mSx5hha6oS9QQyzNxf0bYlmw+lUvHPtSb18uvpsQ5GZPoUDMiwJkHUBOKkmoj3rQWQaeYvivMDYEJNKvu8FTKshyC6LiarmlSOrQWQbuTGxAxj4IGWVG56AmXN3O4oY7LcxsxXgxzSMNC6mbtGQDfAs06pdODKlY4pTyCxaBjWPRDdEjWGhDYtoTwPk9T1MC+eWbfVhJLxHYzCIJ1Oy7EduwfBOL9aUEEHqOvluppQQWGEGJ4PtNWACgPjC7TS8oWFCQRUK1QtMIGBFqC2xXaNkLgUfbpvEQzzm0Jh5rTXC8C37d3aNp3ur4vFIiMgOukGtRym1Or2PqqWJ/RfhxfUSmFClaxyN0uhCJxyiGRiPJqf8G8KhXDKLMQPrdCCYyXUIunLZ6Y2mJoVqoTatl2+A0aKiG0G5X/9tG+GOTOBro7VuLdqjVFzyWpN6Oe3AbShkJ8Cfuv/AMkozTS0zinfAAAAAElFTkSuQmCC) no-repeat;
+        background-size: contain;
+      }
+      .icon_heart_red{
+        margin-right: 4px;
+        display: flex;
+        width: 18px;
+        height: 18px;
+        background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAACBElEQVRYR+3Xv0scQRTA8e+TC0TwTwiYkBQ2IvYBx1QJJIVgkUAE90QI2KQR0qmEkBSBpAlpEvcKwRSCTUC7G8E/QEFQKy/gnxA0kGMn7HLIJW7mx7q3WNw0V9zMm8+9fftmTrhmQ66Zhz7I9UT6Gbpyhszq5ARiZgGF4VYn4BHCdxJpyFzzuHsT03hwlyR5gfAYuI2hhnAKaBL5KHPNfRvqv4/MfFJDDPIZ4bklwC/gHS3eZHOGWQJeIdQsa75xxrws6J95c3JBHcwWwn1XirPvDRvZpzDtOX+Xcx7lofJBsVoHnnoFLzrJsCZ1PfPv8ksg83VyjAGzV3SfoHVGlNSbO91rLoNiFQNpEVcxGhLpyAU6yd6OakZLIn3HDlpVvx1vSXlUQ1vq+oYrQ+fAzfJ2tUTyBB0CIxWBDqSuR10Z+gC8rAQkvJdZvWgHfVH3GOCwgjpKu/y4RPrICsoab6zeZkdAb8eyRHrF2Rgz0JKqMcy691EQCk+Pmh88kxXd9gL1FGXBpPtaL2ilZ8qBcYJKzZQHxgtUCsoT4w3qQm12boIhZbxNiyd5BZwXJOiSb2KVHimbwENP0TYwJZFOe47XCAJ1epQvKhgT9Mi6f55HpgphCoMcmSqMuRLoAiW8JiH929NGWMOwGFIz3p3aqwJ7MCm4qHtg+CtkH+TKcD9Drgz9AS0gsyVs0KiWAAAAAElFTkSuQmCC) no-repeat;
+        background-size: contain;
+      }
+      .follow_text{
+        font-size: 14px;
+        font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+        font-weight: 400;
+        color: #686868;
+        line-height: 22px;
+      }
+    }
   }
   .ent-content{
     margin-top: 16px;
@@ -78,6 +182,60 @@ export default {
     ::v-deep.el-tabs__content{
       padding: 0 40px 32px;
     }
+    .pro_info{
+      width: 100%;
+      height: 171px;
+      .pro_info_ul{
+        display: flex;
+        justify-content: space-between;
+        width: 100%;
+        background: #fff;
+        .pro_list{
+          position: relative;
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          justify-content: center;
+          margin-top: 32px;
+          width: 210px;
+          height: 78px;
+          .pro_li_con{
+            font-size: 20px;
+            font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+            font-weight: 400;
+            color: #2cb7ca;
+            line-height: 32px;
+          }
+          .pro_li_label{
+            font-size: 14px;
+            font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+            font-weight: 400;
+            color: #686868;
+            line-height: 22px;
+          }
+        }
+        .pro_list:after{
+          content: '';
+          position: absolute;
+          right: 0;
+          width: 0px;
+          height: 46px;
+          border: 0.5px solid #ececec;
+        }
+      }
+      .pro_info_tip{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        margin-top: 12px;
+        width: 840px;
+        height: 17px;
+        font-size: 12px;
+        font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+        font-weight: 400;
+        color: #999999;
+      }
+    }
   }
 }
 </style>

+ 151 - 0
src/views/portrayal/components/EntForm.vue

@@ -0,0 +1,151 @@
+<template>
+  <div class="entform">
+    <ul class="ent_ul">
+      <li class="ent_list" v-for="(item, index) in content" :key="index" :class="item.long?'li_long':''">
+        <div class="ent_li_label">{{item.label}}</div>
+        <div class="ent_li_main">{{item.main}}</div>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+import { getEntForm } from '@/api/modules'
+import { dateFormatter } from '@/utils'
+export default {
+  name: 'entform',
+  components: {
+  },
+  data () {
+    return {
+      // content: ['法定代表人', '经营状态', '统一社会信用代码', '成立日期', '组织机构代码', '注册资本', '纳税人识别号', '营业期限', '工商注册号', '公司类型', '人员规模', '联系方式', '注册地址', '经营范围']
+      content: []
+    }
+  },
+  created () {
+    this.getDetail()
+  },
+  methods: {
+    getDetail () {
+      getEntForm({ entId: 'ABCZnEFZCkoLys6GXt6cHISMTA%2FDjdjAWByPB4WPy43YGlwcg47NF8oOHdmCXEEDlxTDFU%3D' }).then(res => {
+        console.log(res)
+        const data = res.data
+        data.operStart = dateFormatter(data.operStart, 'yyyy-MM-dd')
+        data.operEnd = dateFormatter(data.operStart, 'yyyy-MM-dd')
+        this.content = [
+          {
+            label: '法定代表人',
+            main: data.legal
+          },
+          {
+            label: '经营状态',
+            main: data.status
+          },
+          {
+            label: '统一社会信用代码',
+            main: data.creditNo
+          },
+          {
+            label: '成立日期',
+            main: data.establish
+          },
+          {
+            label: '组织机构代码',
+            main: data.org_code
+          },
+          {
+            label: '注册资本',
+            main: data.capital
+          },
+          {
+            label: '纳税人识别号',
+            main: data.taxCode
+          },
+          {
+            label: '营业期限',
+            main: data.operStart + ' ' + '至' + ' ' + data.operEnd
+          },
+          {
+            label: '工商注册号',
+            main: data.company_code
+          },
+          {
+            label: '公司类型',
+            main: data.type
+          },
+          {
+            label: '人员规模',
+            main: data.employeeNo
+          },
+          {
+            label: '联系方式',
+            main: data.phone // 这个没有字段
+          },
+          {
+            label: '注册地址',
+            main: data.address,
+            long: 'islong'
+          },
+          {
+            label: '经营范围',
+            main: data.scope,
+            long: 'islong'
+          }
+        ]
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.entform{
+  margin-top: 32px;
+  width: 840px;
+  .ent_ul{
+    display: flex;
+    flex-wrap: wrap;
+    width: 100%;
+    border-top: 1px solid #ececec;
+    .ent_list{
+      display: flex;
+      min-height: 48px;
+      border-left: 1px solid #ececec;
+      border-bottom: 1px solid #ececec;
+      .ent_li_label{
+        padding: 12px 0 0 16px;
+        width: 144px;
+        height: 100%;
+        background: #F9FAFB;
+        font-size: 14px;
+        font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+        font-weight: 400;
+        text-align: LEFT;
+        color: #686868;
+        line-height: 24px;
+        border-right: 1px solid #ececec;
+      }
+      .ent_li_main{
+        padding: 12px 16px;
+        width: 274.5px;
+        display: flex;
+        align-items: center;
+        flex: 1;
+        font-size: 14px;
+        font-weight: 400;
+        color: #1d1d1d;
+        line-height: 24px;
+      }
+    }
+    .ent_list:nth-of-type(even) {
+      border-right: 1px solid #ececec;
+    }
+    .ent_list:nth-child(13) {
+      border-right: 1px solid #ececec;
+    }
+    .li_long{
+      width: 100%;
+    }
+  }
+}
+</style>

+ 169 - 0
src/views/portrayal/components/EntHistoryForm.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="enthistoryform">
+    <div class="ent_his_head">
+      <span class="ent_his_title">企业情报历史记录</span>
+      <span class="data_length">{{contents.length}}</span>
+    </div>
+    <div class="ent_his_main">
+      <ul class="ent_main_head">
+        <li class="ent_main_list" v-for="(item, index) in titleList" :key="index">{{item}}</li>
+      </ul>
+      <table class="ent_his_ul">
+        <tr class="ent_his_list" v-for="(item, index) in contents" :key="index" :class="item.long?'li_long':''">
+          <td class="ent_his_index inset_list">{{index+1}}</td>
+          <td class="ent_his_time inset_list">{{item.times[0]}}</td>
+          <td class="pro_content">
+            <tr class="pro_con_item" v-for="(data, i) in item.timeData" :key="i">
+              <div class="exe_list">{{data.change_name_new}}</div>
+              <div class="exe_list">{{data.content_before}}</div>
+              <div class="exe_list">{{data.content_after}}</div>
+            </tr>
+          </td>
+        </tr>
+      </table>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getEntChangeList } from '@/api/modules'
+export default {
+  name: 'enthistoryform',
+  components: {
+  },
+  data () {
+    return {
+      titleList: ['序号', '变更日期', '变更项目', '变更前', '变更后'],
+      contents: []
+    }
+  },
+  created () {
+    this.getDetail()
+  },
+  methods: {
+    getDetail () {
+      const that = this
+      getEntChangeList({ entId: 'ABCZnEFZCkoLys6GXt6cHISMTA%2FDjdjAWByPB4WPy43YGlwcg47NF8oOHdmCXEEDlxTDFU%3D' }).then(res => {
+        res.data.forEach(function (item, index) {
+          let dataArr = {}
+          dataArr = {
+            times: Object.keys(item),
+            timeData: item[Object.keys(item)]
+          }
+          that.contents.push(dataArr)
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.enthistoryform{
+  margin-top: 66px;
+  width: 840px;
+  .ent_his_head{
+    height: 28px;
+    font-size: 18px;
+    font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+    font-weight: 400;
+    text-align: LEFT;
+    color: #171826;
+    line-height: 28px;
+    .data_length{
+      margin-left: 4px;
+      color: #2CB7CA;
+    }
+  }
+  .ent_his_main{
+    margin-top: 16px;
+    width: 100%;
+    .ent_main_head{
+      display: flex;
+      .ent_main_list{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        height: 48px;
+        background: #F9FAFB;
+        font-size: 14px;
+        font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+        font-weight: 400;
+        color: #686868;
+        border: 1px solid #ececec;
+        border-right: 0;
+      }
+      .ent_main_list:nth-child(1){
+        width: 60px;
+      }
+      .ent_main_list:nth-child(2){
+        width: 110px;
+      }
+      .ent_main_list:nth-child(3){
+        width: 170px;
+      }
+      .ent_main_list:nth-child(4){
+        width: 250px;
+      }
+      .ent_main_list:nth-child(5){
+        width: 250px;
+        border-right: 1px solid #ececec;
+      }
+    }
+  }
+  .ent_his_ul{
+    display: flex;
+    flex-direction: column;
+    .ent_his_list{
+      display: flex;
+      width: 100%;
+      min-height: 48px;
+      font-size: 14px;
+      font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+      font-weight: 400;
+      text-align: CENTER;
+      color: #1d1d1d;
+      line-height: 24px;
+      .inset_list{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        border-left: 1px solid #ececec;
+        border-bottom: 1px solid #ececec;
+      }
+      .ent_his_index{
+        width: 60px;
+      }
+      .ent_his_time{
+        width: 110px;
+      }
+      .pro_content{
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+        .pro_con_item{
+          display: flex;
+          .exe_list{
+            padding: 12px 16px 0;
+            display: flex;
+            flex-direction: column;
+            width: 250px;
+            min-height: 48px;
+            border-left: 1px solid #ececec;
+            border-bottom: 1px solid #ececec;
+          }
+          .exe_list:nth-child(1){
+            width: 170px;
+            justify-content: center;
+            align-items: center;
+          }
+          .exe_list:nth-child(3){
+            border-right: 1px solid #ececec;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 152 - 0
src/views/portrayal/components/ProActive.vue

@@ -0,0 +1,152 @@
+<template>
+  <div class="proactive">
+    <div class="pro_active_head">
+      项目动态
+    </div>
+    <div class="pro_ul">
+      <div class="pro_list" v-for="(item, index) in listState.list" :key="index">
+        <div class="pro_li_title">{{item.title}}</div>
+        <div class="pro_li_info">
+          <div class="li_left">
+            <span class="tags">{{item.area}}</span>
+            <span class="tags">{{item.bidstatus}}</span>
+            <span class="tags">{{moneyUnit(item.bidamount)}}</span>
+          </div>
+          <div class="li_right">
+            {{dateFormatter(item.firsttime*1000, 'yyyy-MM-dd')}}
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="el-pagination-container">
+      <el-pagination
+        background
+        layout="prev, pager, next, ->"
+        :hide-on-single-page="true"
+        :current-page="listState.pageNum"
+        :page-size="listState.pageSize"
+        :total="listState.total"
+        @current-change="onPageChange"
+      >
+      </el-pagination>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Pagination } from 'element-ui'
+import { getNewMsg } from '@/api/modules'
+import { getParam, moneyUnit, dateFormatter } from '@/utils/'
+export default {
+  name: 'proactive',
+  components: {
+    [Pagination.name]: Pagination
+  },
+  data () {
+    return {
+      listState: {
+        loaded: true, // 是否已经搜索过
+        loading: false,
+        pageNum: 1, // 当前页
+        pageSize: 10, // 每页多少条数据
+        total: 0, // 一共多少条数据
+        list: [] // 查询请求返回的数据
+      }
+    }
+  },
+  created () {
+    this.getProActivcList()
+  },
+  methods: {
+    moneyUnit,
+    dateFormatter,
+    getProActivcList () {
+      // let paramas = {
+      // }
+      getNewMsg({ entId: decodeURIComponent(getParam('eId')), pageSize: 5 }).then(res => {
+        console.log(res)
+        if (res.error_code === 0) {
+          this.listState.list = res.data.list
+          this.listState.total = res.data.count
+        }
+      })
+    },
+    onPageChange (p) {
+      this.listState.pageNum = p
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.proactive{
+  margin-top: 64px;
+  .pro_active_head{
+    margin-bottom: 16px;
+    height: 28px;
+    font-size: 18px;
+    font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+    font-weight: 400;
+    text-align: LEFT;
+    color: #1d1d1d;
+    line-height: 28px;
+  }
+  .pro_ul{
+    width: 100%;
+    .pro_list{
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      width: 100%;
+      min-height: 90px;
+      box-shadow: 0px -1px 0px 0px rgba(0,0,0,0.05) inset;
+      .pro_li_title{
+        height: 24px;
+        font-size: 16px;
+        font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+        font-weight: 400;
+        text-align: LEFT;
+        color: #1d1d1d;
+        line-height: 24px;
+        text-shadow: 0px -1px 0px 0px rgba(0,0,0,0.05) inset;
+      }
+      .pro_li_info{
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-top: 8px;
+        .li_left{
+          display: flex;
+          align-items: center;
+          .tags{
+            display: flex;
+            padding: 1px 8px;
+            opacity: 1;
+            background: #EEF9FB;
+            border-radius: 4px;
+            box-shadow: 0px -1px 0px 0px rgba(0,0,0,0.05) inset;
+            font-size: 12px;
+            font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+            font-weight: 400;
+            text-align: CENTER;
+            color: #2cb7ca;
+            line-height: 20px;
+          }
+        }
+        .li_right{
+          font-size: 12px;
+          font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+          font-weight: 400;
+          color: #999999;
+          line-height: 20px;
+          text-shadow: 0px -1px 0px 0px rgba(0,0,0,0.05) inset;
+        }
+      }
+    }
+  }
+  .el-pagination-container{
+    margin-right: 40px;
+  }
+}
+
+</style>