guhongwei 4 years ago
parent
commit
6bcc83c106
100 changed files with 9363 additions and 81 deletions
  1. 2 0
      .env
  2. 33 0
      .eslintrc.js
  3. 69 4
      package-lock.json
  4. 4 1
      package.json
  5. 14 22
      src/App.vue
  6. BIN
      src/assets/logo.png
  7. BIN
      src/assets/video1.mp4
  8. BIN
      src/assets/video2.mp4
  9. 157 0
      src/components/chat.vue
  10. 172 0
      src/components/frame/swiper-frame.md
  11. 64 0
      src/components/frame/swiper-frame.vue
  12. 89 0
      src/components/list/list-model/model-1.vue
  13. 83 0
      src/components/list/list-model/model-2.vue
  14. 111 0
      src/components/list/list-model/model-3.vue
  15. 99 0
      src/components/list/list-model/model-4.vue
  16. 98 0
      src/components/list/list-model/model-5.vue
  17. 81 0
      src/components/list/list-model/model-6.vue
  18. 191 0
      src/components/list/list-page.vue
  19. 37 0
      src/components/list/search.vue
  20. 65 0
      src/components/list/tabs.vue
  21. 20 7
      src/main.js
  22. 19 0
      src/plugins/axios.js
  23. 39 0
      src/plugins/check-res.js
  24. 5 0
      src/plugins/element.js
  25. 6 0
      src/plugins/filters.js
  26. 27 0
      src/plugins/loading.js
  27. 4 0
      src/plugins/meta.js
  28. 33 0
      src/plugins/methods.js
  29. 20 0
      src/plugins/setting.js
  30. 65 0
      src/plugins/stomp.js
  31. 25 0
      src/plugins/var.js
  32. 174 19
      src/router/index.js
  33. 67 5
      src/store/index.js
  34. 117 0
      src/util/axios-wrapper.js
  35. 10 0
      src/util/filters.js
  36. 50 0
      src/util/methods-util.js
  37. 69 0
      src/util/user-util.js
  38. 0 5
      src/views/About.vue
  39. 0 18
      src/views/Home.vue
  40. 84 0
      src/views/achieveLive/apply.vue
  41. 112 0
      src/views/achieveLive/apply/applyAchieve.vue
  42. 32 0
      src/views/achieveLive/apply/applyTechol.vue
  43. 129 0
      src/views/achieveLive/before.vue
  44. 182 0
      src/views/achieveLive/detail.vue
  45. 118 0
      src/views/achieveLive/detail/chatData.vue
  46. 117 0
      src/views/achieveLive/detail/expertData.vue
  47. 116 0
      src/views/achieveLive/detail/guestData.vue
  48. 68 0
      src/views/achieveLive/detail/imgtxtData.vue
  49. 188 0
      src/views/achieveLive/detail/productData.vue
  50. 110 0
      src/views/achieveLive/detail/projectData.vue
  51. 119 0
      src/views/achieveLive/detail/topInfo.vue
  52. 178 0
      src/views/achieveLive/detail/videoData.vue
  53. 80 0
      src/views/admin/live/achieve.vue
  54. 30 0
      src/views/admin/live/achieve/active.vue
  55. 148 0
      src/views/admin/live/achieve/apply.vue
  56. 97 0
      src/views/admin/live/achieve/apply/goods.vue
  57. 79 0
      src/views/admin/live/achieve/apply/goodsList.vue
  58. 280 0
      src/views/admin/live/achieve/base.vue
  59. 183 0
      src/views/admin/live/achieve/interview.vue
  60. 177 0
      src/views/admin/live/achieve/pw.vue
  61. 30 0
      src/views/admin/live/achieve/road_show.vue
  62. 30 0
      src/views/admin/live/achieve/statistics.vue
  63. 65 0
      src/views/admin/live/achieve/trans.vue
  64. 64 0
      src/views/admin/live/achieve/trans/deal.vue
  65. 63 0
      src/views/admin/live/achieve/trans/finish.vue
  66. 146 0
      src/views/admin/live/achieve/vip.vue
  67. 64 0
      src/views/admin/live/science.vue
  68. 193 0
      src/views/admin/live/science/base.vue
  69. 216 0
      src/views/admin/live/science/video.vue
  70. 63 0
      src/views/admin/live/train.vue
  71. 236 0
      src/views/admin/live/train/base.vue
  72. 176 0
      src/views/admin/live/train/user.vue
  73. 199 0
      src/views/admin/live/train/video.vue
  74. 56 0
      src/views/admin/model/frame.vue
  75. 90 0
      src/views/admin/model/menu.vue
  76. 247 0
      src/views/channelLive/index.vue
  77. 118 0
      src/views/dynamic/index.vue
  78. 129 0
      src/views/dynamic/parts/achieveCom.vue
  79. 128 0
      src/views/dynamic/parts/achieveFiled.vue
  80. 117 0
      src/views/dynamic/parts/expertCom.vue
  81. 168 0
      src/views/dynamic/parts/userTwo.vue
  82. 49 0
      src/views/index.vue
  83. 139 0
      src/views/live/achieve/list.vue
  84. 65 0
      src/views/live/index.vue
  85. 80 0
      src/views/live/parts/achieveLive.vue
  86. 80 0
      src/views/live/parts/personalLive.vue
  87. 146 0
      src/views/live/parts/scienceLive.vue
  88. 183 0
      src/views/live/parts/trainLive.vue
  89. 90 0
      src/views/live/personal/list.vue
  90. 151 0
      src/views/login.vue
  91. 153 0
      src/views/market/index.vue
  92. 133 0
      src/views/market/index/achieve.vue
  93. 116 0
      src/views/market/index/business.vue
  94. 120 0
      src/views/market/index/expert.vue
  95. 93 0
      src/views/market/index/patent.vue
  96. 114 0
      src/views/market/index/roadshow.vue
  97. 101 0
      src/views/market/index/technology.vue
  98. 77 0
      src/views/market/index/top.vue
  99. 129 0
      src/views/market/list.vue
  100. 0 0
      src/views/market/list/achieve.vue

+ 2 - 0
.env

@@ -0,0 +1,2 @@
+VUE_APP_AXIOS_BASE_URL = ''
+VUE_APP_ROUTER="platlive"

+ 33 - 0
.eslintrc.js

@@ -0,0 +1,33 @@
+// https://eslint.org/docs/user-guide/configuring
+
+module.exports = {
+  root: true,
+  env: {
+    node: true,
+  },
+  extends: ['plugin:vue/essential', '@vue/prettier'],
+  plugins: ['vue'],
+  rules: {
+    'max-len': [
+      'warn',
+      {
+        code: 10000,
+      },
+    ],
+    'no-unused-vars': 'off',
+    'no-console': 'off',
+    'prettier/prettier': [
+      'warn',
+      {
+        singleQuote: true,
+        trailingComma: 'es5',
+        bracketSpacing: true,
+        jsxBracketSameLine: true,
+        printWidth: 160,
+      },
+    ],
+  },
+  parserOptions: {
+    parser: 'babel-eslint',
+  },
+};

+ 69 - 4
package-lock.json

@@ -4457,6 +4457,21 @@
         }
       }
     },
+    "dom7": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/dom7/-/dom7-2.1.5.tgz",
+      "integrity": "sha512-xnhwVgyOh3eD++/XGtH+5qBwYTgCm0aW91GFgPJ3XG+jlsRLyJivnbP0QmUBFhI+Oaz9FV0s7cxgXHezwOEBYA==",
+      "requires": {
+        "ssr-window": "^2.0.0"
+      },
+      "dependencies": {
+        "ssr-window": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-2.0.0.tgz",
+          "integrity": "sha512-NXzN+/HPObKAx191H3zKlYomE5WrVIkoCB5IaSdvKokxTpjBdWfr0RaP+1Z5KOfDT0ZVz+2tdtiBkhsEQ9p+0A=="
+        }
+      }
+    },
     "domain-browser": {
       "version": "1.2.0",
       "resolved": "https://registry.npm.taobao.org/domain-browser/download/domain-browser-1.2.0.tgz?cache=0&sync_timestamp=1604239910191&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomain-browser%2Fdownload%2Fdomain-browser-1.2.0.tgz",
@@ -4551,6 +4566,22 @@
         "safe-buffer": "^5.0.1"
       }
     },
+    "echarts": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.0.1.tgz",
+      "integrity": "sha512-JYn22Dolt2esY2jEzUsw1OxbobuW67oGjIoTjZO3rW89SWkfJ4kbrmC2OW9JjsBrD1rdkmaWBuZZ2HgmThyxJw==",
+      "requires": {
+        "tslib": "2.0.3",
+        "zrender": "5.0.3"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
+          "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
+        }
+      }
+    },
     "ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz",
@@ -7587,10 +7618,10 @@
         "minimist": "^1.2.5"
       }
     },
-    "monent": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npm.taobao.org/monent/download/monent-2.0.0.tgz",
-      "integrity": "sha1-9yG+8yGyHeGMoZTldwrd5YN7txI="
+    "moment": {
+      "version": "2.29.1",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
+      "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
     },
     "move-concurrently": {
       "version": "1.0.1",
@@ -10176,6 +10207,11 @@
         "tweetnacl": "~0.14.0"
       }
     },
+    "ssr-window": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-1.0.1.tgz",
+      "integrity": "sha512-dgFqB+f00LJTEgb6UXhx0h+SrG50LJvti2yMKMqAgzfUmUXZrLSv2fjULF7AWGwK25EXu8+smLR3jYsJQChPsg=="
+    },
     "ssri": {
       "version": "6.0.1",
       "resolved": "https://registry.npm.taobao.org/ssri/download/ssri-6.0.1.tgz",
@@ -10410,6 +10446,15 @@
         "util.promisify": "~1.0.0"
       }
     },
+    "swiper": {
+      "version": "5.3.6",
+      "resolved": "https://registry.npmjs.org/swiper/-/swiper-5.3.6.tgz",
+      "integrity": "sha512-FUz50g6RuvGAuXQWmR5lRPoA129leRUZ/p57ckr8+P5kR7VktElVQ47JGmWD86mOJCFfvMhUf0hinyC5UFL5iw==",
+      "requires": {
+        "dom7": "^2.1.3",
+        "ssr-window": "^1.0.1"
+      }
+    },
     "table": {
       "version": "5.4.6",
       "resolved": "https://registry.npm.taobao.org/table/download/table-5.4.6.tgz?cache=0&sync_timestamp=1609732765587&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftable%2Fdownload%2Ftable-5.4.6.tgz",
@@ -11161,6 +11206,11 @@
       "resolved": "https://registry.npm.taobao.org/vue/download/vue-2.6.12.tgz?cache=0&sync_timestamp=1609359858533&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue%2Fdownload%2Fvue-2.6.12.tgz",
       "integrity": "sha1-9evU+mvShpQD4pqJau1JBEVskSM="
     },
+    "vue-awesome-swiper": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/vue-awesome-swiper/-/vue-awesome-swiper-4.1.1.tgz",
+      "integrity": "sha512-50um10t6N+lJaORkpwSi1wWuMmBI1sgFc9Znsi5oUykw2cO5DzLaBHcO2JNX21R+Ue4TGoIJDhhxjBHtkFrTEQ=="
+    },
     "vue-eslint-parser": {
       "version": "7.4.1",
       "resolved": "https://registry.npm.taobao.org/vue-eslint-parser/download/vue-eslint-parser-7.4.1.tgz?cache=0&sync_timestamp=1611231636955&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-eslint-parser%2Fdownload%2Fvue-eslint-parser-7.4.1.tgz",
@@ -12222,6 +12272,21 @@
           "dev": true
         }
       }
+    },
+    "zrender": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.0.3.tgz",
+      "integrity": "sha512-TVcN2IMdo7je3GEq/E4CER4AGBe/n50/izILdupppyHf/hVHuiXCRliqdu8+32Z1OmGg6RfKt5qQlkX+bOtU0g==",
+      "requires": {
+        "tslib": "2.0.3"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
+          "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
+        }
+      }
     }
   }
 }

+ 4 - 1
package.json

@@ -10,12 +10,15 @@
   "dependencies": {
     "axios": "^0.21.1",
     "core-js": "^3.6.5",
+    "echarts": "^5.0.1",
     "element-ui": "^2.15.0",
     "jsonwebtoken": "^8.5.1",
     "lodash": "^4.17.20",
-    "monent": "^2.0.0",
+    "moment": "^2.29.1",
     "naf-core": "^0.1.2",
+    "swiper": "^5.3.6",
     "vue": "^2.6.11",
+    "vue-awesome-swiper": "^4.1.1",
     "vue-meta": "^2.4.0",
     "vue-router": "^3.2.0",
     "vuex": "^3.4.0"

+ 14 - 22
src/App.vue

@@ -1,32 +1,24 @@
 <template>
   <div id="app">
-    <div id="nav">
-      <router-link to="/">Home</router-link> |
-      <router-link to="/about">About</router-link>
-    </div>
     <router-view />
   </div>
 </template>
 
 <style lang="less">
-#app {
-  font-family: Avenir, Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-align: center;
-  color: #2c3e50;
+body {
+  margin: 0;
 }
-
-#nav {
-  padding: 30px;
-
-  a {
-    font-weight: bold;
-    color: #2c3e50;
-
-    &.router-link-exact-active {
-      color: #42b983;
-    }
-  }
+.textOver {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.w_1200 {
+  width: 1200px;
+  margin: 0 auto;
+}
+p {
+  padding: 0;
+  margin: 0;
 }
 </style>

BIN
src/assets/logo.png


BIN
src/assets/video1.mp4


BIN
src/assets/video2.mp4


+ 157 - 0
src/components/chat.vue

@@ -0,0 +1,157 @@
+<template>
+  <div id="chats">
+    <el-row>
+      <el-col :span="24" class="info chat_frame" id="chat">
+        <template v-for="(i, index) in talk">
+          <template v-if="isSender(i, index)">
+            <el-col :span="24" class="senderTime" :key="`div${i.id}${index}`">
+              <span :key="`senderTime${i.id}${index}`">[{{ i.send_time }}] {{ i.sender_name }}</span>
+              <span v-html="i.content" :key="`senderContent${i.id}${index}`"></span>
+            </el-col>
+          </template>
+          <template v-else>
+            <el-col :span="24" class="receverTime" :key="`div${i.id}${index}`">
+              <span :key="`receverTime${i.id}${index}`"> {{ i.sender_name }} [{{ i.send_time }}]</span>
+              <span v-html="i.content" :key="`receverContent${i.id}${index}`"></span>
+            </el-col>
+          </template>
+        </template>
+      </el-col>
+      <el-col :span="24" style="text-align:center">
+        <el-input v-model="content" type="textarea"></el-input>
+        <el-button type="primary" size="mini" @click="sendMessage" style="margin-top:10px">发送</el-button>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+// import wangEditor from '@common/src/components/frame/wang-editor.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: personalChat } = createNamespacedHelpers('personalchat');
+export default {
+  name: 'chats',
+  props: {
+    room: { type: Object },
+  },
+  components: {},
+  data: () => {
+    return {
+      content: '',
+      talk: [],
+    };
+  },
+  created() {},
+  mounted() {
+    this.channel();
+  },
+  methods: {
+    ...personalChat(['create', 'query']),
+    async search() {
+      let res = await this.query({ personroom_id: this.room.id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `talk`, res.data);
+        this.turnBottom();
+      }
+    },
+    async sendMessage() {
+      if (this.content != '') {
+        let obj = { personroom_id: this.room.id, content: this.content, sender_id: this.user.uid, sender_name: this.user.name, send_time: '13:00' };
+        let keys = Object.keys(this.room);
+        let fres = keys.find(f => this.room[f] == this.user.uid);
+        obj.receiver_id = fres === 'buyer_id' ? this.room['seller_id'] : this.room['buyer_id'];
+        obj.receiver_name = fres === 'buyer_id' ? this.room['seller_name'] : this.room['buyer_name'];
+        let res = await this.create(obj);
+        // this.$refs.editor.setContent();
+        this.$set(this, `content`, '');
+        this.$forceUpdate();
+        if (this.$checkRes(res, null, res.errmsg || '发言失败')) {
+          this.talk.push(res.data);
+          this.turnBottom();
+        }
+      } else this.$message.error('请输入信息后发送');
+    },
+    channel() {
+      //TODO 修改订阅地址
+      if (!this.room.id) {
+        console.warn('未获取到房间id,无法进行订阅');
+        return;
+      }
+      this.$stomp({
+        [`/exchange/person_chat/${this.room.id}_${this.user.uid}`]: this.onMessage,
+      });
+    },
+    onMessage(message) {
+      let body = _.get(message, 'body');
+      if (body) {
+        body = JSON.parse(body);
+        this.talk.push(body);
+        this.turnBottom();
+      }
+    },
+    turnBottom() {
+      this.$nextTick(() => {
+        document.getElementById('chat').scrollTop = document.getElementById('chat').scrollHeight;
+      });
+    },
+    isSender(data) {
+      return this.user.uid == data.sender_id;
+    },
+  },
+  watch: {
+    room: {
+      handler(val) {
+        if (val.id) this.search();
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.chat_frame {
+  height: 350px;
+  border: 1px solid #ccc;
+  margin-bottom: 10px;
+  overflow-y: auto;
+}
+.info {
+  float: left;
+  width: 100%;
+  padding: 15px;
+}
+/deep/.info p {
+  margin: 0 !important;
+  padding: 0 !important;
+}
+.senderTime {
+  float: left;
+  width: 100%;
+  text-align: left;
+}
+.receverTime {
+  float: right;
+  width: 100%;
+}
+.receverTime span:first-child {
+  float: right;
+  width: 100%;
+  text-align: right;
+}
+.receverTime span:last-child {
+  float: right;
+  width: 100%;
+  text-align: right;
+}
+</style>

+ 172 - 0
src/components/frame/swiper-frame.md

@@ -0,0 +1,172 @@
+# 组件文档说明
+
+## 下载依赖
+### npm install vue-awesome-swiper  --save
+### npm install swiper@5.3.6  --save
+
+## options设置
+### 1
+切换效果 fade cube coverflow flip
+effect: 'flip',
+样式设定
+fadeEffect: {
+淡出fade
+crossFade: true,
+方块切换 cube
+slideShadows: true,
+shadow: true,
+shadowOffset: 100,
+shadowScale: 0.6,
+coverflow
+slidesPerView: 3,
+centeredSlides: true,
+coverflowEffect: {
+  rotate: 30,
+  stretch: 10,
+  depth: 60,
+  modifier: 2,
+  slideShadows: true,
+},
+flip
+slideShadows: true,
+limitRotation: true,
+},
+### 2
+不可拖动文字
+noSwiping: true,
+### 3
+当你的Swiper在过渡时将无法滑动
+preventInteractionOnTransition : true,
+短切换,长切花
+shortSwipes: false,
+自由滑动,不知道滑过几个
+freeMode: true,
+### 4
+设置预览值 px
+slidesOffsetBefore: 100,
+显示多行
+slidesPerView: 3, //一行显示3个
+slidesPerColumn: 2, //显示2行
+### 5
+一页显示三个,一组为三个
+slidesPerView: 3,
+slidesPerGroup: 3,
+### 6
+slidesPerView: 2,
+//slidesPerView : 'auto',    根据slide的宽度自动调整展示数量。此时需要设置slide的宽度,如下style所示
+//slidesPerView : 3.7,
+### 7
+默认居中,并一页显示三个,第一条在左侧
+slidesPerView: 3,
+centeredSlides: true,
+centeredSlidesBounds: true,
+### 8
+默认居中,并一页显示三个,第一个在中间
+slidesPerView: 3,
+centeredSlides: true,
+### 9
+如果有一个数据时,所有分页,按钮,分页点全部失效
+watchOverflow: true,
+### 10
+滑过触发回调
+runCallbacksOnInit: true,
+on: {
+  slideChangeTransitionStart: function() {
+    选中值
+    console.log(this.activeIndex);
+  },
+},
+### 11
+自适应高度 随silde变化而变化
+autoHeight: true,
+### 12
+默认一页显示数
+slidesPerView: 4,
+数据间隔 px
+spaceBetween: 30,
+不同屏幕配置
+breakpoints: {
+  320: {
+    //当屏幕宽度大于等于320
+    slidesPerView: 2,
+    spaceBetween: 10,
+  },
+  768: {
+    //当屏幕宽度大于等于768
+    slidesPerView: 3,
+    spaceBetween: 20,
+  },
+  1280: {
+    //当屏幕宽度大于等于1280
+    slidesPerView: 4,
+    spaceBetween: 30,
+  },
+},
+### 13
+鼠标滑过显示小手
+grabCursor: true,
+### 14
+自动滑过贴合时间
+speed: 300,
+autoplay: {
+  默认3秒切换一次
+  delay: 3000,
+到最后一个自动停止
+stopOnLastSlide: true,
+触碰当前页自动停止
+disableOnInteraction: true,
+反向自动轮播
+reverseDirection: true,
+},
+### 15
+自动滑过
+autoplay: true,
+//鼠标覆盖停止自动切换
+### 16
+默认索引值,0:开始
+initialSlide: 0,
+### 17
+垂直切换选项
+direction: 'vertical',
+### 18
+循环轮播
+loop: true,
+### 19
+分页
+pagination: {
+el: '.swiper-pagination',
+clickable为true时,点击小点控制轮播
+clickable: true,
+分页类型
+type: 'bullets',//点
+type: 'fraction',//数字
+type: 'progressbar',//上条
+type: 'custom', //自定义
+progressbarOpposite: true,//progressbar-左条
+bulletElement: 'li',//指定标签
+dynamicBullets: true,//bullets-点数过多,隐藏
+dynamicMainBullets: 4,//显示点数量
+hideOnClick: true,//点击数据时,隐藏分页
+自定义点样式
+renderBullet: function(index, className) {
+  return '<span class="' + className + '">' + (index + 1) + '</span>';
+},
+设置分式
+type: 'fraction',
+renderFraction: function(currentClass, totalClass) {
+  return '<span class="' + currentClass + '"></span>' + ' of ' + '<span class="' + totalClass + '"></span>';
+},
+自定义点样式
+type: 'custom',
+renderCustom: function(swiper, current, total) {
+  return current + ' of ' + total;
+},
+bulletClass: 'my-bullet', //需设置.my-bullet样式
+bulletActiveClass: 'my-bullet-active', //需设置my-bullet-active样式
+},
+### 20
+按钮导航
+navigation: {
+  nextEl: '.swiper-button-next',
+  prevEl: '.swiper-button-prev',
+},

+ 64 - 0
src/components/frame/swiper-frame.vue

@@ -0,0 +1,64 @@
+<template>
+  <div id="swiper-frame">
+    <div class="container">
+      <div class="swiper-box">
+        <swiper ref="mySwiper" :options="options">
+          <swiper-slide v-for="(item, index) in list" :key="index">
+            <slot v-bind="{ index, item }"></slot>
+          </swiper-slide>
+          <!-- 分页 -->
+          <div class="swiper-pagination" slot="pagination"></div>
+          <!-- 按钮导航 -->
+          <div class="swiper-button-prev" slot="button-prev"></div>
+          <div class="swiper-button-next" slot="button-next"></div>
+        </swiper>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Swiper, SwiperSlide } from 'vue-awesome-swiper';
+import 'swiper/css/swiper.css';
+export default {
+  name: 'swiper-frame',
+  props: {
+    // 循环列表
+    list: { type: Array, default: () => [] },
+    // 配置文件
+    options: { type: Object, default: () => {} },
+  },
+  components: { Swiper, SwiperSlide },
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {},
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.swiper-box {
+  .swiper-container {
+    height: 45px;
+  }
+}
+// 选中背景颜色,字颜色
+// .swiper-slide-active {
+//   background: #ff6600;
+//   color: #fff;
+// }
+// 设定固定值
+// .swiper-slide {
+//   width: 300px; /*设为固定值*/
+//   width: auto; /*根据内容调整宽度*/
+// }
+// 自定义按钮样式
+// .swiper-container {
+//   --swiper-theme-color: #ff6600; /* 设置Swiper风格 */
+//   --swiper-navigation-color: #00ff33; /* 单独设置按钮颜色 */
+//   --swiper-navigation-size: 30px; /* 设置按钮大小 */
+// }
+</style>

+ 89 - 0
src/components/list/list-model/model-1.vue

@@ -0,0 +1,89 @@
+<template>
+  <div id="model-1">
+    <el-col :span="24" class="list" v-for="(item, index) in list" :key="index">
+      <el-col :span="21" class="name" @click.native="clickDetail(item.id)">
+        {{ item.p1 }}
+      </el-col>
+      <el-col :span="3" class="date">
+        {{ getDate(item.p2) }}
+      </el-col>
+      <el-col :span="24" class="brief"> 成果简介:{{ item.p3 || '暂无' }} </el-col>
+    </el-col>
+  </div>
+</template>
+
+<script>
+const moment = require('moment');
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'model-1',
+  props: {
+    list: { type: Array, default: () => [] },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    clickDetail(id) {
+      // TODO:打开指定详情
+      this.$router.push({ path: './list', query: { index: this.index, id } });
+    },
+    getDate(date) {
+      let newDate = moment(date).format('YYYY-MM-DD');
+      if (newDate) return newDate;
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    index() {
+      return parseInt(this.$route.query.index) || 0;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.list {
+  padding: 10px 0;
+  .name {
+    font-size: 18px;
+    font-weight: bold;
+  }
+  .date {
+    font-size: 16px;
+    text-align: center;
+  }
+  .brief {
+    font-size: 16px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    -webkit-line-clamp: 2;
+    word-break: break-all;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    margin: 10px 0 0 0;
+    max-height: 42px;
+  }
+}
+.list:hover {
+  cursor: pointer;
+  .name {
+    -webkit-transform: translateY(-3px);
+    -ms-transform: translateY(-3px);
+    transform: translateY(-3px);
+    -webkit-box-shadow: 0 0 6px #999;
+    box-shadow: 0 0 6px #999;
+    -webkit-transition: all 0.5s ease-out;
+    transition: all 0.5s ease-out;
+    color: #0085d2;
+  }
+}
+</style>

+ 83 - 0
src/components/list/list-model/model-2.vue

@@ -0,0 +1,83 @@
+<template>
+  <div id="model-2">
+    <el-col :span="24" class="list" v-for="(item, index) in list" :key="index">
+      <el-col :span="15" class="name textOver" @click.native="clickDetail(item.id)">{{ item.p1 }} </el-col>
+      <el-col :span="6" class="type textOver"> 专利有限性:{{ item.p2 }} </el-col>
+      <el-col :span="3" class="date"> {{ item.p3 }} </el-col>
+      <el-col :span="24" class="brief">
+        {{ item.p4 }}
+      </el-col>
+    </el-col>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'model-2',
+  props: {
+    list: { type: Array, default: () => [] },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    clickDetail(id) {
+      // TODO:打开指定详情
+      this.$router.push({ path: './list', query: { index: this.index, id } });
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    index() {
+      return parseInt(this.$route.query.index) || 0;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.list {
+  border-bottom: 1px dashed #ccc;
+  padding: 12px 0;
+  .name {
+    font-size: 18px;
+    font-weight: bold;
+    padding: 0 0 5px 0;
+  }
+  .type {
+    font-size: 16px;
+    text-align: left;
+    padding: 0 0 5px 0;
+  }
+  .date {
+    font-size: 16px;
+    text-align: right;
+    padding: 0 0 5px 0;
+  }
+  .brief {
+    font-size: 16px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    -webkit-line-clamp: 2;
+    word-break: break-all;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    height: 40px;
+  }
+}
+.list:hover {
+  cursor: pointer;
+  .name {
+    color: #409eff;
+  }
+}
+</style>

+ 111 - 0
src/components/list/list-model/model-3.vue

@@ -0,0 +1,111 @@
+<template>
+  <div id="model-3" class="main">
+    <el-col :span="24" class="list" v-for="(item, index) in list" :key="index">
+      <el-col :span="15" class="name textOver" @click.native="clickDetail(item.id)">
+        {{ item.p1 }}
+      </el-col>
+      <el-col :span="6" class="field">{{ item.p2 }} </el-col>
+      <el-col :span="3" class="date">
+        {{ item.p3 | getDate }}
+      </el-col>
+    </el-col>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'model-3',
+  props: {
+    list: { type: Array, default: () => [] },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    clickDetail(id) {
+      // TODO:打开指定详情
+      this.$router.push({ path: './list', query: { index: this.index, id } });
+    },
+  },
+  // 过滤时间
+  filters: {
+    getDate(meta) {
+      let createdAt = _.get(meta, `createdAt`);
+      let date = new Date(createdAt)
+        .toLocaleDateString()
+        .replace('/', '-')
+        .replace('/', '-');
+      return date;
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    index() {
+      return parseInt(this.$route.query.index) || 0;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 0 10px;
+  min-height: 620px;
+  .list {
+    padding: 12px 0;
+    .name {
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .field {
+      font-size: 16px;
+    }
+    .date {
+      text-align: center;
+      font-size: 16px;
+    }
+  }
+  .list:nth-child(7) {
+    border-bottom: 1px solid #ccc;
+  }
+  .list:hover {
+    cursor: pointer;
+    .name {
+      -webkit-transform: translateY(-3px);
+      -ms-transform: translateY(-3px);
+      transform: translateY(-3px);
+      -webkit-box-shadow: 0 0 6px #999;
+      box-shadow: 0 0 6px #999;
+      -webkit-transition: all 0.5s ease-out;
+      transition: all 0.5s ease-out;
+      color: #0085d2;
+    }
+  }
+  .listTop {
+    height: 49px;
+    line-height: 49px;
+    border-bottom: 1px solid #ccc;
+    .columnname {
+      span:first-child {
+        color: #22529a;
+        font-weight: bold;
+        font-size: 25px;
+      }
+      span:last-child {
+        color: #22529a;
+        font-size: 20px;
+        font-weight: bold;
+      }
+    }
+  }
+}
+</style>

+ 99 - 0
src/components/list/list-model/model-4.vue

@@ -0,0 +1,99 @@
+<template>
+  <div id="model-4">
+    <el-col :span="12" class="list" v-for="(item, index) in list" :key="index">
+      <el-col :span="5" class="image">
+        <el-image :src="item.p1">
+          <template #error>
+            <el-image :src="expertimage"></el-image>
+          </template>
+        </el-image>
+      </el-col>
+      <el-col :span="19" class="other">
+        <p class="name textOver" @click="clickDetail(item.id)">{{ item.p2 }}</p>
+        <p class="textOver">工作单位:{{ item.p3 || '暂无' }}</p>
+        <p class="textOver">擅长领域:{{ item.p4 || '暂无' }}</p>
+      </el-col>
+    </el-col>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'model-4',
+  props: {
+    list: { type: Array, default: () => [] },
+  },
+  components: {},
+  data: function() {
+    return {
+      expertimage: require('@p/live/222.png'),
+    };
+  },
+  created() {},
+  methods: {
+    clickDetail(id) {
+      // TODO:打开指定详情
+      this.$router.push({ path: './list', query: { index: this.index, id } });
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    index() {
+      return parseInt(this.$route.query.index) || 0;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.list {
+  padding: 12px 0;
+  border-bottom: 1px dashed #ccc;
+  .image {
+    .el-image {
+      border-radius: 90px;
+      width: 90px;
+      height: 90px;
+    }
+  }
+  .other {
+    padding: 0 10px;
+    p {
+      font-size: 16px;
+      color: #666;
+      padding: 0 0 10px 0;
+      span {
+        display: inline-block;
+        width: 50%;
+      }
+    }
+    p:nth-child(1) {
+      font-size: 18px;
+      font-weight: bold;
+      color: #000;
+    }
+  }
+}
+.list:hover {
+  cursor: pointer;
+  .other {
+    .name {
+      -webkit-transform: translateY(-3px);
+      -ms-transform: translateY(-3px);
+      transform: translateY(-3px);
+      -webkit-box-shadow: 0 0 6px #999;
+      box-shadow: 0 0 6px #999;
+      -webkit-transition: all 0.5s ease-out;
+      transition: all 0.5s ease-out;
+      color: #0085d2;
+    }
+  }
+}
+</style>

+ 98 - 0
src/components/list/list-model/model-5.vue

@@ -0,0 +1,98 @@
+<template>
+  <div id="model-5">
+    <el-col :span="24" class="infoLeftList" v-for="(item, index) in list" :key="index">
+      <el-col :span="4" class="date">
+        <span>{{ item.p1 || '暂无' }}</span>
+      </el-col>
+      <el-col :span="20" class="info" @click.native="clickDetail(item.id)">
+        <el-col :span="24" class="title textOver">
+          {{ item.p2 }}
+        </el-col>
+        <el-col :span="24" class="brief">
+          {{ item.p3 || '暂无' }}
+        </el-col>
+      </el-col>
+    </el-col>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'model-5',
+  props: {
+    list: { type: Array, default: () => [] },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    clickDetail(id) {
+      // TODO:打开指定详情
+      this.$router.push({ path: './list', query: { index: this.index, id } });
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    index() {
+      return parseInt(this.$route.query.index) || 0;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.infoLeftList {
+  width: 97%;
+  padding: 9px 0;
+  border-bottom: 1px dashed #ccc;
+  .date {
+    text-align: center;
+    margin: 6px 0 0 0;
+    span {
+      background-color: #22529a;
+      color: #fff;
+      font-size: 18px;
+      border-radius: 5px;
+      padding: 3px 7px;
+    }
+  }
+  .info {
+    .title {
+      font-size: 18px;
+      font-weight: bold;
+      padding: 5px 0 0 0;
+    }
+    .brief {
+      font-size: 14px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      -webkit-line-clamp: 2;
+      word-break: break-all;
+      display: -webkit-box;
+      -webkit-box-orient: vertical;
+      padding: 5px 0 0 0;
+      height: 43px;
+    }
+  }
+}
+.infoLeftList:last-child {
+  border-bottom: none;
+}
+.infoLeftList:hover {
+  cursor: pointer;
+  .info {
+    .title {
+      color: #409eff;
+    }
+  }
+}
+</style>

+ 81 - 0
src/components/list/list-model/model-6.vue

@@ -0,0 +1,81 @@
+<template>
+  <div id="model-6">
+    <el-col :span="24" class="list">
+      <el-col class="infoRightList" :span="24" v-for="(item, index) in list" :key="index" @click.native="clickDetail(item.id)">
+        <el-col :span="20" class="title textOver">
+          {{ item.p1 }}
+        </el-col>
+        <el-col :span="4" class="date">
+          {{ item.p2 || '暂无' }}
+        </el-col>
+      </el-col>
+    </el-col>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'model-6',
+  props: {
+    list: { type: Array, default: () => [] },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    clickDetail(id) {
+      // TODO:打开指定详情
+      this.$router.push({ path: './list', query: { index: this.index, id } });
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    index() {
+      return parseInt(this.$route.query.index) || 0;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.list {
+  height: 455px;
+  overflow: hidden;
+  height: 455px;
+  overflow: hidden;
+  padding: 0 10px;
+  .infoRightList {
+    padding: 10px 0;
+    .title {
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .date {
+      font-size: 18px;
+      text-align: right;
+    }
+  }
+  .infoRightList:nth-child(5) {
+    border-bottom: 1px dashed #ccc;
+    padding: 10px 0 17px 0;
+  }
+  .infoRightList:nth-child(6) {
+    padding: 15px 0 10px 0;
+  }
+  .infoRightList:hover {
+    cursor: pointer;
+    .title {
+      color: #409eff;
+    }
+  }
+}
+</style>

+ 191 - 0
src/components/list/list-page.vue

@@ -0,0 +1,191 @@
+<template>
+  <div id="list-page">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="top">
+          <el-col :span="3" class="left">
+            <span>|</span> <span>{{ title }}</span>
+          </el-col>
+          <el-col :span="21" class="right">
+            <el-row type="flex" justify="end">
+              <el-col :span="12" class="tabs" v-if="useTab">
+                <tabs :displayList="displayList" :dropList="dropList" @toSearch="change"></tabs>
+              </el-col>
+              <el-col :span="12" class="search" v-if="useSearch">
+                <search @toSearch="toSearch"></search>
+              </el-col>
+            </el-row>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="down">
+          <slot></slot>
+          <!-- <el-col :span="24" class="list" v-for="(item, index) in list" :key="index">
+            <el-col :span="21" class="name" @click.native="clickDetail(item.id)">
+              {{ item.p1 }}
+            </el-col>
+            <el-col :span="3" class="date">
+              {{ getDate(item.p2) }}
+            </el-col>
+            <el-col :span="24" class="brief"> 成果简介:{{ item.p3 || '暂无' }} </el-col>
+          </el-col> -->
+        </el-col>
+        <el-col :span="24" class="page" v-if="usePage">
+          <el-pagination @current-change="search" :current-page="currentPage" layout="total, prev, pager, next, jumper" :total="total" :page-size="pageSize">
+          </el-pagination>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import tabs from './tabs.vue';
+import search from './search.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list-page',
+  props: {
+    useTab: { type: Boolean, default: false },
+    useSearch: { type: Boolean, default: true },
+    displayList: { type: Array, default: () => [] },
+    dropList: { type: Array, default: () => [] },
+    list: { type: Array, default: () => [] },
+    searchModel: { type: String, default: 'title' },
+    title: { type: String },
+    total: { type: Number, default: 0 },
+    usePage: { type: Boolean, default: true },
+  },
+  components: { tabs, search },
+  data: function() {
+    return {
+      searchInfo: '',
+      pageSize: 10,
+      currentPage: 1,
+    };
+  },
+  created() {},
+  methods: {
+    search() {
+      const skip = (this.currentPage - 1) * this.pageSize;
+      let condition = { skip, limit: this.pageSize };
+      if (this.searchInfo && this.searchInfo !== '') condition[this.searchModel] = this.searchInfo;
+      this.$emit('toSearch', condition);
+    },
+    toSearch(condition) {
+      if (condition && condition !== '') {
+        this.$set(this, `searchInfo`, condition);
+      }
+      this.currentPage = 1;
+      this.search();
+    },
+    change(condition) {
+      this.$emit('toChangeTab', condition);
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .top {
+    height: 49px;
+    border-bottom: 1px solid #ccc;
+    padding: 5px 0 0 0;
+    .left {
+      text-align: left;
+      span:first-child {
+        color: #22529a;
+        font-weight: bold;
+        font-size: 25px;
+      }
+      span:last-child {
+        color: #22529a;
+        font-size: 20px;
+        font-weight: bold;
+      }
+    }
+    .right {
+      .tabs {
+        span {
+          font-size: 16px;
+          padding: 8px 10px;
+          display: inline-block;
+          font-weight: bold;
+        }
+        span:hover {
+          cursor: pointer;
+          color: #409eff;
+        }
+        .btn {
+          padding: 0px 0 0 0;
+          position: relative;
+          top: 1px;
+          i {
+            font-size: 20px;
+            font-weight: bold;
+          }
+        }
+      }
+      .search {
+        /deep/.el-input__inner {
+          height: 35px;
+          line-height: 35px;
+        }
+      }
+    }
+  }
+  .down {
+    height: 500px;
+    overflow: hidden;
+    .list {
+      padding: 10px 0;
+      .name {
+        font-size: 18px;
+        font-weight: bold;
+      }
+      .date {
+        font-size: 16px;
+        text-align: center;
+      }
+      .brief {
+        font-size: 16px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        -webkit-line-clamp: 2;
+        word-break: break-all;
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+        margin: 10px 0 0 0;
+        max-height: 42px;
+      }
+    }
+    .list:hover {
+      cursor: pointer;
+      .name {
+        -webkit-transform: translateY(-3px);
+        -ms-transform: translateY(-3px);
+        transform: translateY(-3px);
+        -webkit-box-shadow: 0 0 6px #999;
+        box-shadow: 0 0 6px #999;
+        -webkit-transition: all 0.5s ease-out;
+        transition: all 0.5s ease-out;
+        color: #0085d2;
+      }
+    }
+  }
+  .page {
+    text-align: center;
+    height: 30px;
+    overflow: hidden;
+  }
+}
+</style>

+ 37 - 0
src/components/list/search.vue

@@ -0,0 +1,37 @@
+<template>
+  <div id="search">
+    <el-input placeholder="请输入名称" v-model="input" class="input-with-select" clearable>
+      <el-button slot="append" icon="el-icon-search" @click="searchData()"></el-button>
+    </el-input>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'search',
+  components: {},
+  data: function() {
+    return {
+      input: undefined,
+    };
+  },
+  created() {},
+  methods: {
+    searchData() {
+      this.$emit('toSearch', this.input && this.input === '' ? undefined : this.input);
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 65 - 0
src/components/list/tabs.vue

@@ -0,0 +1,65 @@
+<template>
+  <div id="tabs">
+    <!-- 主要成果单位 -->
+    <span v-for="(item, index) in displayList" :key="index" @click="change(item.name)">{{ item.name }}</span>
+    <!-- 其他 -->
+    <el-dropdown trigger="click" @command="change">
+      <span class="el-dropdown-link btn"><i class="el-icon-d-arrow-right"></i> </span>
+      <el-dropdown-menu slot="dropdown">
+        <el-dropdown-item v-for="(item, index) in dropList" :key="index" :command="item.name">{{ item.name }}</el-dropdown-item>
+      </el-dropdown-menu>
+    </el-dropdown>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'tabs',
+  props: {
+    displayList: { type: Array, default: () => [] },
+    dropList: { type: Array, default: () => [] },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    change(data) {
+      this.$emit('tabsChange', data);
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+span {
+  font-size: 16px;
+  padding: 8px 10px;
+  display: inline-block;
+  font-weight: bold;
+}
+span:hover {
+  cursor: pointer;
+  color: #409eff;
+}
+.btn {
+  padding: 0px 0 0 0;
+  position: relative;
+  top: 1px;
+  i {
+    font-size: 20px;
+    font-weight: bold;
+  }
+}
+</style>

+ 20 - 7
src/main.js

@@ -1,12 +1,25 @@
-import Vue from "vue";
-import App from "./App.vue";
-import router from "./router";
-import store from "./store";
-
+import Vue from 'vue';
+import App from './App.vue';
+import router from './router';
+import store from './store';
+import '@/plugins/element.js';
+import '@/plugins/axios';
+import '@/plugins/check-res';
+import '@/plugins/meta';
+import '@/plugins/filters';
+import '@/plugins/loading';
+import '@/plugins/var';
+import '@/plugins/methods';
+import '@/plugins/setting';
+// import InitStomp from '@/plugins/stomp';
 Vue.config.productionTip = false;
 
 new Vue({
   router,
   store,
-  render: h => h(App)
-}).$mount("#app");
+  render: h => h(App),
+}).$mount('#app');
+// InitStomp();
+window.vm = new Vue({
+  router,
+});

+ 19 - 0
src/plugins/axios.js

@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import AxiosWrapper from '@/util/axios-wrapper';
+
+const Plugin = {
+  install(vue, options) {
+    // 3. 注入组件
+    vue.mixin({
+      created() {
+        if (this.$store && !this.$store.$axios) {
+          this.$store.$axios = this.$axios;
+        }
+      },
+    });
+    // 4. 添加实例方法
+    vue.prototype.$axios = new AxiosWrapper(options);
+  },
+};
+
+Vue.use(Plugin, { baseUrl: process.env.VUE_APP_AXIOS_BASE_URL });

+ 39 - 0
src/plugins/check-res.js

@@ -0,0 +1,39 @@
+/* eslint-disable no-underscore-dangle */
+/* eslint-disable no-param-reassign */
+/* eslint-disable no-unused-vars */
+/* eslint-disable no-shadow */
+import Vue from 'vue';
+import _ from 'lodash';
+import { Message } from 'element-ui';
+
+const vm = new Vue({});
+const Plugin = {
+  install(Vue, options) {
+    // 4. 添加实例方法
+    Vue.prototype.$checkRes = (res, okText, errText) => {
+      let _okText = okText;
+      let _errText = errText;
+      if (!_.isFunction(okText) && _.isObject(okText) && okText != null) {
+        ({ okText: _okText, errText: _errText } = okText);
+      }
+      const { errcode = 0, errmsg } = res || {};
+      if (errcode === 0) {
+        if (_.isFunction(_okText)) {
+          return _okText();
+        }
+        if (_okText) {
+          Message.success(_okText);
+        }
+        return true;
+      }
+      if (_.isFunction(_errText)) {
+        return _errText();
+      }
+      Message.error(_errText || errmsg);
+      // Message({ message: _errText || errmsg, duration: 60000 });
+      return false;
+    };
+  },
+};
+
+Vue.use(Plugin);

+ 5 - 0
src/plugins/element.js

@@ -0,0 +1,5 @@
+import Vue from 'vue';
+import Element from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+
+Vue.use(Element);

+ 6 - 0
src/plugins/filters.js

@@ -0,0 +1,6 @@
+import Vue from 'vue';
+import filters from '@/util/filters';
+
+for (const method in filters) {
+  Vue.filter(method, filters[method]);
+}

+ 27 - 0
src/plugins/loading.js

@@ -0,0 +1,27 @@
+/* eslint-disable no-console */
+/* eslint-disable no-param-reassign */
+
+import Vue from 'vue';
+
+const Plugin = {
+  // eslint-disable-next-line no-unused-vars
+  install(vue, options) {
+    // 3. 注入组件
+    vue.mixin({
+      created() {
+        // eslint-disable-next-line no-underscore-dangle
+        const isRoot = this.constructor === Vue;
+        // console.log(`rootId:${rootVue_uid}; thisId:${this._uid}`);
+        // if (rootVue_uid !== 3) {
+        //   console.log(this);
+        // }
+        if (isRoot) {
+          const el = document.getElementById('loading');
+          if (el) el.style.display = 'none';
+        }
+      },
+    });
+  },
+};
+
+Vue.use(Plugin, { baseUrl: process.env.VUE_APP_AXIOS_BASE_URL });

+ 4 - 0
src/plugins/meta.js

@@ -0,0 +1,4 @@
+import Vue from 'vue';
+import Meta from 'vue-meta';
+
+Vue.use(Meta);

+ 33 - 0
src/plugins/methods.js

@@ -0,0 +1,33 @@
+import Vue from 'vue';
+import _ from 'lodash';
+const Plugin = {
+  install(Vue, options) {
+    // 3. 注入组件
+    Vue.mixin({
+      created() {
+        if (this.$store && !this.$store.$toUndefined) {
+          this.$store.$toUndefined = this.$toUndefined;
+        }
+      },
+    });
+    // 4. 添加实例方法
+    Vue.prototype.$toUndefined = object => {
+      let keys = Object.keys(object);
+      keys.map(item => {
+        object[item] = object[item] === '' ? (object[item] = undefined) : object[item];
+      });
+      return object;
+    };
+    Vue.prototype.$turnTo = item => {
+      if (item.info_type == 1) {
+        window.open(item.url);
+      } else {
+        let router = window.vm.$router;
+        let route = window.vm.$route.path;
+        router.push({ path: `/info/detail?id=${item.id}` });
+      }
+    };
+  },
+};
+
+Vue.use(Plugin);

+ 20 - 0
src/plugins/setting.js

@@ -0,0 +1,20 @@
+import Vue from 'vue';
+
+Vue.config.weixin = {
+  // baseUrl: process.env.BASE_URL + 'weixin',
+  baseUrl: `http://${location.host}`,
+};
+
+Vue.config.stomp = {
+  // brokerURL: 'ws://http://free.liaoningdoupo.com/ws',
+  brokerURL: '/ws', // ws://${location.host}/ws
+  connectHeaders: {
+    host: 'visit',
+    login: 'visit', //visit
+    passcode: 'visit', //visit123
+  },
+  // debug: true,
+  reconnectDelay: 5000,
+  heartbeatIncoming: 4000,
+  heartbeatOutgoing: 4000,
+};

+ 65 - 0
src/plugins/stomp.js

@@ -0,0 +1,65 @@
+/**
+ * 基于WebStomp的消息处理插件
+ */
+
+import Vue from 'vue';
+import _ from 'lodash';
+import assert from 'assert';
+import { Client } from '@stomp/stompjs/esm5/client';
+
+const Plugin = {
+  install(Vue, options) {
+    assert(_.isObject(options));
+    if (options.debug && !_.isFunction(options.debug)) {
+      options.debug = str => {
+        console.log(str);
+      };
+    }
+    assert(_.isString(options.brokerURL));
+    if (!options.brokerURL.startsWith('ws://')) {
+      options.brokerURL = `ws://${location.host}${options.brokerURL}`;
+    }
+
+    // 3. 注入组件
+    Vue.mixin({
+      beforeDestroy: function() {
+        if (this.$stompClient) {
+          this.$stompClient.deactivate();
+          delete this.$stompClient;
+        }
+      },
+    });
+
+    // 4. 添加实例方法
+    Vue.prototype.$stomp = function(subscribes = {}) {
+      // connect to mq
+      const client = new Client(options);
+      client.onConnect = frame => {
+        // Do something, all subscribes must be done is this callback
+        // This is needed because this will be executed after a (re)connect
+        console.log('[stomp] connected');
+        Object.keys(subscribes)
+          .filter(p => _.isFunction(subscribes[p]))
+          .forEach(key => {
+            client.subscribe(key, subscribes[key]);
+          });
+      };
+
+      client.onStompError = frame => {
+        // Will be invoked in case of error encountered at Broker
+        // Bad login/passcode typically will cause an error
+        // Complaint brokers will set `message` header with a brief message. Body may contain details.
+        // Compliant brokers will terminate the connection after any error
+        console.log('Broker reported error: ' + frame.headers['message']);
+        console.log('Additional details: ' + frame.body);
+      };
+
+      client.activate();
+
+      this.$stompClient = client;
+    };
+  },
+};
+export default () => {
+  Vue.use(Plugin, Vue.config.stomp);
+};

+ 25 - 0
src/plugins/var.js

@@ -0,0 +1,25 @@
+import Vue from 'vue';
+import _ from 'lodash';
+
+const getSiteId = () => {
+  let host = `${window.location.hostname}`; //`999991.smart.jilinjobswx.cn ${window.location.hostname}`
+  let schId;
+  host = host.replace('http://', '');
+  let arr = host.split('.');
+  if (arr.length > 0) {
+    schId = arr[0];
+    if (schId === 'smart') schId = 'master';
+    else `${schId}`.includes('localhost') || `${schId}`.includes('127.0.0.1') ? (schId = '99991') : '';
+    sessionStorage.setItem('schId', `${schId}`.includes('localhost') || `${schId}`.includes('127.0.0.1') ? '99991' : schId);
+  }
+  return schId;
+};
+const Plugin = {
+  install(vue, options) {
+    // 4. 添加实例方法
+    vue.prototype.$limit = 10;
+    vue.prototype.$site = getSiteId();
+  },
+};
+
+Vue.use(Plugin);

+ 174 - 19
src/router/index.js

@@ -1,28 +1,183 @@
-import Vue from "vue";
-import VueRouter from "vue-router";
-import Home from "../views/Home.vue";
-
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import store from '@/store/index';
+const jwt = require('jsonwebtoken');
+const list = [
+  {
+    path: '/market/list',
+    name: 'market_list',
+    meta: { title: '科技超市' },
+    component: () => import('../views/market/list.vue'),
+  },
+  {
+    path: '/techolchat/list',
+    name: 'techolchat_list',
+    meta: { title: '技术交流' },
+    component: () => import('../views/techolchat/list.vue'),
+  },
+  {
+    path: '/news/list',
+    name: 'news_list',
+    meta: { title: '新闻资讯' },
+    component: () => import('../views/news/list.vue'),
+  },
+];
+const admin = [
+  {
+    path: '/admin/live/science',
+    name: 'admin_live_science',
+    meta: { title: '科技频道管理中心' },
+    component: () => import('../views/admin/live/science.vue'),
+  },
+  {
+    path: '/admin/live/train',
+    name: 'admin_live_train',
+    meta: { title: '培训问诊管理中心' },
+    component: () => import('../views/admin/live/train.vue'),
+  },
+  {
+    path: '/admin/live/achieve',
+    name: 'admin_live_achieve',
+    meta: { title: '科技成果管理中心' },
+    component: () => import('../views/admin/live/achieve.vue'),
+  },
+];
 Vue.use(VueRouter);
-
-const routes = [
+const live = [
   {
-    path: "/",
-    name: "Home",
-    component: Home
+    path: '/',
+    name: 'index',
+    meta: { title: '网站首页' },
+    component: () => import('../views/index.vue'),
   },
   {
-    path: "/about",
-    name: "About",
-    // route level code-splitting
-    // this generates a separate chunk (about.[hash].js) for this route
-    // which is lazy-loaded when the route is visited.
-    component: () =>
-      import(/* webpackChunkName: "about" */ "../views/About.vue")
-  }
+    path: '/login',
+    name: 'login',
+    meta: { title: '登录' },
+    component: () => import('../views/login.vue'),
+  },
+  {
+    path: '/register',
+    name: 'register',
+    meta: { title: '注册账号' },
+    component: () => import('../views/register.vue'),
+  },
+  {
+    path: '/channelLive/index',
+    name: 'channelLive_index',
+    meta: { title: '直播大厅-科技频道', subSite: true },
+    component: () => import('../views/channelLive/index.vue'),
+  },
+  {
+    path: '/trainLive/index',
+    name: 'trainLive_index',
+    meta: { title: '直播大厅-培训问诊', subSite: true },
+    component: () => import('../views/trainLive/index.vue'),
+  },
+  {
+    path: '/personalLive/index',
+    name: 'personalLive_index',
+    meta: { title: '直播大厅-人才对接', subSite: true },
+    component: () => import('../views/personalLive/index.vue'),
+  },
+  {
+    path: '/achieveLive/before',
+    name: 'personalLive_before',
+    meta: { title: '直播大厅-展会活动页', subSite: true },
+    component: () => import('../views/achieveLive/before.vue'),
+  },
+  {
+    path: '/achieveLive/detail',
+    name: 'personalLive_detail',
+    meta: { title: '直播大厅-展会详情', subSite: true },
+    component: () => import('../views/achieveLive/detail.vue'),
+  },
+  {
+    path: '/website',
+    name: 'website',
+    component: () => import('../views/website.vue'),
+    children: [
+      // 新闻资讯
+      {
+        path: '/news/index',
+        name: 'news_index',
+        meta: { title: '新闻资讯', subSite: true },
+        component: () => import('../views/news/index.vue'),
+      },
+      // 科技超市
+      {
+        path: '/market/index',
+        name: 'market_index',
+        meta: { title: '科技超市', subSite: true },
+        component: () => import('../views/market/index.vue'),
+      },
+      ...list,
+      // 直播大厅
+      {
+        path: '/live/index',
+        name: 'live_index',
+        meta: { title: '直播大厅', subSite: true },
+        component: () => import('../views/live/index.vue'),
+      },
+      {
+        path: '/achieveLive/apply',
+        name: 'achieveLive_apply',
+        meta: { title: '直播大厅-申请参展', subSite: true },
+        component: () => import('../views/achieveLive/apply.vue'),
+      },
+      // 数据动态
+      {
+        path: '/dynamic/index',
+        name: 'dynamic_index',
+        meta: { title: '数据动态', subSite: true },
+        component: () => import('../views/dynamic/index.vue'),
+      },
+      // 技术交流
+      {
+        path: '/techolchat/index',
+        name: 'techolchat_index',
+        meta: { title: '技术交流', subSite: true },
+        component: () => import('../views/techolchat/index.vue'),
+      },
+      {
+        path: '/techolchat/product',
+        name: 'techolchat_product',
+        meta: { title: '信息发布', subSite: true },
+        component: () => import('../views/techolchat/product.vue'),
+      },
+      {
+        path: '/userCenter',
+        name: 'userCenter',
+        meta: { title: '管理中心' },
+        component: () => import('../views/userCenter/index.vue'),
+      },
+      ...admin,
+    ],
+  },
 ];
-
+const routes = [...live];
 const router = new VueRouter({
-  routes
+  mode: 'history',
+  base: process.env.VUE_APP_ROUTER,
+  routes,
+});
+router.beforeEach((to, from, next) => {
+  document.title = `${to.meta.title} `;
+  const token = localStorage.getItem('token');
+  if (to.path == '/userCenter') {
+    if (!token) {
+      next('/login');
+    } else {
+      let user = jwt.decode(token);
+      user.type = '4';
+      store.commit('setUser', user, { root: true });
+      next();
+    }
+  } else {
+    let user = jwt.decode(token);
+    store.commit('setUser', user, { root: true });
+    next();
+  }
 });
 
 export default router;

+ 67 - 5
src/store/index.js

@@ -1,11 +1,73 @@
-import Vue from "vue";
-import Vuex from "vuex";
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as ustate from '@common/src/store/user/state';
+import * as umutations from '@common/src/store/user/mutations';
+// 个人
+import personal from '@common/src/store/personal';
+// 机构
+import organization from '@common/src/store/organization';
+// 专家
+import expert from '@common/src/store/expert';
+// 产品
+import product from '@common/src/store/product';
+// 展会
+import dock from '@common/src/store/dock';
+// 展会图文
+import dockPw from '@common/src/store/dockPw';
+// 展会用户
+import dockUser from '@common/src/store/dockUser';
+// 培训问诊
+import trainLive from '@common/src/store/trainLive';
+// 交易备案
+import transaction from '@common/src/store/transaction';
+// 科技频道
+import channel from '@common/src/store/channel';
+import channelVideo from '@common/src/store/channelVideo';
+// e专利
+import patent from '@common/src/store/patent';
+// 项目路演
+import roadShow from '@common/src/store/roadShow';
+// 嘉宾访谈
+import interview from '@common/src/store/interview';
+// 技术新闻
+import column from '@common/src/store/column';
+import news from '@common/src/store/news';
+// 建言献策,网上调查
+import survey from '@common/src/store/survey';
+// 统计
+import statistics from '@common/src/store/statistics';
 
+// 字典表
+import category from '@common/src/store/category';
+import code from '@common/src/store/code';
+import place from '@common/src/store/place';
 Vue.use(Vuex);
 
 export default new Vuex.Store({
-  state: {},
-  mutations: {},
+  state: { ...ustate },
+  mutations: { ...umutations },
   actions: {},
-  modules: {}
+  modules: {
+    personal,
+    organization,
+    expert,
+    product,
+    dock,
+    dockUser,
+    trainLive,
+    channel,
+    channelVideo,
+    patent,
+    roadShow,
+    interview,
+    column,
+    news,
+    category,
+    code,
+    place,
+    survey,
+    statistics,
+    dockPw,
+    transaction,
+  },
 });

+ 117 - 0
src/util/axios-wrapper.js

@@ -0,0 +1,117 @@
+/* eslint-disable no-console */
+/* eslint-disable no-param-reassign */
+
+import _ from 'lodash';
+import Axios from 'axios';
+import { Util, Error } from 'naf-core';
+// import { Indicator } from 'mint-ui';
+import util from './user-util';
+
+const { trimData, isNullOrUndefined } = Util;
+const { ErrorCode } = Error;
+
+let currentRequests = 0;
+
+export default class AxiosWrapper {
+  constructor({ baseUrl = '', unwrap = true } = {}) {
+    this.baseUrl = baseUrl;
+    this.unwrap = unwrap;
+  }
+
+  // 替换uri中的参数变量
+  static merge(uri, query = {}) {
+    if (!uri.includes(':')) {
+      return uri;
+    }
+    const keys = [];
+    const regexp = /\/:([a-z0-9_]+)/gi;
+    let res;
+    // eslint-disable-next-line no-cond-assign
+    while ((res = regexp.exec(uri)) != null) {
+      keys.push(res[1]);
+    }
+    keys.forEach(key => {
+      if (!isNullOrUndefined(query[key])) {
+        uri = uri.replace(`:${key}`, query[key]);
+      }
+    });
+    return uri;
+  }
+
+  $get(uri, query, options) {
+    return this.$request(uri, null, query, options);
+  }
+
+  $post(uri, data = {}, query, options) {
+    return this.$request(uri, data, query, options);
+  }
+  $delete(uri, data = {}, router, query, options = {}) {
+    options = { ...options, method: 'delete' };
+    return this.$request(uri, data, query, options, router);
+  }
+  async $request(uri, data, query, options) {
+    // TODO: 合并query和options
+    if (_.isObject(query) && _.isObject(options)) {
+      options = { ...options, params: query, method: 'get' };
+    } else if (_.isObject(query) && !query.params) {
+      options = { params: query };
+    } else if (_.isObject(query) && query.params) {
+      options = query;
+    }
+    if (!options) options = {};
+    if (options.params) options.params = trimData(options.params);
+    const url = AxiosWrapper.merge(uri, options.params);
+    currentRequests += 1;
+    // Indicator.open({
+    //   spinnerType: 'fading-circle',
+    // });
+
+    try {
+      const axios = Axios.create({
+        baseURL: this.baseUrl,
+      });
+      axios.defaults.headers.common.Authorization = util.token;
+      let res = await axios.request({
+        method: isNullOrUndefined(data) ? 'get' : 'post',
+        url,
+        data,
+        responseType: 'json',
+        ...options,
+      });
+      res = res.data;
+      const { errcode, errmsg, details } = res;
+      if (errcode) {
+        console.warn(`[${uri}] fail: ${errcode}-${errmsg} ${details}`);
+        return res;
+      }
+      // unwrap data
+      if (this.unwrap) {
+        res = _.omit(res, ['errmsg', 'details']);
+        const keys = Object.keys(res);
+        if (keys.length === 1 && keys.includes('data')) {
+          res = res.data;
+        }
+      }
+      return res;
+    } catch (err) {
+      let errmsg = '接口请求失败,请稍后重试';
+      if (err.response) {
+        const { status } = err.response;
+        if (status === 401) errmsg = '用户认证失败,请重新登录';
+        if (status === 403) errmsg = '当前用户不允许执行该操作';
+      }
+      console.error(
+        `[AxiosWrapper] 接口请求失败: ${err.config && err.config.url} - 
+        ${err.message}`
+      );
+      return { errcode: ErrorCode.SERVICE_FAULT, errmsg, details: err.message };
+    } finally {
+      /* eslint-disable */
+      currentRequests -= 1;
+      if (currentRequests <= 0) {
+        currentRequests = 0;
+        // Indicator.close();
+      }
+    }
+  }
+}

+ 10 - 0
src/util/filters.js

@@ -0,0 +1,10 @@
+import _ from 'lodash';
+
+const filters = {
+  getName(object) {
+    const { data, searchItem } = object;
+    return _.get(data, searchItem) === undefined ? '' : _.get(data, searchItem);
+  },
+};
+
+export default filters;

+ 50 - 0
src/util/methods-util.js

@@ -0,0 +1,50 @@
+import { Util } from 'naf-core';
+
+const { isNullOrUndefined } = Util;
+
+export default {
+  //判断信息是否过期
+  isDateOff(dataDate) {
+    const now = new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
+    dataDate = new Date(dataDate);
+    return now.getTime() <= dataDate.getTime();
+  },
+  //判断企业是否可以执行此动作/显示
+  checkCorp(data) {
+    const { role, unit, selfUnit, status, displayType, userid } = data;
+    if (!isNullOrUndefined(selfUnit) && !isNullOrUndefined(status)) {
+      return role === 'corp' && selfUnit === unit && status === '0';
+    } else if (!isNullOrUndefined(displayType)) {
+      if (role === 'corp') {
+        return role === displayType;
+      } else {
+        return role === displayType && !isNullOrUndefined(userid);
+      }
+    }
+  },
+  //获取url的参数params
+  getParams() {
+    let str = location.href;
+    let num = str.indexOf('?');
+    const param = {};
+    str = str.substr(num + 1);
+    let num2 = str.indexOf('#');
+    let str2 = '';
+    if (num2 > 0) {
+      str2 = str.substr(0, num2);
+    } else {
+      num2 = str.indexOf('/');
+      str2 = str.substr(0, num2);
+    }
+    const arr = str2.split('&');
+    for (let i = 0; i < arr.length; i++) {
+      num = arr[i].indexOf('=');
+      if (num > 0) {
+        const name = arr[i].substring(0, num);
+        const value = arr[i].substr(num + 1);
+        param[name] = decodeURI(value);
+      }
+    }
+    return param;
+  },
+};

+ 69 - 0
src/util/user-util.js

@@ -0,0 +1,69 @@
+/* eslint-disable no-console */
+export default {
+  get user() {
+    const val = sessionStorage.getItem('user');
+    try {
+      if (val) return JSON.parse(val);
+    } catch (err) {
+      console.error(err);
+    }
+    return null;
+  },
+  set user(userinfo) {
+    sessionStorage.setItem('user', JSON.stringify(userinfo));
+  },
+  get token() {
+    return sessionStorage.getItem('token');
+  },
+  set token(token) {
+    sessionStorage.setItem('token', token);
+  },
+  get openid() {
+    return sessionStorage.getItem('openid');
+  },
+  set openid(openid) {
+    sessionStorage.setItem('openid', openid);
+  },
+  get isGuest() {
+    return !this.user || this.user.role === 'guest';
+  },
+  save({ userinfo, token }) {
+    sessionStorage.setItem('user', JSON.stringify(userinfo));
+    sessionStorage.setItem('token', token);
+  },
+
+  get corpInfo() {
+    const val = sessionStorage.getItem('corpInfo');
+    if (val) return JSON.parse(val);
+    return null;
+  },
+  set corpInfo(corpInfo) {
+    sessionStorage.setItem('corpInfo', JSON.stringify(corpInfo));
+  },
+  saveCorpInfo(corpInfo) {
+    sessionStorage.setItem('corpInfo', JSON.stringify(corpInfo));
+  },
+
+  get unit() {
+    const val = sessionStorage.getItem('unit');
+    if (val) return JSON.parse(val);
+    return null;
+  },
+  set unit(unitList) {
+    sessionStorage.setItem('unit', JSON.stringify(unitList));
+  },
+  saveUnit(unitList) {
+    sessionStorage.setItem('unit', JSON.stringify(unitList));
+  },
+  get userInfo() {
+    const val = sessionStorage.getItem('userInfo');
+    if (val) return JSON.parse(val);
+    return null;
+  },
+  set userInfo(userInfo) {
+    sessionStorage.setItem('userInfo', JSON.stringify(userInfo));
+  },
+  saveUserInfo(userInfo) {
+    sessionStorage.setItem('userInfo', JSON.stringify(userInfo));
+  },
+};

+ 0 - 5
src/views/About.vue

@@ -1,5 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>

+ 0 - 18
src/views/Home.vue

@@ -1,18 +0,0 @@
-<template>
-  <div class="home">
-    <img alt="Vue logo" src="../assets/logo.png" />
-    <HelloWorld msg="Welcome to Your Vue.js App" />
-  </div>
-</template>
-
-<script>
-// @ is an alias to /src
-import HelloWorld from "@/components/HelloWorld.vue";
-
-export default {
-  name: "Home",
-  components: {
-    HelloWorld
-  }
-};
-</script>

+ 84 - 0
src/views/achieveLive/apply.vue

@@ -0,0 +1,84 @@
+<template>
+  <div id="apply">
+    <el-row>
+      <el-col :span="24" class="main">
+        <div class="w_1200">
+          <el-col :span="24" class="top">
+            <el-col :span="3">
+              <el-image :src="img_path" style="width:105px;height:105px;"></el-image>
+            </el-col>
+            <el-col :span="20">
+              <p>温馨提示:</p>
+              <p>1、为了保证您的信息能顺利通过我们的审核,请将信息的真实情况尽可能全面的发布出来!</p>
+              <p>2、根据我们的长期跟踪统计,信息完整度越高,越容易获得目标客户的关注!</p>
+              <p>3、信息完整度越高,将在我们的平台搜索结果排序靠前、获得推荐机会,以及享受增值服务试用机会!</p>
+            </el-col>
+          </el-col>
+          <el-col :span="24" class="down">
+            <el-tabs v-model="active" type="card">
+              <el-tab-pane label="技术成果" name="first">
+                <applyAchieve :dock_id="dock_id" @toRest="toRest"></applyAchieve>
+              </el-tab-pane>
+              <el-tab-pane label="科技需求" name="second" disabled>
+                <applyTechol></applyTechol>
+              </el-tab-pane>
+            </el-tabs>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import applyAchieve from './apply/applyAchieve.vue';
+import applyTechol from './apply/applyTechol.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'apply',
+  props: {},
+  components: {
+    applyAchieve,
+    applyTechol,
+  },
+  data: function() {
+    return {
+      img_path: require('@common/src/assets/product.png'),
+      active: 'first',
+    };
+  },
+  created() {},
+  methods: {
+    // 取消
+    toRest() {
+      this.$router.push({ path: '/live/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    dock_id() {
+      return this.$route.query.id;
+    },
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  min-height: 557px;
+  padding: 15px 0;
+  .top {
+    background: #f3faff;
+    padding: 15px;
+    border: 1px solid #ccc;
+    margin: 0 0 15px 0;
+    p {
+      font-size: 16px;
+    }
+  }
+}
+</style>

+ 112 - 0
src/views/achieveLive/apply/applyAchieve.vue

@@ -0,0 +1,112 @@
+<template>
+  <div id="applyAchieve">
+    <el-row>
+      <el-col :span="24">
+        <data-form :data="form" :fields="fields" :rules="{}" :needSave="false">
+          <template #custom="{item,form}">
+            <template v-if="item.model === 'goodsList'">
+              <el-select v-model="form.goodsList" value-key="id" multiple collapse-tags placeholder="请选择选择产品">
+                <el-option v-for="(item, index) in goodsList" :key="index" :label="item.name" :value="item"> </el-option>
+              </el-select>
+            </template>
+          </template>
+          <template slot="submit">
+            <el-button type="danger" size="mini" @click="toRest">取消</el-button>
+            <el-button type="primary" size="mini" @click="toSave">确认</el-button>
+          </template>
+        </data-form>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import dataForm from '@common/src/components/frame/form.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: product } = createNamespacedHelpers('product');
+const { mapActions: dockUser } = createNamespacedHelpers('dockUser');
+export default {
+  name: 'applyAchieve',
+  props: {
+    dock_id: { type: String },
+  },
+  components: { dataForm },
+  data: function() {
+    return {
+      fields: [
+        { label: '用户名称', model: 'user_name' },
+        { label: '联系电话', model: 'contact_tel' },
+        { label: '申请时间', model: 'apply_time', type: 'date' },
+        { label: '成果列表', model: 'goodsList', custom: true },
+      ],
+      form: {},
+      // 成果列表
+      goodsList: [],
+      isNews: true,
+    };
+  },
+  async created() {
+    await this.searchOther();
+  },
+  methods: {
+    ...product({ productQuery: 'query' }),
+    ...dockUser(['query', 'fetch', , 'update', 'create']),
+    async searchOther() {
+      // 查询用户
+      let res = await this.query({ dock_id: this.dock_id, user_id: this.user.id });
+      if (this.$checkRes(res)) {
+        if (res.total > 0) {
+          this.$set(this, `form`, res.data[0]);
+          this.$set(this, `isNews`, false);
+        } else {
+          let data = {
+            dock_id: this.dock_id,
+            user_id: this.user.id,
+            user_name: this.user.name,
+            contact_tel: this.user.phone,
+          };
+          this.$set(this, `form`, data);
+        }
+      }
+      // 查询用户产品
+      res = await this.productQuery({ type: '1', status: '2', user_id: this.user.id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `goodsList`, res.data);
+      }
+    },
+    // 确认
+    async toSave() {
+      let data = this.form;
+      if (this.isNews) {
+        let res = await this.create(data);
+        if (this.$checkRes(res)) {
+          this.$message({
+            message: '创建参展信息成功',
+            type: 'success',
+          });
+          this.toRest();
+        }
+      } else {
+        let res = await this.update(data);
+        if (this.$checkRes(res)) {
+          this.$message({
+            message: '修改参展信息成功',
+            type: 'success',
+          });
+          this.toRest();
+        }
+      }
+    },
+    // 取消
+    toRest() {
+      this.$emit('toRest');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped></style>

+ 32 - 0
src/views/achieveLive/apply/applyTechol.vue

@@ -0,0 +1,32 @@
+<template>
+  <div id="applyTechol">
+    <el-row>
+      <el-col :span="24">
+        <p>applyTechol</p>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'applyTechol',
+  props: {},
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped></style>

+ 129 - 0
src/views/achieveLive/before.vue

@@ -0,0 +1,129 @@
+<template>
+  <div id="before">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-col :span="24" class="top">
+          <el-col :span="24" class="left">
+            中科在线(长春)
+          </el-col>
+          <el-col :span="24" class="right">
+            直播大厅
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="main">
+          <div class="w_1200">
+            <el-col :span="24" class="title">{{ dockInfo.title }}</el-col>
+            <el-col :span="24" class="txt">主办方:{{ dockInfo.sponsor }}</el-col>
+            <el-col :span="24" class="txt">承办方:{{ dockInfo.organizer }}</el-col>
+            <el-col :span="24" class="txt">技术支持:长春市福瑞科技有限公司</el-col>
+            <el-col :span="24" class="btn">
+              <el-button @click="dockBtn(dockInfo)">进入活动现场<i class="iconfont icon-bofang"></i></el-button>
+            </el-col>
+          </div>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dock } = createNamespacedHelpers('dock');
+export default {
+  name: 'before',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      dockInfo: {},
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...dock(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `dockInfo`, res.data);
+      }
+    },
+    dockBtn(data) {
+      if (data.room_id == '1007') {
+        this.$router.push({ path: '/halltwo/directTwo', query: { id: data.id } });
+      } else {
+        this.$router.push({ path: '/achieveLive/detail', query: { id: data.id } });
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.w_1200 {
+  width: 1200px;
+  margin: 0 auto;
+}
+.style {
+  background-image: url('~@common/src/assets/directBack.png');
+  height: 100vh;
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+  .top {
+    .left {
+      height: 40px;
+      line-height: 40px;
+      padding: 0 15px;
+      font-size: 20px;
+      color: #fffa93;
+    }
+    .right {
+      text-align: center;
+      color: #fffa93;
+      font-size: 50px;
+      text-shadow: 2px 2px 5px #000;
+    }
+  }
+  .main {
+    text-align: center;
+    margin: 100px 0 0 0;
+    .title {
+      font-size: 40px;
+      color: #fffa93;
+      font-weight: bold;
+      font-family: monospace;
+      padding: 0 0 20px 0;
+      height: 100px;
+    }
+    .txt {
+      font-size: 25px;
+      color: #fffa93;
+      padding: 0 0 10px 0;
+    }
+    /deep/.btn {
+      margin: 50px 0 0 0;
+      .el-button {
+        background: linear-gradient(to bottom, #ffbd00 0%, #fd5a00 100%);
+        color: #fff;
+        border: none;
+        border-radius: 25px;
+        padding: 15px 40px;
+        font-size: 20px;
+      }
+    }
+  }
+}
+</style>

+ 182 - 0
src/views/achieveLive/detail.vue

@@ -0,0 +1,182 @@
+<template>
+  <div id="detail">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="top">
+          <div class="w_1200">
+            <topInfo :info="liveInfo"></topInfo>
+          </div>
+        </el-col>
+        <el-col :span="24" class="info">
+          <div class="w_1200">
+            <el-col :span="24" class="one">
+              <el-col :span="12" class="left">
+                <videoData :info="liveInfo"></videoData>
+              </el-col>
+              <el-col :span="12" class="right">
+                <imgtxtData></imgtxtData>
+              </el-col>
+            </el-col>
+            <el-col :span="24" class="two">
+              <productData></productData>
+            </el-col>
+            <el-col :span="24" class="thr">
+              <expertData></expertData>
+            </el-col>
+            <el-col :span="24" class="four">
+              <el-image :src="fourImg"> </el-image>
+            </el-col>
+            <el-col :span="24" class="five">
+              <el-col :span="8" class="left">
+                <guestData></guestData>
+              </el-col>
+              <el-col :span="8" class="cen">
+                <projectData></projectData>
+              </el-col>
+              <el-col :span="8" class="right">
+                <chatData></chatData>
+              </el-col>
+            </el-col>
+          </div>
+        </el-col>
+        <el-col :span="24" class="foot">
+          <foot></foot>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import foot from '@common/src/components/common/foot.vue';
+import topInfo from './detail/topInfo.vue';
+import videoData from './detail/videoData.vue';
+import imgtxtData from './detail/imgtxtData.vue';
+import productData from './detail/productData.vue';
+import expertData from './detail/expertData.vue';
+import guestData from './detail/guestData.vue';
+import projectData from './detail/projectData.vue';
+import chatData from './detail/chatData.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dock } = createNamespacedHelpers('dock');
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'detail',
+  props: {},
+  components: { foot, topInfo, videoData, imgtxtData, productData, expertData, guestData, projectData, chatData },
+  data: function() {
+    return {
+      // 展会详情
+      liveInfo: {},
+      // 第四部分
+      fourImg: require('@common/src/assets/live/top_bg.png'),
+    };
+  },
+  async created() {
+    await this.searchInfo();
+  },
+  methods: {
+    ...dock(['fetch']),
+    async searchInfo() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `liveInfo`, res.data);
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .top {
+    background: url('~@common/src/assets/live/dock_top.png');
+    height: 480px;
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+  }
+  .info {
+    min-height: 500px;
+    background: #fcfcfd;
+    .one {
+      height: 480px;
+      background-color: #fff;
+      margin: 0 0 15px 0;
+      .left {
+        width: 49%;
+        height: 480px;
+        margin: 0 15px 0 0;
+        border: 4px solid #000;
+      }
+      .right {
+        width: 49.5%;
+        height: 480px;
+        box-shadow: 0 0 5px #ccc;
+        border-radius: 5px;
+      }
+    }
+    .two {
+      height: 620px;
+      background-color: #fff;
+      box-shadow: 0 0 5px #ccc;
+      border-radius: 5px;
+      margin: 0 0 15px 0;
+    }
+    .thr {
+      height: 560px;
+      background-color: #fff;
+      box-shadow: 0 0 5px #ccc;
+      border-radius: 5px;
+      margin: 0 0 15px 0;
+    }
+    .four {
+      height: 120px;
+      box-shadow: 0 0 5px #ccc;
+      border-radius: 5px;
+      margin: 0 0 15px 0;
+      .el-image {
+        height: 120px;
+        overflow: hidden;
+        border-radius: 5px;
+      }
+    }
+    .five {
+      height: 510px;
+      background-color: #fff;
+      margin: 0 0 15px 0;
+      .left {
+        width: 33%;
+        height: 510px;
+        overflow: hidden;
+        box-shadow: 0 0 5px #ccc;
+        border-radius: 5px;
+        margin: 0 10px 0 0;
+      }
+      .cen {
+        width: 33%;
+        height: 510px;
+        overflow: hidden;
+        box-shadow: 0 0 5px #ccc;
+        border-radius: 5px;
+        margin: 0 10px 0 0;
+      }
+      .right {
+        width: 32.3%;
+        height: 510px;
+        overflow: hidden;
+        box-shadow: 0 0 5px #ccc;
+        border-radius: 5px;
+      }
+    }
+  }
+}
+</style>

+ 118 - 0
src/views/achieveLive/detail/chatData.vue

@@ -0,0 +1,118 @@
+<template>
+  <div id="guestData">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-tabs v-model="active" type="card">
+          <el-tab-pane label="公共聊天" name="first">
+            <el-col :span="24" class="info">
+              <el-col :span="24" class="list" v-for="(item, index) in list" :key="index">
+                <el-col :span="24" class="user">
+                  <span>[{{ item.create_time }}]</span>
+                  <span>{{ item.user }}</span>
+                </el-col>
+                <el-col :span="24" class="content">
+                  {{ item.content }}
+                </el-col>
+              </el-col>
+            </el-col>
+            <el-col :span="24" class="send">
+              <el-col :span="14" class="text">
+                <el-input v-model="text"></el-input>
+              </el-col>
+              <el-col :span="10" class="btn">
+                <el-button type="primary" size="mini">发送</el-button>
+                <el-button type="primary" size="mini">快捷发言</el-button>
+              </el-col>
+            </el-col>
+          </el-tab-pane>
+        </el-tabs>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: interview } = createNamespacedHelpers('interview');
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'guestData',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      active: 'first',
+      list: [
+        {
+          create_time: '2020-12-12 12:00:00',
+          user: '发言人',
+          content: '发言内容发言内容发言内容发言内容发言内容发言内容发言内容发言内容',
+        },
+        { create_time: '2020-12-12 12:00:00', user: '发言人', content: '发言内容' },
+      ],
+      // 发言内容
+      text: '',
+    };
+  },
+  async created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .info {
+    padding: 0 10px;
+    height: 410px;
+    overflow-y: auto;
+    .list {
+      margin: 0 0 10px 0;
+      .user {
+        margin: 0 0 5px 0;
+        span {
+          font-size: 16px;
+        }
+        span:nth-child(1) {
+          color: #666;
+        }
+        span:nth-child(2) {
+          font-weight: bold;
+          padding: 0 0 0 5px;
+        }
+      }
+      .content {
+        background-color: #f1f1f1;
+        padding: 10px 5px;
+        border-radius: 5px;
+      }
+    }
+  }
+  .send {
+    height: 30px;
+    overflow: hidden;
+    padding: 0 10px;
+    margin: 10px 0 0 0;
+    .text {
+      /deep/.el-input__inner {
+        height: 30px;
+        line-height: 30px;
+      }
+    }
+    .btn {
+      text-align: center;
+    }
+  }
+  /deep/.el-tabs__header {
+    margin: 0 0 10px 0;
+  }
+}
+</style>

+ 117 - 0
src/views/achieveLive/detail/expertData.vue

@@ -0,0 +1,117 @@
+<template>
+  <div id="expertData">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-tabs v-model="active" type="card">
+          <el-tab-pane label="专家智库" name="first">
+            <el-col :span="24" class="info">
+              <el-col :span="8" class="list" v-for="(item, index) in expertList" :key="index">
+                <el-col :span="6" class="image">
+                  <el-image :src="item.img_path">
+                    <div slot="error" class="image-slot">
+                      <el-image :src="img_url"></el-image>
+                    </div>
+                  </el-image>
+                </el-col>
+                <el-col :span="18" class="text">
+                  <p class="textOver">{{ item.name }}</p>
+                  <p class="textOver">{{ item.zwzc }}</p>
+                  <p class="textOver">{{ item.company }}</p>
+                  <p>
+                    <el-button type="primary" size="mini">详情</el-button>
+                    <el-button type="success" size="mini">对接</el-button>
+                  </p>
+                </el-col>
+              </el-col>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini">查看更多专家</el-button>
+            </el-col>
+          </el-tab-pane>
+        </el-tabs>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: expert } = createNamespacedHelpers('expert');
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'expertData',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      active: 'first',
+      // 专家列表
+      // 无头像
+      img_url: require('@common/src/assets/live/d10_fbb1.png'),
+      expertList: [],
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...expert(['query']),
+    async search({ skip = 0, limit = 9, ...info } = {}) {
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `expertList`, res.data);
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .info {
+    height: 465px;
+    overflow: hidden;
+    padding: 0 10px;
+    .list {
+      width: 32.6%;
+      height: 140px;
+      box-shadow: 0 0 5px #ccc;
+      border-radius: 5px;
+      margin: 5px 10px 10px 0;
+      padding: 10px;
+      .image {
+        height: 120px;
+        overflow: hidden;
+        border: 1px dashed #ccc;
+        .el-image {
+          height: 120px;
+          overflow: hidden;
+        }
+      }
+      .text {
+        padding: 0 0 0 10px;
+        p {
+          font-size: 16px;
+          margin: 0 0 8px 0;
+        }
+        p:nth-child(1) {
+          font-size: 18px;
+          font-weight: bold;
+        }
+      }
+    }
+    .list:nth-child(3n) {
+      margin: 5px 0 10px 0;
+    }
+  }
+  .btn {
+    text-align: center;
+  }
+}
+</style>

+ 116 - 0
src/views/achieveLive/detail/guestData.vue

@@ -0,0 +1,116 @@
+<template>
+  <div id="guestData">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-tabs v-model="active" type="card">
+          <el-tab-pane label="嘉宾访谈" name="first">
+            <el-col :span="24" class="info">
+              <el-col :span="24" class="list" v-for="(item, index) in list" :key="index">
+                <el-col :span="6" class="image">
+                  <el-image :src="item.picture">
+                    <div slot="error" class="image-slot">
+                      <p>暂无图片</p>
+                    </div>
+                  </el-image>
+                </el-col>
+                <el-col :span="18" class="text">
+                  <p class="textOver">{{ item.title }}</p>
+                  <p class="textOver">更新时间:{{ item.publish_time }}</p>
+                  <p class="textOver">信息来源:{{ item.origin }}</p>
+                </el-col>
+              </el-col>
+            </el-col>
+          </el-tab-pane>
+        </el-tabs>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: interview } = createNamespacedHelpers('interview');
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'guestData',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      active: 'first',
+      list: [],
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...interview(['query']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.query({ skip, dock_id: this.id, ...info });
+      if (this.$checkRes(res)) this.$set(this, `list`, res.data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .info {
+    padding: 0 10px;
+    height: 450px;
+    overflow-y: auto;
+    .list {
+      border: 1px solid #ccc;
+      border-radius: 5px;
+      margin: 0 0 10px 0;
+      padding: 10px;
+      .image {
+        height: 80px;
+        overflow: hidden;
+        border: 1px dashed #ccc;
+        text-align: center;
+        .el-image {
+          height: 80px;
+        }
+        p {
+          line-height: 80px;
+        }
+      }
+      .text {
+        padding: 0 0 0 10px;
+        p {
+          font-size: 14px;
+          margin: 0 0 5px 0;
+        }
+        p:nth-child(1) {
+          font-size: 16px;
+          font-weight: bold;
+          padding: 3px 0;
+        }
+      }
+    }
+    .list:hover {
+      cursor: pointer;
+      border: 1px solid #409eff;
+      .text {
+        p:nth-child(1) {
+          color: #409eff;
+        }
+      }
+    }
+  }
+  /deep/.el-tabs__header {
+    margin: 0 0 10px 0;
+  }
+}
+</style>

+ 68 - 0
src/views/achieveLive/detail/imgtxtData.vue

@@ -0,0 +1,68 @@
+<template>
+  <div id="guestData">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-tabs v-model="active" type="card">
+          <el-tab-pane label="图文直播" name="first">
+            图文直播
+          </el-tab-pane>
+          <el-tab-pane label="洽谈合作" name="second">
+            洽谈合作
+          </el-tab-pane>
+          <el-tab-pane label="达成意向" name="third">
+            达成意向
+          </el-tab-pane>
+          <el-tab-pane label="交易完成" name="fourth">
+            交易完成
+          </el-tab-pane>
+        </el-tabs>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: interview } = createNamespacedHelpers('interview');
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'guestData',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      active: 'first',
+      list: [],
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...interview(['query']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {},
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .info {
+    padding: 0 10px;
+    height: 450px;
+    overflow-y: auto;
+  }
+  /deep/.el-tabs__header {
+    margin: 0 0 10px 0;
+  }
+}
+</style>

+ 188 - 0
src/views/achieveLive/detail/productData.vue

@@ -0,0 +1,188 @@
+<template>
+  <div id="productData">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-tabs v-model="active" type="card">
+          <el-tab-pane label="技术成果" name="first">
+            <el-col :span="24" class="first">
+              <el-col :span="5" class="achieveList" v-for="(item, index) in achieveList" :key="index">
+                <el-col :span="24" class="top">
+                  <p class="textOver">{{ item.name }}</p>
+                  <p>{{ item.achievebrief }}</p>
+                  <p class="textOver">领域:{{ item.field }}</p>
+                  <p class="textOver">联系人{{ item.contacts }}</p>
+                </el-col>
+                <el-col :span="24" class="down">
+                  <el-button size="mini" type="primary">详情</el-button>
+                  <el-button size="mini" type="success">对接</el-button>
+                </el-col>
+              </el-col>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini">查看更多项目</el-button>
+            </el-col>
+          </el-tab-pane>
+          <el-tab-pane label="科技需求" name="second">
+            <el-col :span="24" class="second">
+              <el-col :span="5" class="techolList" v-for="(item, index) in techolList" :key="index">
+                <el-col :span="24" class="top">
+                  <p>{{ item.name }}</p>
+                  <p class="textOver">领域:{{ item.field }}</p>
+                </el-col>
+                <el-col :span="24" class="down">
+                  <el-button size="mini" type="primary">详情</el-button>
+                  <el-button size="mini" type="success">对接</el-button>
+                </el-col>
+              </el-col>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini">查看更多项目</el-button>
+            </el-col>
+          </el-tab-pane>
+        </el-tabs>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dock } = createNamespacedHelpers('dock');
+export default {
+  name: 'productData',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      active: 'first',
+      achieveList: [],
+      techolList: [
+        {
+          name: '科技需求',
+          field: '所属领域',
+        },
+      ],
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...dock(['productQuery']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      // 技术成果
+      let res = await this.productQuery({ skip, limit, type: '1', dock_id: this.id, ...info });
+      if (this.$checkRes(res)) this.$set(this, `achieveList`, res.data);
+      // 科技需求
+      res = await this.productQuery({ skip, limit, type: '0', dock_id: this.id, ...info });
+      if (this.$checkRes(res)) this.$set(this, `techolList`, res.data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .btn {
+    text-align: center;
+  }
+  .first {
+    padding: 0 10px;
+    height: 530px;
+    overflow: hidden;
+    .achieveList {
+      width: 18.9%;
+      height: 250px;
+      background: url('~@common/src/assets/achieve.png');
+      background-size: 100% 100%;
+      background-repeat: no-repeat;
+      margin: 0 15px 15px 0;
+      padding: 22px 22px 8px 22px;
+      .top {
+        height: 190px;
+        overflow: hidden;
+        p:nth-child(1) {
+          font-size: 18px;
+          font-weight: bold;
+          padding: 5px 0;
+        }
+        p:nth-child(2) {
+          font-size: 12px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          -webkit-line-clamp: 6;
+          word-break: break-all;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          padding: 0 5px;
+          height: 105px;
+          line-height: 17px;
+        }
+        p:nth-child(3) {
+          font-size: 14px;
+          padding: 4px 0 0 0;
+        }
+        p:nth-child(4) {
+          font-size: 14px;
+          padding: 4px 0 0 0;
+        }
+      }
+      .down {
+        text-align: center;
+      }
+    }
+    .achieveList:nth-child(5n) {
+      margin: 0 0 15px 0;
+    }
+  }
+  .second {
+    padding: 0 10px;
+    height: 530px;
+    overflow: hidden;
+    .techolList {
+      width: 18.9%;
+      height: 250px;
+      border: 1px solid #ccc;
+      border-radius: 5px;
+      margin: 0 15px 15px 0;
+      padding: 22px 22px 8px 22px;
+      .top {
+        height: 190px;
+        overflow: hidden;
+        p:nth-child(1) {
+          height: 145px;
+          text-align: center;
+          font-size: 18px;
+          margin: 0 0 10px 0;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          -webkit-line-clamp: 6;
+          word-break: break-all;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+        }
+        p:nth-child(2) {
+          text-align: center;
+          font-size: 16px;
+        }
+      }
+      .down {
+        text-align: center;
+      }
+    }
+    .techolList:nth-child(5n) {
+      margin: 0 0 15px 0;
+    }
+  }
+  /deep/.el-tabs__header {
+    margin: 0 0 10px 0;
+  }
+}
+</style>

+ 110 - 0
src/views/achieveLive/detail/projectData.vue

@@ -0,0 +1,110 @@
+<template>
+  <div id="projectData">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-tabs v-model="active" type="card">
+          <el-tab-pane label="项目路演" name="first">
+            <el-col :span="24" class="info">
+              <el-col :span="24" class="list" v-for="(item, index) in list" :key="index">
+                <el-col :span="24" class="text">
+                  <el-col :span="18" class="title textOver">
+                    {{ item.title }}
+                  </el-col>
+                  <el-col :span="6" class="date textOver">
+                    {{ item.publish_time }}
+                  </el-col>
+                </el-col>
+                <el-col :span="24" class="brief">
+                  {{ item.brief }}
+                </el-col>
+              </el-col>
+            </el-col>
+          </el-tab-pane>
+        </el-tabs>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: roadShow } = createNamespacedHelpers('roadShow');
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'projectData',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      active: 'first',
+      list: [],
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...roadShow(['query']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.query({ skip, dock_id: this.id, ...info });
+      if (this.$checkRes(res)) this.$set(this, `list`, res.data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .info {
+    padding: 0 10px;
+    height: 450px;
+    overflow-y: auto;
+    .list {
+      border: 1px solid #ccc;
+      padding: 10px;
+      border-radius: 5px;
+      margin: 0 0 10px 0;
+      .text {
+        margin: 0 0 5px 0;
+        .title {
+          font-size: 16px;
+          font-weight: bold;
+        }
+        .date {
+          font-size: 14px;
+          text-align: right;
+        }
+      }
+      .brief {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        -webkit-line-clamp: 2;
+        word-break: break-all;
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+      }
+    }
+    .list:hover {
+      cursor: pointer;
+      border: 1px solid #409eff;
+      .text {
+        .title {
+          color: #409eff;
+        }
+      }
+    }
+  }
+  /deep/.el-tabs__header {
+    margin: 0 0 10px 0;
+  }
+}
+</style>

+ 119 - 0
src/views/achieveLive/detail/topInfo.vue

@@ -0,0 +1,119 @@
+<template>
+  <div id="topInfo">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-col :span="24" class="one">
+          {{ info.title }}
+        </el-col>
+        <el-col :span="24" class="two">
+          <span>主办方:</span>
+          <span>{{ info.sponsor }}</span>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <p>
+            <span>同时在线</span>
+            <span>{{ 1000 + statNum.tszx || 0 }}人</span>
+          </p>
+          <p>
+            <span>特邀嘉宾</span>
+            <span>{{ statNum.tyjb || 0 }}人</span>
+          </p>
+          <p>
+            <span>洽谈合作</span>
+            <span>{{ statNum.qthz || 0 }}项</span>
+          </p>
+          <p>
+            <span>达成意愿</span>
+            <span>{{ statNum.dcyx || 0 }}项</span>
+          </p>
+          <p>
+            <span>交易完成</span>
+            <span>{{ statNum.jywc || 0 }}项</span>
+          </p>
+          <p>
+            <span>参展项目</span>
+            <span>{{ statNum.czxm || 0 }}项</span>
+          </p>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'topInfo',
+  props: {
+    info: { type: Object },
+  },
+  components: {},
+  data: function() {
+    return {
+      statNum: {},
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .one {
+    text-align: center;
+    font-size: 40px;
+    color: #fff;
+    padding: 6% 8% 0 10%;
+    height: 185px;
+    margin: 0 0 40px 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    -webkit-line-clamp: 2;
+    word-break: break-all;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+  }
+  .two {
+    text-align: center;
+    font-size: 30px;
+    color: #fff;
+    margin: 0 0 50px 0;
+  }
+  .thr {
+    p {
+      float: left;
+      width: 15%;
+      background: #fff;
+      margin: 0 10px 0 10px !important;
+      color: #000;
+      height: 50px;
+      line-height: 50px;
+      border-radius: 30px;
+      span:first-child {
+        display: inline-block;
+        width: 56%;
+        text-align: center;
+        height: 50px;
+        line-height: 50px;
+        font-size: 16px;
+        background: red;
+        border-radius: 30px;
+        color: #fff;
+        font-weight: 700;
+      }
+      span:last-child {
+        display: inline-block;
+        width: 42%;
+        text-align: center;
+        font-size: 15px;
+        font-weight: 700;
+      }
+    }
+  }
+}
+</style>

+ 178 - 0
src/views/achieveLive/detail/videoData.vue

@@ -0,0 +1,178 @@
+<template>
+  <div id="videoData">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-col :span="24" class="top">
+          <el-col :span="4" class="txt">
+            <span style="color:#FF8400;">视频</span>
+            <span>直播</span>
+          </el-col>
+          <el-col :span="20" class="title textOver">
+            {{ info.title }}
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="video">
+          <video :src="videoPath" controls autoplay loop v-if="videoList != ''">
+            <source src="movie.mp4" type="video/mp4" />
+            <source src="movie.ogg" type="video/ogg" />
+          </video>
+          <div class="videointro" v-else>
+            <p>{{ info.title }}</p>
+          </div>
+        </el-col>
+        <el-col :span="24" class="list">
+          <swiper :list="videoList" :options="options">
+            <template v-slot="{ index, item }">
+              <p :class="`${menuIndex == index ? 'indexClass' : 'videodata'}`" @click="changeMenu(index, item)">{{ item.videointro }}</p>
+            </template>
+          </swiper>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import swiper from '@c/frame/swiper-frame.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dock } = createNamespacedHelpers('dock');
+export default {
+  name: 'videoData',
+  props: {
+    info: { type: Object },
+  },
+  components: { swiper },
+  data: function() {
+    return {
+      // 视频路径
+      videoPath: '',
+      // 视频列表
+      videoList: [],
+      menuIndex: '0',
+      options: {
+        slidesPerView: 5,
+        spaceBetween: 10,
+        // 分页
+        navigation: {
+          nextEl: '.swiper-button-next',
+          prevEl: '.swiper-button-prev',
+        },
+      },
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...dock(['fetch']),
+    async search() {
+      // let res = await this.fetch(this.id);
+      // if (this.$checkRes(res)) {
+      //   this.$set(this, `videoList`, res.data.videodata);
+      //   this.changeMenu('0', this.videoList[0]);
+      // }
+      let data = [
+        {
+          videointro: '1',
+          file_path: require('@common/src/assets/video1.mp4'),
+        },
+        {
+          videointro: '2',
+          file_path: require('@common/src/assets/video2.mp4'),
+        },
+      ];
+      this.$set(this, `videoList`, data);
+      this.changeMenu('0', this.videoList[0]);
+    },
+    changeMenu(index, item) {
+      if (item) {
+        this.menuIndex = index;
+        this.$set(this, `videoPath`, item.file_path);
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .top {
+    height: 60px;
+    overflow: hidden;
+    .txt {
+      height: 60px;
+      line-height: 40px;
+      font-size: 20px;
+      font-weight: 700;
+      text-align: center;
+    }
+    .title {
+      height: 60px;
+      line-height: 65px;
+      font-weight: bolder;
+      -webkit-text-stroke: 1px #6e042c;
+      -webkit-text-fill-color: #fff;
+      letter-spacing: 3px;
+      font-size: 20px;
+    }
+  }
+  .video {
+    height: 365px;
+    overflow: hidden;
+    video {
+      width: 100%;
+      height: 365px;
+      background: #000;
+      object-fit: fill;
+    }
+    .videointro {
+      height: 365px;
+      text-align: center;
+      background-image: url('~@common/src/assets/directBack.png');
+      background-size: 100% 100%;
+      background-repeat: no-repeat;
+      p {
+        text-align: center;
+        font-size: 30px;
+        padding: 60px 15px;
+        color: #fff;
+      }
+    }
+  }
+  .list {
+    height: 45px;
+    overflow: hidden;
+    .videodata {
+      border-radius: 10px;
+      background: #cccccc8f;
+      height: 39px;
+      line-height: 39px;
+      text-align: center;
+      margin: 2px 0 0 0;
+      font-weight: bold;
+    }
+    .videodata:hover {
+      cursor: pointer;
+      color: #fff;
+      background: #409eff;
+    }
+    .indexClass {
+      border-radius: 10px;
+      height: 39px;
+      line-height: 39px;
+      text-align: center;
+      margin: 2px 0 0 0;
+      font-weight: bold;
+      color: #fff;
+      background: #409eff;
+    }
+  }
+}
+</style>

+ 80 - 0
src/views/admin/live/achieve.vue

@@ -0,0 +1,80 @@
+<template>
+  <div id="achieve">
+    <admin-frame>
+      <template #menu>
+        <admin-meun :list="menus"></admin-meun>
+      </template>
+      <component :is="component"></component>
+    </admin-frame>
+  </div>
+</template>
+
+<script>
+import adminFrame from '../model/frame.vue';
+import adminMeun from '../model/menu.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'achieve',
+  props: {},
+  components: {
+    adminFrame,
+    adminMeun,
+    aBase: () => import('./achieve/base.vue'),
+    aPw: () => import('./achieve/pw.vue'),
+    aApply: () => import('./achieve/apply.vue'),
+    aVip: () => import('./achieve/vip.vue'),
+    aTransaction: () => import('./achieve/trans.vue'),
+    aInterview: () => import('./achieve/interview.vue'),
+    aRoadShow: () => import('./achieve/road_show.vue'),
+    aStatistics: () => import('./achieve/statistics.vue'),
+    aActive: () => import('./achieve/active.vue'),
+  },
+  data: function() {
+    return {
+      menus: [
+        { num: '1', title: '展会管理', icon: 'el-icon-s-tools', component: 'aBase' },
+        { num: '2', title: '图文管理', icon: 'el-icon-pie-chart', component: 'aPw' },
+        { num: '3', title: '申请管理', icon: 'el-icon-pie-chart', component: 'aApply' },
+        { num: '4', title: 'vip用户', icon: 'el-icon-user-solid', component: 'aVip' },
+        { num: '5', title: '交易备案', icon: 'el-icon-notebook-1', component: 'aTransaction' },
+        { num: '6', title: '嘉宾访谈', icon: 'el-icon-user-solid', component: 'aInterview' },
+        { num: '7', title: '项目路演', icon: 'el-icon-video-camera-solid', component: 'aRoadShow' },
+        { num: '8', title: '统计报表', icon: 'el-icon-s-operation', component: 'aStatistics' },
+        { num: '9', title: '数据动态', icon: 'el-icon-pie-chart', component: 'aActive' },
+        {
+          num: '10',
+          title: '退出登陆',
+          icon: 'el-icon-circle-close',
+          component: () => this.$router.push({ path: '/channel/index', query: { id: this.user.id } }),
+        },
+      ],
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    num() {
+      return this.$route.query.num || '1';
+    },
+    component() {
+      let component = 'aBase';
+      const res = this.menus.find(f => f.num === this.num);
+      if (res && _.isFunction(res)) {
+        res();
+      } else if (res) {
+        component = _.get(res, 'component', 'aBase');
+      }
+      return component;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 30 - 0
src/views/admin/live/achieve/active.vue

@@ -0,0 +1,30 @@
+<template>
+  <div id="aActive">
+    <p>aActive</p>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'aActive',
+  props: {},
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 148 - 0
src/views/admin/live/achieve/apply.vue

@@ -0,0 +1,148 @@
+<template>
+  <div id="aApply">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="24" class="leftTop"> <span>|</span> <span>申请管理</span> </el-col>
+        <el-col :span="24" class="info">
+          <span v-if="display == 'list'">
+            <data-table :fields="fields" :opera="opera" :data="list" :total="total" @check="toCheck"></data-table>
+          </span>
+          <span v-else>
+            <el-col :span="24">
+              <!-- <el-table :data="goodsList" border style="width: 100%">
+                <el-table-column prop="name" label="产品名称" align="center" show-overflow-tooltip> </el-table-column>
+                <el-table-column label="类型" align="center">
+                  <template slot-scope="scope">
+                    <span>{{ scope.row.type == '0' ? '科技需求' : scope.row.type == '1' ? '技术成果' : '商务服务' }}</span>
+                  </template>
+                </el-table-column>
+                <el-table-column prop="contacts" label="联系人" align="center" show-overflow-tooltip> </el-table-column>
+                <el-table-column prop="phone" label="电话" align="center"> </el-table-column>
+                <el-table-column prop="field" label="所属领域" align="center" show-overflow-tooltip> </el-table-column>
+                <el-table-column prop="cooperation" label="合作方式" align="center"> </el-table-column>
+                <el-table-column label="用户状态" align="center">
+                  <template slot-scope="scope">
+                    <span>{{ scope.row.dockStatus == '0' ? '待审核' : scope.row.dockStatus == '1' ? '通过' : '拒绝' }}</span>
+                  </template>
+                </el-table-column>
+                <el-table-column fixed="right" label="操作" align="center">
+                  <template slot-scope="scope">
+                    <el-button @click="toCheck(scope.row)" type="text" size="small">审核</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+              <el-col :span="24" class="page">
+                <el-pagination
+                  @current-change="handleCurrentChange1"
+                  :current-page="currentPage1"
+                  layout="total, prev, pager, next, jumper"
+                  :total="goodsTotal"
+                  :page-size="pageSize"
+                >
+                </el-pagination>
+              </el-col> -->
+              <goodsList :list="gList" :total="gTotal" @search="getGoodsList" @check="toGoodsCheck" @back="display = 'list'"></goodsList>
+            </el-col>
+          </span>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import goodsList from './apply/goodsList.vue';
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dockUser } = createNamespacedHelpers('dockUser');
+export default {
+  name: 'aApply',
+  props: {},
+  components: { dataTable, goodsList },
+  data: function() {
+    return {
+      display: 'list',
+      opera: [
+        {
+          label: '审核',
+          method: 'check',
+        },
+      ],
+      fields: [
+        { label: '姓名', prop: 'user_name' },
+        { label: '电话', prop: 'contact_tel' },
+        { label: '申请时间', prop: 'apply_time' },
+        { label: '用户状态', prop: 'status', format: i => (i == '1' ? '已通过' : i == '2' ? '已拒绝' : '未审核') },
+      ],
+      list: [],
+      total: 0,
+      gList: [],
+      gTotal: 0,
+      gListIndex: 0,
+      gUserid: '',
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...dockUser(['query', 'update', 'delete', 'goodsCheck']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    toCheck({ index }) {
+      this.display = 'goodsList';
+      this.$set(this, `gListIndex`, index);
+      const list = _.slice(_.get(this.list[this.gListIndex], 'goodsList', []), 0, 1);
+      this.$set(this, `gList`, list);
+      this.$set(this, `gTotal`, _.get(this.list[this.gListIndex], 'goodsList', []).length);
+      this.$set(this, `gUserid`, _.get(this.list[this.gListIndex], 'id'));
+    },
+    getGoodsList({ skip, limit }) {
+      const list = _.slice(_.get(this.list[this.gListIndex], 'goodsList', []), skip, skip + limit);
+      this.$set(this, `gList`, list);
+    },
+    async toGoodsCheck(data) {
+      const res = await this.goodsCheck({ ...data, id: this.gUserid });
+      if (this.$checkRes(res, '审核完成', res.errmsg || '审核失败')) {
+        this.search();
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  padding: 0 40px 0 10px;
+}
+.page {
+  text-align: center;
+  padding: 10px 0;
+}
+</style>

+ 97 - 0
src/views/admin/live/achieve/apply/goods.vue

@@ -0,0 +1,97 @@
+<template>
+  <div id="goods">
+    <data-form :fields="fields" :data="data" submitText="审核" @save="toCheck">
+      <template #radios="{item}">
+        <template v-if="item.model === 'status'">
+          <el-radio label="1"><font style="color:green">审核通过</font></el-radio>
+          <el-radio label="2"><font style="color:red">审核拒绝</font></el-radio>
+        </template>
+      </template>
+      <template #custom="{item, form}">
+        <template v-if="item.model === 'type'">
+          {{ form[item.model] === '1' ? '技术成果' : form[item.model] === '2' ? '商务服务' : '科技需求' }}
+        </template>
+
+        <template v-if="item.model === 'image'">
+          <el-row :gutter="20">
+            <el-col :span="6" v-for="(i, index) in form[item.model]" :key="`image${index}`">
+              <el-image :src="i.url" :preview-src-list="getScreenList(i.url)"></el-image>
+            </el-col>
+          </el-row>
+        </template>
+      </template>
+    </data-form>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import dataForm from '@common/src/components/frame/form.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'goods',
+  props: {
+    data: { type: Object, default: () => {} },
+  },
+  model: {
+    prop: 'data',
+    event: 'change',
+  },
+  components: { dataForm },
+  data: function() {
+    return {
+      fields: [
+        { label: '产品类型', model: 'type', custom: true },
+        { label: '名称', model: 'name', type: 'text' },
+        { label: '联系人', model: 'contacts', type: 'text' },
+        { label: '联系电话', model: 'phone', type: 'text' },
+        { label: 'QQ/微信', model: 'qqwx', type: 'text' },
+        { label: '电子邮箱', model: 'email', type: 'text' },
+        { label: '所属领域', model: 'field', type: 'text' },
+        { label: '合作方式', model: 'cooperation', type: 'text' },
+        { label: '企业名称', model: 'company', type: 'text' },
+        { label: '合作条件及要求', model: 'condition', type: 'text' },
+        { label: '产品图片', model: 'image', custom: true },
+        { label: '成果状态', model: 'achievestatus', type: 'text' },
+        { label: '成果权属', model: 'achieveown', type: 'text' },
+        { label: '成果来源', model: 'achievesource', type: 'text' },
+        { label: '意向价格', model: 'intentionprice', type: 'text' },
+        { label: '成果简介', model: 'achievebrief', type: 'text' },
+        { label: '技术特点', model: 'features', type: 'text' },
+        { label: '技术团队', model: 'team', type: 'text' },
+        { label: '商业预期', model: 'expect', type: 'text' },
+        { label: '审核信息状态', model: 'status', type: 'radio', required: true, options: { style: { zoom: 1.2 } } },
+      ],
+    };
+  },
+  created() {},
+  methods: {
+    getScreenList(pic) {
+      let list = _.get(this.data, 'image', []);
+      list = list.filter(f => f.url !== pic).map(i => i.url);
+      list.unshift(pic);
+      return list;
+    },
+    toCheck({ data }) {
+      if (_.get(data, 'status') != '1' && _.get(data, 'status') != '2') {
+        this.$message.warning('请选择审核状态!');
+        return;
+      }
+      const { id, status } = data;
+      this.$emit('check', { good_id: id, status });
+      this.$emit('back');
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 79 - 0
src/views/admin/live/achieve/apply/goodsList.vue

@@ -0,0 +1,79 @@
+<template>
+  <div id="goodsList">
+    <template v-if="display === 'list'">
+      <el-col :span="24" style="text-align:right;padding-bottom:10px">
+        <el-button type="primary" size="mini" @click="toBack">返回</el-button>
+      </el-col>
+      <data-table :fields="fields" :opera="opera" :data="list" :total="total" :operaWidth="100" @query="search" @check="toCheck"></data-table>
+    </template>
+    <template v-else>
+      <el-col :span="24" style="text-align:right;padding-bottom:10px">
+        <el-button type="primary" size="mini" @click="display = 'list'">返回</el-button>
+      </el-col>
+      <goods :data="info" v-on="$listeners" @back="toStart"></goods>
+    </template>
+  </div>
+</template>
+
+<script>
+import goods from './goods.vue';
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'goodsList',
+  props: {
+    list: { type: Array, default: () => [] },
+    total: { type: Number, default: 0 },
+  },
+  components: { dataTable, goods },
+  data: function() {
+    return {
+      display: 'list',
+      opera: [
+        {
+          label: '审核',
+          method: 'check',
+        },
+      ],
+      fields: [
+        { label: '产品名称', prop: 'name' },
+        { label: '类型', prop: 'type', format: i => (i == '1' ? '技术成果' : i == '2' ? '商务服务' : '科技需求') },
+        { label: '联系人', prop: 'contacts' },
+        { label: '联系电话', prop: 'phone' },
+        { label: '所属领域', prop: 'field' },
+        { label: '合作方式', prop: 'cooperation' },
+        { label: '审核状态', prop: 'status', format: i => (i == '1' ? '已通过' : i == '2' ? '已拒绝' : '未审核') },
+      ],
+      info: {},
+    };
+  },
+  created() {},
+  methods: {
+    search({ skip = 0, limit = 10, ...info } = {}) {
+      this.$emit('search', { skip, limit, ...info });
+    },
+    toCheck({ data }) {
+      this.$set(this, `info`, data);
+      this.display = 'detail';
+    },
+    toBack() {
+      this.$emit('back');
+    },
+    toStart() {
+      this.display = 'list';
+      this.toBack();
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 280 - 0
src/views/admin/live/achieve/base.vue

@@ -0,0 +1,280 @@
+<template>
+  <div id="aBase">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="24" class="leftTop"> <span>|</span> <span>展会管理</span> </el-col>
+        <el-col :span="24" class="info">
+          <el-row>
+            <el-col :span="24">
+              <el-form :model="form" ref="form" label-width="100px" class="demo-ruleForm">
+                <el-form-item label="对接会标题">
+                  <el-input v-model="form.title"></el-input>
+                </el-form-item>
+                <el-form-item label="开始时间">
+                  <el-date-picker
+                    v-model="form.start_time"
+                    type="datetime"
+                    placeholder="选择日期时间"
+                    format="yyyy-MM-dd HH:mm:ss"
+                    value-format="yyyy-MM-dd HH:mm:ss"
+                  >
+                  </el-date-picker>
+                </el-form-item>
+                <el-form-item label="结束时间">
+                  <el-date-picker
+                    v-model="form.end_time"
+                    type="datetime"
+                    placeholder="请选择结束时间"
+                    format="yyyy-MM-dd HH:mm:ss"
+                    value-format="yyyy-MM-dd HH:mm:ss"
+                  >
+                  </el-date-picker>
+                </el-form-item>
+                <el-form-item label="报名截止时间">
+                  <el-date-picker
+                    v-model="form.join_end"
+                    type="datetime"
+                    placeholder="请选择报名截止时间"
+                    format="yyyy-MM-dd HH:mm:ss"
+                    value-format="yyyy-MM-dd HH:mm:ss"
+                  >
+                  </el-date-picker>
+                </el-form-item>
+                <el-form-item label="省份">
+                  <el-select v-model="form.province" placeholder="请选择省份" @change="searchOther">
+                    <el-option v-for="item in provinceList" :key="item.code" :label="item.name" :value="item.code"> </el-option>
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="市">
+                  <el-select v-model="form.place" placeholder="请选择市">
+                    <el-option v-for="item in placeList" :key="item.code" :label="item.name" :value="item.code"> </el-option>
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="简介">
+                  <el-input type="textarea" v-model="form.desc"></el-input>
+                </el-form-item>
+                <el-form-item label="负责人">
+                  <el-input v-model="form.adminuser" placeholder="请输入用户名"></el-input>
+                </el-form-item>
+                <el-form-item label="负责人手机号">
+                  <el-input v-model="form.phone" maxlength="11" placeholder="请输入手机号" disabled> </el-input>
+                </el-form-item>
+                <el-form-item label="主办方">
+                  <el-input v-model="form.sponsor" placeholder="请输入主办方"></el-input>
+                </el-form-item>
+                <el-form-item label="承办方">
+                  <el-input v-model="form.organizer" placeholder="请输入承办方"></el-input>
+                </el-form-item>
+                <el-form-item label="对接会视频">
+                  <el-button type="primary" size="mini" @click="addDialog = true">添加视频</el-button>
+                  <el-button type="primary" size="mini" @click="videDialog = true">查看视频</el-button>
+                </el-form-item>
+                <el-form-item label="状态">
+                  <el-radio v-model="form.status" label="1">开始</el-radio>
+                  <el-radio v-model="form.status" label="2">结束</el-radio>
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" @click="submitForm('form')">保存</el-button>
+                  <el-button type="primary" @click="submitStatus('form')" v-if="this.form.status == '1' || this.form.status == '2'"
+                    >展会开启&结束提交</el-button
+                  >
+                </el-form-item>
+              </el-form>
+            </el-col>
+          </el-row>
+        </el-col>
+      </el-col>
+    </el-row>
+
+    <el-dialog title="添加视频信息" :visible.sync="addDialog" width="40%" :before-close="videoclose">
+      <el-form ref="videoform" :model="videoform" label-width="80px" class="videoform">
+        <el-form-item label="名称">
+          <el-input v-model="videoform.videointro" placeholder="请输入名称"></el-input>
+        </el-form-item>
+        <el-form-item label="简介">
+          <el-input v-model="videoform.videointroinfo" type="textarea" placeholder="请输入简介"></el-input>
+        </el-form-item>
+        <el-form-item label="视频路径">
+          <upload
+            :limit="1"
+            :data="videoform.file_path"
+            type="file_path"
+            listType=""
+            :url="'/files/imgpath/upload'"
+            @upload="uploadSuccess"
+            @delete="uploadDelete"
+          ></upload>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="addvideoSubmit">保存</el-button>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+
+    <el-dialog title="查看视频信息" :visible.sync="videDialog" width="40%" height="400px" :before-close="videoclose">
+      <el-table :data="form.videodata" style="width: 100%" border class="table">
+        <el-table-column type="index" width="50" label="序号" align="center"> </el-table-column>
+        <el-table-column prop="videointro" label="名称" align="center" show-overflow-tooltip> </el-table-column>
+        <el-table-column label="操作" align="center">
+          <template v-slot="scoped">
+            <el-button type="text" @click="delAttend(scoped.$index, scoped.row)" size="small">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import upload from '@common/src/components/frame/upload.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dock } = createNamespacedHelpers('dock');
+const { mapActions: place } = createNamespacedHelpers('place');
+export default {
+  name: 'aBase',
+  props: {},
+  components: { upload },
+  data: function() {
+    return {
+      form: {},
+      provinceList: [],
+      placeList: [],
+      addDialog: false,
+      videoform: {},
+      videDialog: false,
+    };
+  },
+  created() {
+    this.search();
+    this.searchOther();
+  },
+  methods: {
+    ...place({ placeQuery: 'query' }),
+    ...dock(['fetch', 'update']),
+    async search() {
+      const res = await this.fetch(this.user._id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+        if (this.form.place) {
+          this.searchOther(this.form.province);
+        }
+      }
+    },
+
+    async submitForm() {
+      const res = await this.update(_.cloneDeep(this.form));
+      if (this.$checkRes(res)) {
+        this.$message({
+          message: '修改信息成功',
+          type: 'success',
+        });
+        this.search();
+      }
+    },
+    async submitStatus() {
+      const res = await this.update(_.cloneDeep(this.form));
+      if (this.$checkRes(res)) {
+        this.$message({
+          message: '对接会审核成功',
+          type: 'success',
+        });
+        this.search();
+      }
+    },
+
+    async addvideoSubmit() {
+      const id = this.form.id;
+      const videodata = _.cloneDeep(this.form.videodata);
+      videodata.push(this.videoform);
+      const res = await this.update({ id, videodata });
+      if (this.$checkRes(res, '视频保存成功')) {
+        this.search();
+      }
+    },
+    async delAttend(index) {
+      const duplicate = _.cloneDeep(this.form.videodata);
+      duplicate.splice(index, 1);
+      const res = await this.update({ id: this.form.id, videodata: duplicate });
+      if (this.$checkRes(res, '视频删除成功', res.errmsg || '视频删除失败')) {
+        this.search();
+      }
+    },
+
+    async searchOther(val) {
+      const query = {};
+      if (val) query.code = val;
+      let res = await this.placeQuery(query);
+      if (this.$checkRes(res)) {
+        if (!val) this.$set(this, `provinceList`, res.data);
+        else this.$set(this, `placeList`, res.data);
+      }
+    },
+    // 取消
+    videoclose() {
+      this.addDialog = false;
+      this.videDialog = false;
+    },
+    uploadSuccess({ type, data }) {
+      this.$set(this.videoform, `${type}`, data.uri);
+      this.$message({
+        message: '上传视频成功',
+        type: 'success',
+      });
+    },
+    uploadDelete(index) {
+      this.$set(this.form, `file_path`, null);
+      this.$message({
+        message: '删除视频成功',
+        type: 'success',
+      });
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  padding: 0 38px 0 10px;
+}
+/deep/.el-dialog__body {
+  height: 350px;
+  overflow: hidden;
+  padding: 20px;
+}
+.table {
+  height: 328px;
+  overflow: hidden;
+}
+/deep/.el-table td {
+  padding: 5px 0;
+}
+/deep/.el-table th {
+  padding: 5px 0;
+}
+.page {
+  text-align: center;
+  padding: 10px 0;
+}
+</style>

+ 183 - 0
src/views/admin/live/achieve/interview.vue

@@ -0,0 +1,183 @@
+<template>
+  <div id="aInterview">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="24" class="leftTop"> <span>|</span> <span>嘉宾访谈</span> </el-col>
+        <el-col :span="24" class="info">
+          <template v-if="view == 'list'">
+            <el-col :span="24" class="add">
+              <el-button type="primary" size="mini" @click="view = 'detail'">添加信息</el-button>
+            </el-col>
+            <el-col :span="24" class="list">
+              <data-table :fields="fields" :opera="opera" :data="list" :total="total" @query="search" @edit="toEdit" @delete="toDelete"></data-table>
+            </el-col>
+          </template>
+          <template v-else>
+            <el-col :span="24">
+              <el-col :span="24" class="back">
+                <el-button type="primary" size="mini" @click="toBack()">返回</el-button>
+              </el-col>
+              <el-col :span="24">
+                <data-form :fields="fields" v-model="form" @save="toSave">
+                  <template #custom="{item, form}">
+                    <template v-if="item.model === 'picture'">
+                      <upload
+                        :limit="1"
+                        :data="form.picture"
+                        type="picture"
+                        :url="'/files/imgpath/upload'"
+                        @upload="uploadSuccess"
+                        @delete="uploadDelete"
+                      ></upload>
+                    </template>
+                    <template v-if="item.model === 'filepath'">
+                      <upload
+                        :limit="1"
+                        :data="form.filepath"
+                        type="filepath"
+                        listType=""
+                        :url="'/files/imgpath/upload'"
+                        @upload="uploadSuccess"
+                        @delete="uploadDelete"
+                      ></upload>
+                    </template>
+                  </template>
+                </data-form>
+              </el-col>
+            </el-col>
+          </template>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import dataForm from '@common/src/components/frame/vform.vue';
+import upload from '@common/src/components/frame/upload.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: interview } = createNamespacedHelpers('interview');
+export default {
+  name: 'aInterview',
+  props: {},
+  components: { dataTable, dataForm, upload },
+  data: function() {
+    return {
+      view: 'list',
+      opera: [
+        {
+          label: '编辑',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          type: 'danger',
+          method: 'delete',
+        },
+      ],
+      fields: [
+        { label: '信息标题', prop: 'title', model: 'title', filter: true },
+        { label: '来源', prop: 'origin', model: 'origin' },
+        { label: '图片', model: 'picture', custom: true, notable: true },
+        { label: '视频', model: 'filepath', custom: true, notable: true },
+        { label: '发布时间', prop: 'publish_time', model: 'publish_time', type: 'date' },
+        { label: '简介', prop: 'brief', model: 'brief', type: 'textarea' },
+      ],
+      list: [],
+      total: 0,
+      form: {},
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...interview(['query', 'create', 'update', 'delete']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    async toSave() {
+      const dup = _.cloneDeep(this.form);
+      let res;
+      if (dup._id) {
+        res = await this.update(dup);
+      } else {
+        dup.dock_id = this.user.id;
+        res = await this.create(dup);
+      }
+      if (this.$checkRes(res, '嘉宾访谈保存成功', res.errmsg || '嘉宾访谈保存失败')) {
+        this.search();
+        this.toBack();
+      }
+    },
+    toEdit({ data }) {
+      this.view = 'detail';
+      this.$set(this, `form`, data);
+    },
+    toDelete({ data }) {
+      this.$confirm('您确定要删除此用户吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(async () => {
+          const res = await this.delete(data._id);
+          if (this.$checkRes(res, '嘉宾访谈删除成功', res.errmsg || '嘉宾访谈删除失败')) {
+            this.search();
+          }
+        })
+        .catch(() => {});
+    },
+    toBack() {
+      this.view = 'list';
+      this.form = {};
+    },
+    uploadSuccess({ type, data }) {
+      this.$set(this.form, `${type}`, data.uri);
+    },
+    uploadDelete({ type }) {
+      this.$set(this.form, type, null);
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  padding: 0 40px 0 10px;
+  .add {
+    text-align: right;
+    padding: 10px 0;
+  }
+}
+.back {
+  text-align: right;
+  padding: 10px 0;
+}
+</style>

+ 177 - 0
src/views/admin/live/achieve/pw.vue

@@ -0,0 +1,177 @@
+<template>
+  <div id="aPw">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="24" class="leftTop"> <span>|</span> <span>图文管理</span> </el-col>
+        <el-col :span="24" class="info">
+          <el-col :span="24" class="top">
+            <el-button type="primary" size="mini" @click="dialog = true">添加信息</el-button>
+          </el-col>
+          <el-col :span="24" class="list">
+            <data-table :fields="fields" :opera="opera" :data="list" :total="total" @edit="toEdit" @delete="toDelete" @query="search"></data-table>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog title="信息管理" :visible.sync="dialog" @closed="handleClose" width="50%" :append-to-body="true">
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="信息内容" prop="content">
+          <el-input v-model="form.content" type="textarea" placeholder="请输入信息内容"></el-input>
+        </el-form-item>
+        <el-form-item label="图片" prop="url">
+          <uploadArray
+            :limit="6"
+            :data="form.url"
+            :uploadBtn="true"
+            type="url"
+            :url="`/files/image/upload`"
+            @upload="uploadSuccess"
+            @delete="uploadDelete"
+          ></uploadArray>
+        </el-form-item>
+        <el-form-item label="视频文件" prop="file_path">
+          <upload
+            :limit="1"
+            :data="form.file_path"
+            type="file_path"
+            listType=""
+            :url="'/files/imgpath/upload'"
+            @upload="uploadSuccess"
+            @delete="uploadDelete"
+          ></upload>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="save">保存</el-button>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import upload from '@common/src/components/frame/upload.vue';
+import uploadArray from '@common/src/components/upload/uploadArray.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dockPw } = createNamespacedHelpers('dockPw');
+export default {
+  name: 'aPw',
+  props: {},
+  components: { dataTable, upload, uploadArray },
+  data: function() {
+    return {
+      opera: [
+        {
+          label: '修改',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          icon: 'el-icon-delete',
+          method: 'delete',
+        },
+      ],
+      fields: [
+        { label: '信息内容', prop: 'content' },
+        { label: '发布时间', prop: 'create_time' },
+      ],
+      list: [],
+      total: 0,
+      dialog: false,
+      form: { url: [] },
+      rules: {},
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...dockPw(['query', 'fetch', 'update', 'delete', 'create']),
+    async search({ skip = 0, limit = 10 } = {}) {
+      const res = await this.query({ dock_id: this.user.id, skip, limit });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 修改
+    toEdit({ data }) {
+      this.$set(this, `form`, data);
+      this.dialog = true;
+    },
+    // 保存
+    async save() {
+      let data = _.cloneDeep(this.form);
+      if (data.id) {
+        let res = await this.update(data);
+        if (this.$checkRes(res, '信息修改成功', res.errmsg || '信息修改失败')) this.handleClose();
+      } else {
+        data.dock_id = this.user.id;
+        let res = await this.create(data);
+        if (this.$checkRes(res, '信息添加成功', res.errmsg || '信息添加失败')) this.handleClose();
+      }
+    },
+    // 删除
+    async toDelete({ data }) {
+      let res = await this.delete(data.id);
+      if (this.$checkRes(res, '信息删除成功', res.errmsg || '信息删除失败')) this.search();
+    },
+    // 取消
+    handleClose() {
+      this.form = { url: [] };
+      this.dialog = false;
+      this.search();
+    },
+    uploadSuccess({ type, data }) {
+      let arr = _.get(this.form, type);
+      if (_.isArray(arr)) {
+        let datas = { name: data.name, url: data.uri };
+        this.form[type].push({ name: data.name, url: data.uri });
+      } else {
+        this.$set(this.form, `${type}`, data.uri);
+      }
+    },
+    uploadDelete(data) {
+      if (_.isObject(data)) this.$set(this.form, data.type, null);
+      else this.form.url.splice(data, 1);
+      this.$message({
+        message: '删除成功',
+        type: 'success',
+      });
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  padding: 0 38px 0 10px;
+  .top {
+    text-align: right;
+    padding: 10px 0;
+  }
+}
+</style>

+ 30 - 0
src/views/admin/live/achieve/road_show.vue

@@ -0,0 +1,30 @@
+<template>
+  <div id="aRoadShow">
+    <p>aRoadShow</p>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'aRoadShow',
+  props: {},
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 30 - 0
src/views/admin/live/achieve/statistics.vue

@@ -0,0 +1,30 @@
+<template>
+  <div id="aStatistics">
+    <p>aStatistics</p>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'aStatistics',
+  props: {},
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 65 - 0
src/views/admin/live/achieve/trans.vue

@@ -0,0 +1,65 @@
+<template>
+  <div id="trans">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="24" class="leftTop"> <span>|</span> <span>交易备案</span> </el-col>
+        <el-col :span="24" class="info">
+          <el-tabs v-model="activeName" type="card">
+            <el-tab-pane label="待确定" name="first">
+              <deal></deal>
+            </el-tab-pane>
+            <el-tab-pane label="审核完成" name="second">
+              <finish></finish>
+            </el-tab-pane>
+          </el-tabs>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'trans',
+  props: {},
+  components: {
+    deal: () => import('./trans/deal.vue'),
+    finish: () => import('./trans/finish.vue'),
+  },
+  data: function() {
+    return {
+      activeName: 'first',
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  padding: 0 40px 0 10px;
+}
+</style>

+ 64 - 0
src/views/admin/live/achieve/trans/deal.vue

@@ -0,0 +1,64 @@
+<template>
+  <div id="deal">
+    <data-table :fields="fields" :opera="opera" :operaWidth="150" :data="list" :total="total" @query="search"></data-table>
+  </div>
+</template>
+
+<script>
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: transaction } = createNamespacedHelpers('transaction');
+export default {
+  name: 'deal',
+  props: {},
+  components: { dataTable },
+  data: function() {
+    return {
+      opera: [],
+      fields: [
+        { label: '商品名称', prop: 'product' },
+        { label: '购买人名称', prop: 'd_name' },
+        { label: '营销人名称', prop: 's_name' },
+        {
+          label: '状态',
+          prop: 'status',
+          format: i => {
+            let word = '正在洽谈';
+            if (i == '1') word = '达成意向';
+            else if (i == '2') word = '待确定';
+            else if (i == '3') word = '交易完成';
+            else if (i == '4') word = '交易失败';
+            return word;
+          },
+        },
+      ],
+      list: [],
+      total: 0,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...transaction(['query']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ skip, limit, dock_id: this.user.id, status: '2' });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 63 - 0
src/views/admin/live/achieve/trans/finish.vue

@@ -0,0 +1,63 @@
+<template>
+  <div id="finish">
+    <data-table :fields="fields" :opera="[]" :data="list" :total="total"></data-table>
+  </div>
+</template>
+
+<script>
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: transaction } = createNamespacedHelpers('transaction');
+export default {
+  name: 'finish',
+  props: {},
+  components: { dataTable },
+  data: function() {
+    return {
+      fields: [
+        { label: '商品名称', prop: 'product' },
+        { label: '购买人名称', prop: 'd_name' },
+        { label: '营销人名称', prop: 's_name' },
+        {
+          label: '状态',
+          prop: 'status',
+          format: i => {
+            let word = '正在洽谈';
+            if (i == '1') word = '达成意向';
+            else if (i == '2') word = '交易备案';
+            else if (i == '3') word = '交易完成';
+            else if (i == '4') word = '交易失败';
+            return word;
+          },
+        },
+      ],
+      list: [],
+      total: 0,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...transaction(['query']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ skip, limit, dock_id: this.user.id, status: '3' });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 146 - 0
src/views/admin/live/achieve/vip.vue

@@ -0,0 +1,146 @@
+<template>
+  <div id="vip">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="24" class="leftTop"> <span>|</span> <span>VIP用户</span> </el-col>
+        <el-col :span="24" class="info">
+          <el-col :span="24" class="add">
+            <el-button type="primary" size="mini" @click="dialog = true">添加用户</el-button>
+          </el-col>
+          <el-col :span="24">
+            <data-table :fields="fields" :opera="opera" :data="list" :usePage="false" @edit="toEdit" @delete="toDelete"></data-table>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog title="vip用户" :visible.sync="dialog" width="30%" center :before-close="toClose">
+      <data-form v-model="form" :fields="fields" @save="toSave"></data-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import dataForm from '@common/src/components/frame/vform.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dock } = createNamespacedHelpers('dock');
+export default {
+  name: 'vip',
+  props: {},
+  components: { dataTable, dataForm },
+  data: function() {
+    return {
+      dialog: false,
+      opera: [
+        {
+          label: '编辑',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          type: 'danger',
+          method: 'delete',
+        },
+      ],
+      fields: [
+        { label: '姓名', prop: 'vipname', model: 'vipname' },
+        { label: '手机号', prop: 'vipphone', model: 'vipphone' },
+        { label: '邮箱', prop: 'email', model: 'email' },
+        { label: '简介', prop: 'content', model: 'content', notable: true, type: 'textarea' },
+      ],
+      list: [],
+      form: {},
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...dock(['fetch', 'update']),
+    async search() {
+      const res = await this.fetch(this.user.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, _.get(res.data, 'vipuser', []));
+      }
+    },
+    async toSave() {
+      const dup = _.cloneDeep(this.form);
+      dup.password = this.user.room_id;
+      const vipuser = _.cloneDeep(this.list);
+      if (dup._id) {
+        const index = vipuser.findIndex(f => f._id == dup._id);
+        vipuser[index] = dup;
+      } else vipuser.push(dup);
+      const res = await this.update({ id: this.user.id, vipuser });
+      if (this.$checkRes(res, 'vip用户保存成功', res.errmsg || 'vip用户保存失败')) {
+        this.search();
+        this.toClose();
+      }
+    },
+    toEdit({ data }) {
+      this.dialog = true;
+      this.$set(this, `form`, data);
+    },
+    toDelete({ data }) {
+      this.$confirm('您确定要删除此用户吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(async () => {
+          console.log(index);
+          let vipuser = _.cloneDeep(this.list);
+          vipuser = vipuser.filter(f => f._id !== data._id);
+          const res = await this.update({ id: this.user.id, vipuser });
+          if (this.$checkRes(res, 'vip用户删除成功', res.errmsg || 'vip用户删除失败')) {
+            this.search();
+          }
+        })
+        .catch(() => {});
+    },
+    toClose() {
+      this.dialog = false;
+      this.form = {};
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  padding: 0 40px 0 10px;
+  .add {
+    text-align: right;
+    padding: 10px 0;
+  }
+}
+.page {
+  text-align: right;
+  padding: 10px 0;
+}
+/deep/.el-dialog__body {
+  min-height: 300px;
+}
+</style>

+ 64 - 0
src/views/admin/live/science.vue

@@ -0,0 +1,64 @@
+<template>
+  <div id="science">
+    <admin-frame>
+      <template #menu>
+        <admin-meun :list="menus" title="个人中心"></admin-meun>
+      </template>
+      <component :is="component"></component>
+    </admin-frame>
+  </div>
+</template>
+
+<script>
+import adminFrame from '../model/frame.vue';
+import adminMeun from '../model/menu.vue';
+import sBase from './science/base.vue';
+import sVideo from './science/video.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'science',
+  props: {},
+  components: {
+    adminFrame,
+    adminMeun,
+    sBase,
+    sVideo,
+  },
+  data: function() {
+    return {
+      menus: [
+        { num: '1', title: '基本信息', icon: 'el-icon-notebook-1', component: 'sBase' },
+        { num: '2', title: '视频管理', icon: 'el-icon-video-camera-solid', component: 'sVideo' },
+        { num: '3', title: '进入频道', icon: 'el-icon-refresh', component: () => this.$router.push({ path: '/channel/index', query: { id: this.user.id } }) },
+      ],
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    num() {
+      return this.$route.query.num || '1';
+    },
+    component() {
+      let component = 'sBase';
+      const res = this.menus.find(f => f.num === this.num);
+      console.log(res);
+      if (res && _.isFunction(res)) {
+        res();
+      } else if (res) {
+        component = _.get(res, 'component', 'sBase');
+      }
+      return component;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 193 - 0
src/views/admin/live/science/base.vue

@@ -0,0 +1,193 @@
+<template>
+  <div id="base">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="24" class="leftTop">
+          <span>|</span>
+          <span>基本信息</span>
+        </el-col>
+        <el-col :span="24" class="info">
+          <el-form ref="form" :model="form" label-width="150px">
+            <el-col :span="24">
+              <el-form-item prop="name">
+                <el-col :span="21" slot="label">
+                  房间号
+                </el-col>
+                <el-col :span="24">
+                  <el-input v-model="form.room_id" placeholder="请输入房间号" disabled></el-input>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item prop="type">
+                <el-col :span="21" slot="label">
+                  类型
+                </el-col>
+                <el-col :span="24">
+                  <el-select v-model="form.type" placeholder="请选择类型" disabled>
+                    <el-option v-for="item in typelist" :key="item.name" :label="item.name" :value="item.id"> </el-option>
+                  </el-select>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item prop=" title">
+                <el-col :span="21" slot="label">
+                  标题
+                </el-col>
+                <el-col :span="24">
+                  <el-input v-model="form.title" placeholder="请输入标题"></el-input>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item prop="orgin">
+                <el-col :span="21" slot="label">
+                  来源
+                </el-col>
+                <el-col :span="24">
+                  <el-input v-model="form.orgin" placeholder="请输入来源"></el-input>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item prop="create_time">
+                <el-col :span="21" slot="label">
+                  时间
+                </el-col>
+                <el-col :span="24">
+                  <el-col :span="24">
+                    <el-date-picker
+                      v-model="form.create_time"
+                      placeholder="请选择"
+                      value-format="yyyy-MM-dd"
+                      format="yyyy-MM-dd"
+                      type="date"
+                      style="width: 100%;"
+                    >
+                    </el-date-picker>
+                  </el-col>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item prop="desc" class="langInfo">
+                <el-col :span="21" slot="label">
+                  简介
+                </el-col>
+                <el-col :span="24">
+                  <el-input v-model="form.desc" type="textarea" placeholder="请输入简介"></el-input>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-button type="primary" @click="onSubmit()">提交修改</el-button>
+            </el-col>
+          </el-form>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: channel } = createNamespacedHelpers('channel');
+const { mapActions: code } = createNamespacedHelpers('code');
+export default {
+  name: 'sBase',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      form: {},
+      typelist: [],
+    };
+  },
+  created() {
+    this.searchOther();
+    this.search();
+  },
+  methods: {
+    ...code({ codeQuery: 'query' }),
+    ...channel(['fetch', 'update']),
+    async search() {
+      const res = await this.fetch(this.user._id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    async searchOther(val) {
+      let res = await this.codeQuery({ category: '04' });
+      if (this.$checkRes(res)) {
+        this.$set(this, `typelist`, res.data);
+      }
+    },
+    async onSubmit() {
+      const res = await this.update(_.cloneDeep(this.form));
+      this.$checkRes(res, '修改成功', res.errmsg || '修改失败');
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  margin: 0 40px 15px 10px;
+  border: 1px dashed #ccc;
+  width: 96%;
+  padding: 10px;
+  .btn {
+    text-align: center;
+    margin: 10px 0;
+  }
+}
+/deep/.el-form-item {
+  margin-bottom: 0px;
+}
+/deep/.el-form-item__label {
+  border: 1px solid #dcdfe6;
+}
+/deep/.el-radio-group {
+  border: 1px solid #ccc;
+  padding: 12px 20px;
+  // width: 943px;
+}
+/deep/.el-input__inner {
+  border-radius: 0;
+  height: 42px;
+  line-height: 42px;
+}
+/deep/.el-textarea__inner {
+  border-radius: 0;
+  height: 140px !important;
+}
+/deep/.langInfo .el-form-item__label {
+  padding: 49px 0;
+}
+/deep/.langInfo .workexperience .textarea__inner {
+  height: 140px !important;
+}
+</style>

+ 216 - 0
src/views/admin/live/science/video.vue

@@ -0,0 +1,216 @@
+<template>
+  <div id="video">
+    <el-row>
+      <el-col :span="24" class="video">
+        <el-col :span="24" class="leftTop">
+          <span>|</span>
+          <span>视频管理</span>
+        </el-col>
+        <el-col :span="24" class="info">
+          <span v-if="display == 'list'">
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini" @click="display = 'detail'">添加信息</el-button>
+            </el-col>
+            <el-col :span="24" class="list">
+              <data-table :fields="fields" :data="list" :total="total" @query="search" :opera="opera" @edit="toEdit" @delete="toDelete"></data-table>
+            </el-col>
+          </span>
+          <span v-else>
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini" @click="back()">返回</el-button>
+            </el-col>
+            <el-col :span="24" class="detail">
+              <data-form :data="form" :fields="formFields" @save="toSave" :rules="rules" submitText="发布">
+                <template #custom="{item, form}">
+                  <template v-if="item.model === 'file_path'">
+                    <upload
+                      :limit="1"
+                      :data="form.file_path"
+                      type="file_path"
+                      listType=""
+                      :url="'/files/filepath/upload'"
+                      @upload="uploadSuccess"
+                      @delete="uploadDelete"
+                    ></upload>
+                  </template>
+                  <template v-else-if="item.model === 'start_time'">
+                    <el-date-picker
+                      v-model="form.start_time"
+                      type="datetime"
+                      placeholder="请选择开始时间"
+                      format="yyyy-MM-dd HH:mm"
+                      value-format="yyyy-MM-dd HH:mm"
+                    >
+                    </el-date-picker>
+                  </template>
+                  <template v-else-if="item.model === 'end_time'">
+                    <el-date-picker
+                      v-model="form.end_time"
+                      type="datetime"
+                      placeholder="请选择开始时间"
+                      format="yyyy-MM-dd HH:mm"
+                      value-format="yyyy-MM-dd HH:mm"
+                    >
+                    </el-date-picker>
+                  </template>
+                </template>
+              </data-form>
+            </el-col>
+          </span>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import upload from '@common/src/components/frame/upload.vue';
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import dataForm from '@common/src/components/frame/form.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: channelVideo } = createNamespacedHelpers('channelVideo');
+export default {
+  name: 'sVideo',
+  props: {},
+  components: { dataTable, dataForm, upload },
+  data: function() {
+    return {
+      display: 'list',
+      opera: [
+        {
+          label: '编辑',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          icon: 'el-icon-delete',
+          method: 'delete',
+        },
+      ],
+      fields: [
+        { label: '标题', prop: 'title', showTip: true },
+        { label: '开始时间', prop: 'start_time' },
+        { label: '结束时间', prop: 'end_time' },
+      ],
+      list: [],
+      total: 0,
+      // 添加信息
+      form: {},
+      rules: {},
+      formFields: [
+        { label: '标题', model: 'title' },
+        { label: '开始时间', model: 'start_time', custom: true },
+        { label: '结束时间', model: 'end_time', custom: true },
+        { label: '视频文件', model: 'file_path', custom: true },
+      ],
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...channelVideo(['query', 'create', 'update', 'delete']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ skip, limit, ...info, channel_id: this.user._id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 修改
+    toEdit({ data }) {
+      this.$set(this, `form`, data);
+      this.display = 'detail';
+    },
+    // 提交
+    async toSave({ data }) {
+      if (data.id) {
+        let res = await this.update(data);
+        if (this.$checkRes(res)) {
+          this.$checkRes(res, '修改成功', '修改失败');
+          this.back();
+        }
+      } else {
+        data.channel_id = this.user.id;
+        let res = await this.create(data);
+        if (this.$checkRes(res)) {
+          this.$checkRes(res, '创建成功', '创建失败');
+          this.back();
+        }
+      }
+    },
+    // 返回
+    back() {
+      this.form = {};
+      this.display = 'list';
+      this.search();
+    },
+    async toDelete({ data }) {
+      this.$confirm('您确定要删除此信息吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(async () => {
+          let res = await this.delete(data.id);
+          if (this.$checkRes(res)) {
+            this.$message({
+              message: '删除信息成功',
+              type: 'success',
+            });
+            this.search();
+          }
+        })
+        .catch(() => {});
+    },
+    uploadSuccess({ type, data }) {
+      this.$set(this.form, `${type}`, data.uri);
+      this.$message({
+        message: '上传视频成功',
+        type: 'success',
+      });
+    },
+    uploadDelete(index) {
+      this.$set(this.form, `file_path`, null);
+      this.$message({
+        message: '删除视频成功',
+        type: 'success',
+      });
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  margin: 0 40px 15px 10px;
+  width: 96%;
+  padding: 10px;
+  .btn {
+    text-align: right;
+    margin: 10px 0;
+  }
+}
+</style>

+ 63 - 0
src/views/admin/live/train.vue

@@ -0,0 +1,63 @@
+<template>
+  <div id="train">
+    <admin-frame>
+      <template #menu>
+        <admin-meun :list="menus" title="个人中心"></admin-meun>
+      </template>
+      <component :is="component"></component>
+    </admin-frame>
+  </div>
+</template>
+
+<script>
+import adminFrame from '../model/frame.vue';
+import adminMeun from '../model/menu.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'train',
+  props: {},
+  components: {
+    adminFrame,
+    adminMeun,
+    tBase: () => import('./train/base.vue'),
+    tVideo: () => import('./train/video.vue'),
+    tUser: () => import('./train/user.vue'),
+  },
+  data: function() {
+    return {
+      menus: [
+        { num: '1', title: '基本信息', icon: 'el-icon-notebook-1', component: 'tBase' },
+        { num: '2', title: '视频管理', icon: 'el-icon-video-camera-solid', component: 'tVideo' },
+        { num: '3', title: '用户管理', icon: 'el-icon-user', component: 'tUser' },
+        { num: '4', title: '进入频道', icon: 'el-icon-refresh', component: () => this.$router.push({ path: '/channel/index', query: { id: this.user.id } }) },
+      ],
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    num() {
+      return this.$route.query.num || '1';
+    },
+    component() {
+      let component = 'tBase';
+      const res = this.menus.find(f => f.num === this.num);
+      if (res && _.isFunction(res)) {
+        res();
+      } else if (res) {
+        component = _.get(res, 'component', 'tBase');
+      }
+      return component;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 236 - 0
src/views/admin/live/train/base.vue

@@ -0,0 +1,236 @@
+<template>
+  <div id="tbase">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="24" class="leftTop">
+          <span>|</span>
+          <span>基本信息</span>
+        </el-col>
+        <el-col :span="24" class="info">
+          <el-form ref="form" :model="form" label-width="150px">
+            <el-col :span="24">
+              <el-form-item prop="name">
+                <el-col :span="21" slot="label">
+                  房间号
+                </el-col>
+                <el-col :span="24">
+                  <el-input v-model="form.room_id" placeholder="请输入房间号" disabled></el-input>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item prop=" title">
+                <el-col :span="21" slot="label">
+                  标题
+                </el-col>
+                <el-col :span="24">
+                  <el-input v-model="form.title" placeholder="请输入标题"></el-input>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item prop="create_time">
+                <el-col :span="21" slot="label">
+                  开始时间
+                </el-col>
+                <el-col :span="24">
+                  <el-col :span="24">
+                    <el-date-picker
+                      v-model="form.create_date"
+                      type="datetime"
+                      placeholder="请选择"
+                      format="yyyy-MM-dd HH:mm:ss"
+                      value-format="yyyy-MM-dd HH:mm:ss"
+                      style="width:100%"
+                    >
+                    </el-date-picker>
+                  </el-col>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item>
+                <el-col :span="21" slot="label">
+                  省份
+                </el-col>
+                <el-col :span="24">
+                  <el-col :span="24">
+                    <el-select v-model="form.province" placeholder="请选择" filterable clearable @change="searchOther" style="width:100%">
+                      <el-option v-for="(i, index) in provinceList" :key="index" :label="i.name" :value="i.code"></el-option>
+                    </el-select>
+                  </el-col>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item prop="create_time">
+                <el-col :span="21" slot="label">
+                  市区
+                </el-col>
+                <el-col :span="24">
+                  <el-col :span="24">
+                    <el-select v-model="form.place" placeholder="请选择" filterable clearable style="width:100%">
+                      <el-option v-for="(i, index) in placeList" :key="index" :label="i.name" :value="i.code"></el-option>
+                    </el-select>
+                  </el-col>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item prop="sponsor">
+                <el-col :span="21" slot="label">
+                  主办方
+                </el-col>
+                <el-col :span="24">
+                  <el-input v-model="form.sponsor" placeholder="请输入主办方"></el-input>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item prop="user">
+                <el-col :span="21" slot="label">
+                  负责人
+                </el-col>
+                <el-col :span="24">
+                  <el-input v-model="form.user" placeholder="请输入负责人"></el-input>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item prop="phone">
+                <el-col :span="21" slot="label">
+                  联系电话
+                </el-col>
+                <el-col :span="24">
+                  <el-input v-model="form.phone" placeholder="请输入联系电话"></el-input>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item prop="brief" class="langInfo">
+                <el-col :span="21" slot="label">
+                  简介
+                </el-col>
+                <el-col :span="24">
+                  <el-input v-model="form.brief" type="textarea" placeholder="请输入简介"></el-input>
+                </el-col>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-button type="primary" @click="onSubmit()">提交修改</el-button>
+            </el-col>
+          </el-form>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainlive } = createNamespacedHelpers('trainLive');
+const { mapActions: place } = createNamespacedHelpers('place');
+export default {
+  name: 'tbase',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      form: {},
+      // 省市
+      provinceList: [],
+      placeList: [],
+    };
+  },
+  created() {
+    this.search();
+    this.searchOther();
+  },
+  methods: {
+    ...place({ placeQuery: 'query' }),
+    ...trainlive(['fetch', 'update']),
+    async search() {
+      const res = await this.fetch(this.user._id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+        if (this.form.place) {
+          this.searchOther(this.form.place);
+        }
+      }
+    },
+    async searchOther(val) {
+      const query = {};
+      if (val) query.code = val;
+      let res = await this.placeQuery(query);
+      if (this.$checkRes(res)) {
+        if (!val) this.$set(this, `provinceList`, res.data);
+        else this.$set(this, `placeList`, res.data);
+      }
+    },
+    async onSubmit() {
+      const res = await this.update(_.cloneDeep(this.form));
+      this.$checkRes(res, '修改成功', res.errmsg || '修改失败');
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  margin: 0 40px 15px 10px;
+  border: 1px dashed #ccc;
+  width: 96%;
+  padding: 10px;
+  .btn {
+    text-align: center;
+    margin: 10px 0;
+  }
+}
+/deep/.el-form-item {
+  margin-bottom: 0px;
+}
+/deep/.el-form-item__label {
+  border: 1px solid #dcdfe6;
+}
+/deep/.el-radio-group {
+  border: 1px solid #ccc;
+  padding: 12px 20px;
+  // width: 943px;
+}
+/deep/.el-input__inner {
+  border-radius: 0;
+  height: 42px;
+  line-height: 42px;
+}
+/deep/.el-textarea__inner {
+  border-radius: 0;
+  height: 140px !important;
+}
+/deep/.langInfo .el-form-item__label {
+  padding: 49px 0;
+}
+/deep/.langInfo .workexperience .textarea__inner {
+  height: 140px !important;
+}
+</style>

+ 176 - 0
src/views/admin/live/train/user.vue

@@ -0,0 +1,176 @@
+<template>
+  <div id="tUser">
+    <el-row>
+      <el-col :span="24" class="video">
+        <el-col :span="24" class="leftTop">
+          <span>|</span>
+          <span>用户管理</span>
+        </el-col>
+        <el-col :span="24" class="info">
+          <span v-if="display == 'list'">
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini" @click="add">添加</el-button>
+            </el-col>
+            <el-col :span="24" class="list">
+              <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" @delete="toDelete" :total="total"></data-table>
+            </el-col>
+          </span>
+          <span v-else>
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini" @click="back">返回</el-button>
+            </el-col>
+            <el-col :span="24" class="detail">
+              <data-form :data="form" :fields="fields" @save="submitBtn" :rules="rules" submitText="发布"> </data-form>
+            </el-col>
+          </span>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import dataForm from '@common/src/components/frame/form.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainlive } = createNamespacedHelpers('trainLive');
+export default {
+  name: 'tUser',
+  props: {},
+  components: { dataTable, dataForm },
+  data: function() {
+    return {
+      display: 'list',
+      opera: [
+        {
+          label: '编辑',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          icon: 'el-icon-delete',
+          method: 'delete',
+        },
+      ],
+      fields: [
+        { label: '用户名', prop: 'user_title', model: 'user_title' },
+        { label: '账号', prop: 'user_phone', model: 'user_phone' },
+        { label: '密码', prop: 'user_password', model: 'user_password', type: 'password' },
+      ],
+      list: [],
+      total: 0,
+      origin: [],
+      form: {},
+      rules: {},
+      formFields: [
+        { label: '视频标题', model: 'video_title' },
+        { label: '视频时间', model: 'video_date', type: 'date' },
+        { label: '视频', model: 'video_url', custom: true },
+      ],
+      is_new: true,
+    };
+  },
+  created() {
+    this.search({ reseacher: true });
+  },
+  methods: {
+    ...trainlive(['userQuery', 'userCreate', 'userUpdate', 'userDelete']),
+    async search({ skip = 0, limit = 10, research = false } = {}) {
+      if (!research) this.getList({ skip, limit });
+      const res = await this.userQuery(this.user._id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `origin`, res.data);
+        this.$set(this, `total`, res.data.length);
+        this.getList({ skip, limit });
+      }
+    },
+    getList({ skip, limit }) {
+      const list = _.slice(this.origin, skip, skip + limit);
+      this.$set(this, `list`, list);
+    },
+    // 添加
+    add() {
+      this.display = 'detail';
+    },
+    // 修改
+    toEdit({ data }) {
+      this.$set(this, `form`, data);
+      this.display = 'detail';
+      this.is_new = false;
+    },
+    async toDelete({ data, index }) {
+      this.$confirm('您确定要删除此信息吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(async () => {
+          const users = [data._id];
+          let res = await this.userDelete({ id: this.user._id, users });
+          if (this.$checkRes(res)) {
+            this.$message({
+              message: '删除信息成功',
+              type: 'success',
+            });
+            this.search();
+          }
+        })
+        .catch(() => {});
+    },
+    // 提交
+    async submitBtn({ data }) {
+      const users = [data];
+      let res;
+      if (this.is_new) res = await this.userCreate({ id: this.user._id, users });
+      else res = await this.userUpdate({ id: this.user._id, users });
+      if (this.$checkRes(res)) {
+        this.$message({
+          message: '用户保存成功',
+          type: 'success',
+        });
+        this.back();
+      }
+    },
+    // 返回
+    back() {
+      this.form = {};
+      this.display = 'list';
+      this.is_new = true;
+      this.search({ reseacher: true });
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  padding: 0 25px 0 10px;
+  .btn {
+    text-align: right;
+    margin: 0 0 10px 0;
+  }
+}
+</style>

+ 199 - 0
src/views/admin/live/train/video.vue

@@ -0,0 +1,199 @@
+<template>
+  <div id="tVideo">
+    <el-row>
+      <el-col :span="24" class="video">
+        <el-col :span="24" class="leftTop">
+          <span>|</span>
+          <span>视频管理</span>
+        </el-col>
+        <el-col :span="24" class="info">
+          <span v-if="display == 'list'">
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini" @click="add">添加</el-button>
+            </el-col>
+            <el-col :span="24" class="list">
+              <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" @delete="toDelete" :usePage="false"></data-table>
+            </el-col>
+          </span>
+          <span v-else>
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini" @click="back">返回</el-button>
+            </el-col>
+            <el-col :span="24" class="detail">
+              <data-form :data="form" :fields="formFields" @save="submitBtn" :rules="rules" submitText="发布">
+                <template #custom="{item, form}">
+                  <template v-if="item.model === 'video_url'">
+                    <upload
+                      :limit="1"
+                      :data="form.video_url"
+                      type="video_url"
+                      listType=""
+                      :url="'/files/videourl/upload'"
+                      @upload="uploadSuccess"
+                      @delete="uploadDelete"
+                    ></upload>
+                  </template>
+                </template>
+              </data-form>
+            </el-col>
+          </span>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import dataForm from '@common/src/components/frame/form.vue';
+import upload from '@common/src/components/frame/upload.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainlive } = createNamespacedHelpers('trainLive');
+export default {
+  name: 'tVideo',
+  props: {},
+  components: { dataTable, dataForm, upload },
+  data: function() {
+    return {
+      display: 'list',
+      opera: [
+        {
+          label: '编辑',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          icon: 'el-icon-delete',
+          method: 'delete',
+        },
+      ],
+      fields: [
+        { label: '视频标题', prop: 'video_title' },
+        { label: '视频时间', prop: 'video_date' },
+        { label: '视频路径', prop: 'video_url' },
+      ],
+      list: [],
+      form: {},
+      rules: {},
+      formFields: [
+        { label: '视频标题', model: 'video_title' },
+        { label: '视频时间', model: 'video_date', type: 'date' },
+        { label: '视频', model: 'video_url', custom: true },
+      ],
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...trainlive(['fetch', 'update']),
+    async search() {
+      const res = await this.fetch(this.user._id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data.video_data || []);
+      }
+    },
+    // 添加
+    add() {
+      this.display = 'detail';
+    },
+    // 修改
+    toEdit({ data }) {
+      this.$set(this, `form`, data);
+      this.display = 'detail';
+    },
+
+    async toDelete({ data, index }) {
+      this.$confirm('您确定要删除此信息吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(async () => {
+          const dup = _.cloneDeep(this.list);
+          dup.splice(index, 1);
+          let res = await this.update({ id: this.user._id, video_data: dup });
+          if (this.$checkRes(res)) {
+            this.$message({
+              message: '删除信息成功',
+              type: 'success',
+            });
+            this.search();
+          }
+        })
+        .catch(() => {});
+    },
+    // 提交
+    async submitBtn({ data }) {
+      const dup = _.cloneDeep(this.list);
+      if (data._id) {
+        const index = dup.findIndex(f => f._id === data._id);
+        dup[index] = data;
+      } else {
+        dup.push(data);
+      }
+      let res = await this.update({ id: this.user._id, video_data: dup });
+      if (this.$checkRes(res)) {
+        this.$message({
+          message: '视频保存成功',
+          type: 'success',
+        });
+        this.back();
+      }
+    },
+    // 返回
+    back() {
+      this.form = {};
+      this.display = 'list';
+      this.search();
+    },
+
+    uploadSuccess({ type, data }) {
+      this.$set(this.form, `${type}`, data.uri);
+      this.$message({
+        message: '上传视频成功',
+        type: 'success',
+      });
+    },
+    uploadDelete(index) {
+      this.$set(this.form, `video_url`, null);
+      this.$message({
+        message: '删除视频成功',
+        type: 'success',
+      });
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.leftTop {
+  font-size: 18px;
+  width: 96%;
+  height: 41px;
+  line-height: 35px;
+  border-bottom: 1px solid #e5e5e5;
+  position: relative;
+  bottom: 1px;
+  margin: 10px;
+  font-weight: 600;
+  color: #22529a;
+}
+.info {
+  padding: 0 25px 0 10px;
+  .btn {
+    text-align: right;
+    margin: 0 0 10px 0;
+  }
+}
+</style>

+ 56 - 0
src/views/admin/model/frame.vue

@@ -0,0 +1,56 @@
+<template>
+  <div id="frame">
+    <el-row>
+      <el-col :span="24" class="main">
+        <div class="w_1200">
+          <el-col :span="5" class="left">
+            <slot name="menu"></slot>
+          </el-col>
+          <el-col :span="19" class="right">
+            <slot></slot>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'adminFrame',
+  props: {},
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 10px 0;
+  background: #e9edf6;
+  .left {
+    min-height: 537px;
+    margin: 0 15px 0 0;
+    background: #fff;
+  }
+  .right {
+    width: 935px;
+    min-height: 537px;
+    background: #fff;
+  }
+}
+</style>

+ 90 - 0
src/views/admin/model/menu.vue

@@ -0,0 +1,90 @@
+<template>
+  <div id="menu">
+    <el-row>
+      <el-col :span="24" class="menuInfo">
+        <el-col :span="24" class="top">
+          <el-image :src="topUrl"></el-image>
+          <span>{{ title }}</span>
+        </el-col>
+        <el-col :span="24" class="menu">
+          <el-menu :default-active="num" @select="selectMenu" text-color="#999" active-text-color="#044b79">
+            <el-menu-item :index="i.num" v-for="(i, index) in list" :key="index">
+              <template slot="title">
+                <i :class="i.icon"></i>
+                <span>{{ i.title }}</span>
+              </template>
+            </el-menu-item>
+          </el-menu>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'menus',
+  props: {
+    list: { type: Array, default: () => [] }, //[{num,icon,title}]
+    title: { type: String, default: '管理中心' },
+  },
+  components: {},
+  data: function() {
+    return {
+      topUrl: require('@p/square_big.png'),
+    };
+  },
+  created() {},
+  methods: {
+    selectMenu(key) {
+      this.$router.push({ path: this.$route.path, query: { num: key } });
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    num() {
+      return this.$route.query.num || '1';
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.menuInfo {
+  .menu {
+    padding-bottom: 20px;
+  }
+  .top {
+    height: 50px;
+    overflow: hidden;
+    text-align: center;
+    border-bottom: 1px solid #2d64b3;
+    .el-image {
+      width: 40px;
+      top: 8px;
+    }
+    span {
+      padding: 0 15px;
+      font-size: 20px;
+      font-weight: bold;
+      position: relative;
+      top: -3px;
+      color: #999;
+    }
+  }
+}
+/deep/.el-menu-item {
+  font-weight: bold;
+  font-size: 20px;
+  text-align: center;
+  border-bottom: 1px solid #2d64b3;
+  margin: 0 20px;
+}
+</style>

+ 247 - 0
src/views/channelLive/index.vue

@@ -0,0 +1,247 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <div class="w_1200">
+          <el-col :span="24" class="top">
+            科技频道
+          </el-col>
+          <el-col :span="24" class="info">
+            <el-col :span="24" class="one">
+              <el-button type="primary" size="mini" @click="$router.push({ path: '/live/index' })">返回</el-button>
+            </el-col>
+            <el-col :span="24" class="two">
+              <el-col :span="24" class="title">
+                {{ info.title }}
+              </el-col>
+              <el-col :span="24" class="twoVideo">
+                <el-col :span="18" class="left">
+                  <video :src="video_url" controls="" controlsList="nodownload">
+                    您的浏览器不支持 video 标签。
+                  </video>
+                </el-col>
+                <el-col :span="6" class="right">
+                  <h1>视频列表</h1>
+                  <el-col :span="24" class="list">
+                    <el-col :span="24" class="videoList" v-for="(item, index) in videoList" :key="index" @click.native="show(item, index)">
+                      <el-col :span="12" class="file">
+                        <video :src="item.file_path" controlsList="nodownload">
+                          您的浏览器不支持 video 标签。
+                        </video>
+                      </el-col>
+                      <el-col :span="12" class="videoInfo">
+                        <p :style="`color:${menuIndex == index ? menuColor : ''}`">{{ item.title }}</p>
+                        <p class="textOver">{{ item.start_time }}</p>
+                        <p class="textOver">{{ item.end_time }}</p>
+                      </el-col>
+                    </el-col>
+                  </el-col>
+                </el-col>
+              </el-col>
+            </el-col>
+            <el-col :span="24" class="thr">
+              <el-tabs type="border-card">
+                <el-tab-pane label="视频简介">
+                  <p class="textOver"><span>来源:</span>{{ info.origin || '暂无' }}</p>
+                  <p class="textOver"><span>更新时间:</span>{{ info.create_time || '暂无' }}</p>
+                  <p class="textOver"><span>视频简介:</span>{{ info.desc || '暂无' }}</p>
+                </el-tab-pane>
+              </el-tabs>
+            </el-col>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: channel } = createNamespacedHelpers('channel');
+const { mapActions: channelVideo } = createNamespacedHelpers('channelVideo');
+
+var moment = require('moment');
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'index',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      info: {},
+      // 视频
+      video_url: '',
+      videoList: [],
+      menuIndex: '',
+      menuColor: 'rgb(64,158,255)',
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...channel(['fetch']),
+    ...channelVideo(['query']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `info`, res.data);
+      }
+      res = await this.query({ channel_id: this.id });
+      if (this.$checkRes(res)) {
+        // this.$set(this, `videoList`, _.orderBy(res.data, ['start_time'], ['asc']));
+        this.$set(this, `videoList`, res.data);
+      }
+    },
+    show(data, index) {
+      if (data) {
+        this.menuIndex = index;
+        this.$set(this, `video_url`, data.file_path);
+      }
+    },
+    searchvideo() {
+      let data = this.videoList;
+      let adate = moment().format('YYYY-MM-DD HH:mm');
+      let arr = data.find(i => i.start_time <= adate && i.end_time >= adate);
+      let index = data.findIndex(i => i.start_time <= adate && i.end_time >= adate);
+      if (arr && index) {
+        this.show(arr, index);
+      } else {
+        this.show(data[0], '0');
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  watch: {
+    videoList: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        if (val) {
+          this.searchvideo();
+        }
+      },
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  background: url('~@common/src/assets/kjpd.png');
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  height: 100vh;
+  .top {
+    font-size: 50px;
+    color: #ffffff;
+    font-family: '楷体';
+    height: 130px;
+    line-height: 130px;
+  }
+  .info {
+    min-height: 700px;
+    background: #ffffff5f;
+    border-radius: 10px;
+    padding: 20px;
+    .one {
+      text-align: right;
+      margin: 0 0 10px 0;
+    }
+    .two {
+      margin: 0 0 10px 0;
+      .title {
+        font-size: 35px;
+        font-weight: bold;
+        text-align: center;
+        font-family: cursive;
+        margin: 0 0 10px 0;
+      }
+      .twoVideo {
+        height: 430px;
+        background: #000;
+        .left {
+          video {
+            width: 100%;
+            height: 430px;
+          }
+        }
+        .right {
+          height: 430px;
+          background: #000;
+          h1 {
+            height: 40px;
+            line-height: 40px;
+            text-align: center;
+            color: #fff;
+            font-size: 16px;
+            background: #409eff;
+            margin: 0;
+          }
+          .list {
+            height: 390px;
+            overflow: auto;
+            padding: 0px 10px;
+            .videoList {
+              border-radius: 10px;
+              border: 1px solid #fff;
+              color: #fff;
+              margin: 10px 0 0 0;
+              height: 120px;
+              .file {
+                video {
+                  width: 100%;
+                  height: 120px;
+                  overflow: hidden;
+                }
+              }
+              .videoInfo {
+                padding: 10px 5px;
+                p {
+                  font-size: 12px;
+                  margin: 0 0 5px 0;
+                }
+                p:nth-child(1) {
+                  font-size: 16px;
+                  overflow: hidden;
+                  text-overflow: ellipsis;
+                  -webkit-line-clamp: 2;
+                  word-break: break-all;
+                  display: -webkit-box;
+                  -webkit-box-orient: vertical;
+                }
+              }
+            }
+            .videoList:hover {
+              cursor: pointer;
+              .videoInfo {
+                p:nth-child(1) {
+                  color: #409eff;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+    .thr {
+      /deep/.el-tabs--border-card {
+        background: transparent;
+      }
+      p {
+        border-bottom: 1px dashed #ccc;
+        color: #000000;
+        font-weight: bold;
+        padding: 10px 0;
+      }
+    }
+  }
+}
+</style>

+ 118 - 0
src/views/dynamic/index.vue

@@ -0,0 +1,118 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <div class="w_1200">
+          <el-col :span="24" class="one">
+            <el-col :span="12" class="left">
+              <userTwo></userTwo>
+            </el-col>
+            <el-col :span="12" class="right">
+              <expertCom></expertCom>
+            </el-col>
+          </el-col>
+          <el-col :span="24" class="two">
+            <el-image :src="centerImage"></el-image>
+          </el-col>
+          <el-col :span="24" class="thr">
+            <el-col :span="12" class="left">
+              <achieveFiled></achieveFiled>
+            </el-col>
+            <el-col :span="12" class="right">
+              <achieveCom></achieveCom>
+            </el-col>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+// 数据整理
+import userTwo from './parts/userTwo.vue';
+// 专家一览
+import expertCom from './parts/expertCom.vue';
+// 成果领域
+import achieveFiled from './parts/achieveFiled.vue';
+// 成果单位
+import achieveCom from './parts/achieveCom.vue';
+
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'index',
+  props: {},
+  components: {
+    userTwo,
+    expertCom,
+    achieveFiled,
+    achieveCom,
+  },
+  data: function() {
+    return {
+      centerImage: require('@common/src/assets/center/sjdt.png'),
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  min-height: 500px;
+  padding: 10px 0;
+  .one {
+    margin: 0 0 10px 0;
+    .left {
+      width: 49%;
+      height: 530px;
+      background: #ffffff;
+      padding: 15px;
+      border-radius: 20px;
+      margin: 0 24px 0 0;
+      box-shadow: 0 0 5px #409eff;
+    }
+    .right {
+      width: 49%;
+      height: 530px;
+      background: #ffffff;
+      border-radius: 20px;
+      padding: 15px;
+      box-shadow: 0 0 5px #409eff;
+    }
+  }
+  .two {
+    height: 140px;
+    overflow: hidden;
+    margin: 0 0 10px 0;
+  }
+  .thr {
+    margin: 0 0 10px 0;
+    .left {
+      width: 49%;
+      height: 530px;
+      background: #ffffff;
+      padding: 15px;
+      border-radius: 20px;
+      margin: 0 24px 0 0;
+      box-shadow: 0 0 5px #409eff;
+    }
+    .right {
+      width: 49%;
+      height: 530px;
+      background: #ffffff;
+      border-radius: 20px;
+      padding: 15px;
+      box-shadow: 0 0 5px #409eff;
+    }
+  }
+}
+</style>

+ 129 - 0
src/views/dynamic/parts/achieveCom.vue

@@ -0,0 +1,129 @@
+<template>
+  <div id="top-right">
+    <el-row>
+      <el-col :span="24" class="main" v-loading="loading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
+        <el-col :span="24" class="one">
+          e专利
+        </el-col>
+        <el-col :span="24" class="two">
+          <div id="achieveCom" class="style"></div>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import echarts from 'echarts/lib/echarts';
+import 'echarts/lib/chart/bar';
+import 'echarts/lib/chart/pie';
+import 'echarts/lib/chart/line';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+import 'echarts/lib/component/toolbox';
+import 'echarts/lib/component/markPoint';
+import 'echarts/lib/component/tooltip';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: statistics } = createNamespacedHelpers('statistics');
+export default {
+  name: 'top-right',
+  props: {},
+  components: {},
+  data: () => {
+    return {
+      myChart: null,
+      loading: true,
+      data: [],
+    };
+  },
+  created() {
+    this.init();
+  },
+  methods: {
+    ...statistics(['query']),
+    async init() {
+      let res = await this.query('patent');
+      if (this.$checkRes(res)) {
+        let viewData = res.data.map(i => i.name);
+        this.myChart = echarts.init(document.getElementById('achieveCom'));
+        const option = {
+          title: {},
+          tooltip: { trigger: 'axis' },
+          grid: {
+            y2: 140,
+          },
+          xAxis: {
+            type: 'category',
+            boundaryGap: [0, 0.01],
+            name: '申请人',
+            data: viewData,
+            axisLabel: {
+              interval: 0, //横轴信息全部显示
+              rotate: -30, //-30度角倾斜显示
+            },
+            axisTick: {
+              // 坐标轴 刻度
+              show: true, // 是否显示
+              inside: true, // 是否朝内
+              length: 3, // 长度
+              lineStyle: {
+                // 默认取轴线的样式
+                color: 'red',
+                width: 1,
+                type: 'solid',
+              },
+            },
+          },
+          yAxis: {},
+          series: [
+            {
+              name: 'e专利',
+              label: {
+                show: true,
+                position: 'top',
+              },
+              type: 'bar',
+              itemStyle: {
+                normal: {
+                  color: function(params) {
+                    var colorList = ['#C1232B', '#B5C334', '#FCCE10', '#E87C25', '#27727B', '#FE8463', '#9BCA63', '#FAD860', '#F3A43B', '#60C0DD', '#778899'];
+                    return colorList[params.dataIndex];
+                  },
+                },
+              },
+              data: res.data,
+            },
+          ],
+        };
+        this.myChart.setOption(option);
+        this.loading = false;
+      }
+    },
+  },
+  computed: {
+    ...mapState(['top-right']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    padding: 15px 0 30px 0;
+    text-align: center;
+    font-size: 18px;
+    font-weight: bold;
+  }
+  .two {
+    .style {
+      height: 430px;
+    }
+  }
+}
+</style>

+ 128 - 0
src/views/dynamic/parts/achieveFiled.vue

@@ -0,0 +1,128 @@
+<template>
+  <div id="achieveFiled">
+    <el-row>
+      <el-col :span="24" class="main" v-loading="loading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
+        <el-col :span="24" class="one">
+          技术成果
+        </el-col>
+        <el-col :span="24" class="two">
+          <div id="chartPie" style="height:400px;"></div>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: statistics } = createNamespacedHelpers('statistics');
+import echarts from 'echarts/lib/echarts';
+import 'echarts/lib/chart/pie';
+import 'echarts/lib/chart/pie';
+import 'echarts/lib/chart/line';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+import 'echarts/lib/component/toolbox';
+import 'echarts/lib/component/markPoint';
+import 'echarts/lib/component/tooltip';
+export default {
+  name: 'achieveFiled',
+  props: {},
+  components: {},
+  data: () => {
+    return {
+      myChart: null,
+      type: 'pie',
+      // 成果领域
+      fieldList: [],
+      loading: true,
+      data: [],
+    };
+  },
+  created() {
+    this.init();
+  },
+  methods: {
+    ...statistics(['query']),
+    async init() {
+      let res = await this.query('product');
+      if (this.$checkRes(res)) {
+        this.myChart = echarts.init(document.getElementById('chartPie'));
+        const option = {
+          title: {},
+          grid: {
+            x: 25,
+            y: 45,
+            x2: 5,
+            y2: 20,
+            borderWidth: 1,
+          },
+          tooltip: { trigger: 'item', formatter: '{a} <br/>{b} : {c} ({d}%)' },
+          legend: {
+            // orient: 'vertical',
+            // x: 'left', //可设定图例在左、右、居中
+            // y: 'top', //可设定图例在上、下、居中
+            // padding: [0, 0, 0, 0],
+          },
+          series: [
+            {
+              name: '技术成果',
+              // 大小
+              radius: '50%',
+              // 位置
+              center: ['50%', '55%'],
+              label: {
+                show: true,
+                position: 'top',
+              },
+              type: 'pie',
+              data: res.data,
+              animationType: 'scale',
+              // 显示名称,数目,百分比
+              itemStyle: {
+                normal: {
+                  label: {
+                    show: true,
+                    formatter: '{b} : {c} ({d}%)',
+                  },
+                  labelLine: { show: true },
+                },
+              },
+            },
+          ],
+          // 数据视图
+          // toolbox: {
+          //   show: true,
+          //   feature: {
+          //     dataView: { readOnly: false },
+          //     saveAsImage: {},
+          //   },
+          // },
+        };
+        this.myChart.setOption(option);
+        this.loading = false;
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    padding: 15px 0 30px 0;
+    text-align: center;
+    font-size: 18px;
+    font-weight: bold;
+  }
+}
+</style>

+ 117 - 0
src/views/dynamic/parts/expertCom.vue

@@ -0,0 +1,117 @@
+<template>
+  <div id="down-pie">
+    <el-row>
+      <el-col :span="24" class="main" v-loading="loading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
+        <el-col :span="24" class="one">
+          专家一览
+        </el-col>
+        <el-col :span="24" class="two">
+          <div id="chatCom" style="height:400px;"></div>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: statistics } = createNamespacedHelpers('statistics');
+import echarts from 'echarts/lib/echarts';
+import 'echarts/lib/chart/pie';
+import 'echarts/lib/chart/pie';
+import 'echarts/lib/chart/line';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+import 'echarts/lib/component/toolbox';
+import 'echarts/lib/component/markPoint';
+import 'echarts/lib/component/tooltip';
+export default {
+  name: 'downPie',
+  props: {},
+  components: {},
+  data: () => {
+    return {
+      myChart: null,
+      type: 'pie',
+      loading: true,
+      data: [],
+    };
+  },
+  created() {
+    this.init();
+  },
+  methods: {
+    ...statistics(['query']),
+    async init() {
+      let res = await this.query('expert');
+      if (this.$checkRes(res)) {
+        this.myChart = echarts.init(document.getElementById('chatCom'));
+        const option = {
+          title: {},
+          grid: {
+            x: 25,
+            y: 45,
+            x2: 5,
+            y2: 20,
+            borderWidth: 1,
+          },
+          tooltip: { trigger: 'item', formatter: '{a} <br/>{b} : {c} ({d}%)' },
+          legend: {},
+          series: [
+            {
+              name: '专家一览',
+              // 大小
+              radius: '60%',
+              // 位置
+              center: ['50%', '60%'],
+              label: {
+                show: true,
+                position: 'top',
+              },
+              type: 'pie',
+              data: res.data,
+              animationType: 'scale',
+              // 显示名称,数目,百分比
+              itemStyle: {
+                normal: {
+                  label: {
+                    show: true,
+                    formatter: '{b} : {c} ({d}%)',
+                  },
+                  labelLine: { show: true },
+                  color: function(params) {
+                    var colorList = ['#4169E1', '#00FFFF', '#00FF7F', '#FFFF00', '#FF8C00', '#FF7F50'];
+                    return colorList[params.dataIndex];
+                  },
+                },
+              },
+            },
+          ],
+        };
+        this.myChart.setOption(option);
+        this.loading = false;
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    padding: 15px 0 30px 0;
+    text-align: center;
+    font-size: 18px;
+    font-weight: bold;
+  }
+}
+</style>

+ 168 - 0
src/views/dynamic/parts/userTwo.vue

@@ -0,0 +1,168 @@
+<template>
+  <div id="userTwo">
+    <el-row v-loading="loading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading">
+      <el-col :span="24" class="userTwo">
+        <el-col :psan="24" class="userTwoTop">
+          统计数据
+        </el-col>
+        <el-col :span="24" class="left">
+          <el-col class="list" :span="5" v-for="(item, index) in list" :key="index">
+            <el-col :span="24" class="one">
+              <p>{{ item.name }}</p>
+              <p>{{ item.value }}</p>
+            </el-col>
+          </el-col>
+          <!-- <el-col class="box" :span="5">
+            <el-col :span="24" class="two one">
+              <p>个人注册数量</p>
+              <p>{{ detail.num2 }}</p>
+            </el-col>
+          </el-col>
+          <el-col class="box" :span="5">
+            <el-col :span="24" class="three one">
+              <p>专家注册数量</p>
+              <p>{{ detail.num3 }}</p>
+            </el-col>
+          </el-col>
+          <el-col class="box" :span="5">
+            <el-col :span="24" class="five one">
+              <p>信息发布数量</p>
+              <p>{{ detail.num4 }}</p>
+            </el-col>
+          </el-col>
+          <el-col class="box" :span="5">
+            <el-col :span="24" class="five one">
+              <p>交流互动</p>
+              <p>{{ detail.num5 }}</p>
+            </el-col>
+          </el-col>
+          <el-col class="box" :span="5">
+            <el-col :span="24" class="three one">
+              <p>正在洽谈</p>
+              <p>{{ transaction1 }}</p>
+            </el-col>
+          </el-col>
+          <el-col class="box" :span="5">
+            <el-col :span="24" class="two one">
+              <p>达成意向</p>
+              <p>{{ transaction2 }}</p>
+            </el-col>
+          </el-col>
+          <el-col class="box" :span="5">
+            <el-col :span="24" class="four one">
+              <p>对接完成</p>
+              <p>{{ transaction3 }}</p>
+            </el-col>
+          </el-col> -->
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, mapActions, createNamespacedHelpers } from 'vuex';
+const { mapActions: statistics } = createNamespacedHelpers('statistics');
+export default {
+  name: 'userTwo',
+  props: {},
+  components: {},
+  data: () => ({
+    list: [],
+    loading: true,
+  }),
+  created() {
+    this.searchInfo();
+  },
+  methods: {
+    ...statistics(['query']),
+    async searchInfo({} = {}) {
+      let res = await this.query('circle');
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+      }
+      this.loading = false;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.userTwo {
+  float: left;
+  width: 100%;
+}
+.userTwo .userTwoTop {
+  padding: 20px 0 10px 0;
+  font-size: 18px;
+  font-weight: bold;
+  text-align: center;
+}
+.left {
+  float: left;
+}
+.right {
+  float: right;
+}
+.list {
+  overflow: hidden;
+  border-radius: 10px;
+  box-shadow: 0 0 5px #ccc;
+  padding: 10px;
+  margin: 30px 5px 30px 14px;
+  .one {
+    height: 110px;
+    p {
+      font-size: 22px;
+      color: #346da4;
+      font-weight: bold;
+      text-align: center;
+    }
+    p:first-child {
+      padding: 40px 0 0 0;
+      font-size: 12px;
+    }
+  }
+}
+.list:nth-child(1) .one {
+  background: url('~@common/src/assets/dynamic/4.png') no-repeat;
+  background-size: 100% 100%;
+}
+.list:nth-child(2) .one {
+  background: url('~@common/src/assets/dynamic/1.png') no-repeat;
+  background-size: 100% 100%;
+}
+.list:nth-child(3) .one {
+  background: url('~@common/src/assets/dynamic/3.png') no-repeat;
+  background-size: 100% 100%;
+}
+.list:nth-child(4) .one {
+  background: url('~@common/src/assets/dynamic/2.png') no-repeat;
+  background-size: 100% 100%;
+}
+.list:nth-child(5) .one {
+  background: url('~@common/src/assets/dynamic/2.png') no-repeat;
+  background-size: 100% 100%;
+}
+.list:nth-child(6) .one {
+  background: url('~@common/src/assets/dynamic/3.png') no-repeat;
+  background-size: 100% 100%;
+}
+.list:nth-child(7) .one {
+  background: url('~@common/src/assets/dynamic/1.png') no-repeat;
+  background-size: 100% 100%;
+}
+.list:nth-child(8) .one {
+  background: url('~@common/src/assets/dynamic/4.png') no-repeat;
+  background-size: 100% 100%;
+}
+</style>

+ 49 - 0
src/views/index.vue

@@ -0,0 +1,49 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24">
+        <div class="w_1200">
+          <el-col :span="24">
+            <el-button type="primary" size="mini" @click="loginBtn">平台登录</el-button>
+            <el-button type="primary" size="mini">管理登录</el-button>
+          </el-col>
+          <el-col :span="24">
+            <el-button type="primary" size="mini" @click="$router.push({ path: '/news/index' })">新闻资讯</el-button>
+            <el-button type="primary" size="mini" @click="$router.push({ path: '/market/index' })">科技超市</el-button>
+            <el-button type="primary" size="mini" @click="$router.push({ path: '/live/index' })">直播大厅</el-button>
+            <el-button type="primary" size="mini" @click="$router.push({ path: '/dynamic/index' })">数据动态</el-button>
+            <el-button type="primary" size="mini" @click="$router.push({ path: '/techolchat/index' })">技术交流</el-button>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'index',
+  props: {},
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    // 登录
+    loginBtn() {
+      this.$router.push({ path: '/login' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped></style>

+ 139 - 0
src/views/live/achieve/list.vue

@@ -0,0 +1,139 @@
+<template>
+  <div id="list">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="8" class="list" v-for="(item, index) in achieveList" :key="index">
+          <el-col :span="24" class="image">
+            <el-image :src="imgUrl"></el-image>
+          </el-col>
+          <el-col :span="24" class="info">
+            <el-col :span="24" class="title textOver">
+              <span>[{{ item.room_id }}]</span>
+              <span>{{ item.title }}</span>
+            </el-col>
+            <el-col :span="24" class="other">
+              <el-col :span="10" class="textOver"><i class="el-icon-location-outline"></i>{{ item.province }}-{{ item.place }} </el-col>
+              <el-col :span="14" class="textOver"><i class="el-icon-time"></i>{{ item.start_time }}-{{ item.end_time }} </el-col>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini" @click="toAdmin(item.room_id)">管理进入</el-button>
+              <el-button type="primary" size="mini" v-if="item.status == '1'" @click="liveBtn(item)">进入对接会</el-button>
+              <el-button type="primary" size="mini" v-else-if="item.status == '0'" @click="applyBtn(item)">申请对接会</el-button>
+              <el-button type="primary" size="mini" v-else-if="item.status == '2'">查看成果</el-button>
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog title="登陆" width="40%" :visible.sync="adminDialog" @closed="handleClose" :destroy-on-close="true">
+      <data-form :data="loginForm" :fields="loginFields" :rules="rules" @save="toSave" submitText="登陆"> </data-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import dataForm from '@common/src/components/frame/form.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dock } = createNamespacedHelpers('dock');
+export default {
+  name: 'list',
+  props: {
+    achieveList: { type: Array },
+  },
+  components: { dataForm },
+  data: function() {
+    return {
+      adminDialog: false,
+      imgUrl: require('@common/src/assets/hall.jpg'),
+      loginForm: {},
+      loginFields: [
+        { label: '房间号', model: 'room_phone', type: 'text' },
+        { label: '登录密码', model: 'password', required: true, type: 'password' },
+      ],
+      rules: {
+        room_phone: [{ required: true, trigger: 'blur', message: '请填写房间号' }],
+        password: [{ required: true, trigger: 'blur', message: '请填写密码' }],
+      },
+    };
+  },
+  created() {},
+  methods: {
+    ...dock(['login']),
+    toAdmin(room_id) {
+      if (_.isEqual(_.get(this.user, 'room_id'), room_id)) this.$router.push({ path: '/admin/live/achieve' });
+      this.adminDialog = true;
+      this.loginForm.room_phone = room_id;
+    },
+    handleClose() {
+      this.loginForm = {};
+      this.adminDialog = false;
+    },
+    async toSave() {
+      const res = await this.login(_.cloneDeep(this.loginForm));
+      if (res.errcode != '0') {
+        this.$message.error(res.errmsg);
+      } else {
+        this.$router.push({ path: '/admin/live/achieve' });
+      }
+    },
+    // 申请展会
+    applyBtn(data) {
+      if (this.user) {
+        this.$router.push({ path: '/achieveLive/apply', query: { id: data.id } });
+      } else {
+        this.$message({
+          message: '请先登录,再申请参展!',
+          type: 'warning',
+        });
+      }
+    },
+    // 进入展会
+    liveBtn(data) {
+      this.$router.push({ path: '/achieveLive/before', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.list {
+  width: 379px;
+  border: 1px solid #ccc;
+  margin: 0 15px 10px 0;
+  border-radius: 10px;
+  .image {
+    height: 260px;
+    .el-image {
+      height: 260px;
+      border-top-left-radius: 10px;
+      border-top-right-radius: 10px;
+    }
+  }
+  .info {
+    padding: 5px 10px 10px 10px;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      padding: 0 0 5px 0;
+      span:nth-child(1) {
+        color: #ff0000;
+      }
+    }
+    .other {
+      font-size: 14px;
+      padding: 0 0 5px 0;
+    }
+    .btn {
+      text-align: center;
+    }
+  }
+}
+.list:nth-child(3n) {
+  margin: 0 0 10px 0;
+}
+</style>

+ 65 - 0
src/views/live/index.vue

@@ -0,0 +1,65 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <div class="w_1200">
+          <el-tabs type="border-card" class="tab">
+            <el-tab-pane label="科技成果在线">
+              <achieveLive></achieveLive>
+            </el-tab-pane>
+            <el-tab-pane label="人才对接在线">
+              <personalLive></personalLive>
+            </el-tab-pane>
+            <el-tab-pane label="培训问诊在线">
+              <trainLive></trainLive>
+            </el-tab-pane>
+            <el-tab-pane label="科技频道在线">
+              <scienceLive></scienceLive>
+            </el-tab-pane>
+          </el-tabs>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import achieveLive from './parts/achieveLive.vue';
+import personalLive from './parts/personalLive.vue';
+import trainLive from './parts/trainLive.vue';
+import scienceLive from './parts/scienceLive.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'index',
+  props: {},
+  components: { achieveLive, personalLive, trainLive, scienceLive },
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  min-height: 557px;
+  padding: 10px 0;
+  .tab {
+    min-height: 530px;
+    /deep/.el-tabs__item {
+      font-size: 18px;
+      font-weight: bold;
+      height: 50px;
+      line-height: 50px;
+    }
+  }
+}
+</style>

+ 80 - 0
src/views/live/parts/achieveLive.vue

@@ -0,0 +1,80 @@
+<template>
+  <div id="achieveLive">
+    <el-row>
+      <el-col :span="24" class="achieve">
+        <el-collapse v-model="active" accordion>
+          <el-collapse-item title="正在直播" name="1">
+            <list :achieveList="oneList"></list>
+          </el-collapse-item>
+          <el-collapse-item title="下期预告" name="2">
+            <list :achieveList="twoList"></list>
+          </el-collapse-item>
+          <el-collapse-item title="已往直播" name="3">
+            <list :achieveList="thrList"></list>
+          </el-collapse-item>
+        </el-collapse>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import list from '../achieve/list.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dock } = createNamespacedHelpers('dock');
+const { mapActions: place } = createNamespacedHelpers('place');
+export default {
+  name: 'achieveLive',
+  props: {},
+  components: {
+    list,
+  },
+  data: function() {
+    return {
+      active: '1',
+      // 正在直播
+      oneList: [],
+      // 下期预告
+      twoList: [],
+      // 已往直播
+      thrList: [],
+    };
+  },
+  async created() {
+    await this.searchOther();
+  },
+  methods: {
+    ...dock(['query']),
+    ...place({ queryName: 'queryName' }),
+    async searchOther({ skip = 0, limit = 10, ...info } = {}) {
+      let one = await this.query({ skip, status: 1, ...info });
+      let two = await this.query({ skip, status: 0, ...info });
+      let thr = await this.query({ skip, status: 2, ...info });
+      if (this.$checkRes(one) || this.$checkRes(two) || this.$checkRes(thr)) {
+        for (const val of one.data) this.searchPlace(val);
+        this.$set(this, `oneList`, one.data);
+        for (const val of two.data) this.searchPlace(val);
+        this.$set(this, `twoList`, two.data);
+        for (const val of thr.data) this.searchPlace(val);
+        this.$set(this, `thrList`, thr.data);
+      }
+    },
+    // 查询省市
+    async searchPlace(data) {
+      let nameData = { code: [data.province, data.place] };
+      let res = await this.queryName(nameData);
+      if (this.$checkRes(res)) {
+        data.province = res.data.find(i => i.code == data.province).name;
+        data.place = res.data.find(i => i.code == data.place).name;
+        return data;
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped></style>

+ 80 - 0
src/views/live/parts/personalLive.vue

@@ -0,0 +1,80 @@
+<template>
+  <div id="personalLive">
+    <el-row>
+      <el-col :span="24" class="personal">
+        <el-collapse v-model="active" accordion>
+          <el-collapse-item title="正在直播" name="1">
+            <list :personalList="oneList"></list>
+          </el-collapse-item>
+          <el-collapse-item title="下期预告" name="2">
+            <list :personalList="twoList"></list>
+          </el-collapse-item>
+          <el-collapse-item title="已往直播" name="3">
+            <list :personalList="thrList"></list>
+          </el-collapse-item>
+        </el-collapse>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import list from '../personal/list.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'personalLive',
+  props: {},
+  components: {
+    list,
+  },
+  data: function() {
+    return {
+      active: '1',
+      oneList: [
+        {
+          room_id: '1001',
+          end_time: '2020-10-23 00:00',
+          place: '吉林省',
+          province: '长春市',
+          start_time: '2020-10-16 10:00',
+          status: '1',
+          title: '人才招聘会模板',
+        },
+      ],
+      twoList: [
+        {
+          room_id: '1001',
+          end_time: '2020-10-23 00:00',
+          place: '吉林省',
+          province: '长春市',
+          start_time: '2020-10-16 10:00',
+          status: '0',
+          title: '人才招聘会模板',
+        },
+      ],
+      thrList: [
+        {
+          room_id: '1001',
+          end_time: '2020-10-23 00:00',
+          place: '吉林省',
+          province: '长春市',
+          start_time: '2020-10-16 10:00',
+          status: '2',
+          title: '人才招聘会模板',
+        },
+      ],
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped></style>

+ 146 - 0
src/views/live/parts/scienceLive.vue

@@ -0,0 +1,146 @@
+<template>
+  <div id="scienceLive">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="8" class="list" v-for="(item, index) in list" :key="index">
+          <el-col :span="24" class="image">
+            <el-image :src="imgUrl"></el-image>
+          </el-col>
+          <el-col :span="24" class="info">
+            <el-col :span="24" class="title textOver">
+              <span>[{{ item.room_id }}]</span>
+              <span>{{ item.type }}-{{ item.title }}</span>
+            </el-col>
+            <el-col :span="24" class="other">
+              <el-col :span="12" class="textOver">信息来源:{{ item.origin }} </el-col>
+              <el-col :span="12" class="textOver">更新时间:{{ item.create_time }} </el-col>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-button type="warning" size="mini" @click="toAdmin(item.room_id)">管理进入</el-button>
+              <el-button type="primary" size="mini" @click="inChannel(item)">进入频道</el-button>
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog title="登陆" width="40%" :visible.sync="adminDialog" @closed="handleClose" :destroy-on-close="true">
+      <data-form :data="loginForm" :fields="loginFields" :rules="rules" @save="toSave" submitText="登陆"> </data-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import dataForm from '@common/src/components/frame/form.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: channel } = createNamespacedHelpers('channel');
+const { mapActions: code } = createNamespacedHelpers('code');
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'scienceLive',
+  props: {},
+  components: { dataForm },
+  data: function() {
+    return {
+      adminDialog: false,
+      imgUrl: require('@common/src/assets/kjzx.jpg'),
+      list: [],
+      loginForm: {},
+      loginFields: [
+        { label: '房间号', model: 'room_id', type: 'text' },
+        { label: '登录密码', model: 'passwd', required: true, type: 'password' },
+      ],
+      rules: {
+        room_id: [{ required: true, trigger: 'blur', message: '请填写房间号' }],
+        passwd: [{ required: true, trigger: 'blur', message: '请填写密码' }],
+      },
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...code({ codeQuery: 'query' }),
+    ...channel(['query', 'login']),
+    async search() {
+      let res = await this.query();
+      if (this.$checkRes(res)) {
+        for (const val of res.data) this.searchOther(val);
+      }
+      this.$set(this, `list`, res.data);
+    },
+    async searchOther(val) {
+      let res = await this.codeQuery({ category: '04' });
+      if (this.$checkRes(res)) {
+        val.type = res.data.find(i => i.id == val.type).name;
+        return;
+      }
+    },
+    toAdmin(room_id) {
+      if (_.isEqual(_.get(this.user, 'room_id'), room_id)) this.$router.push({ path: '/admin/live/science' });
+      this.adminDialog = true;
+      this.loginForm.room_id = room_id;
+    },
+    handleClose() {
+      this.loginForm = {};
+      this.adminDialog = false;
+    },
+    async toSave() {
+      const res = await this.login(_.cloneDeep(this.loginForm));
+      if (res.errcode != '0') {
+        this.$message.error(res.errmsg);
+      } else {
+        this.$router.push({ path: '/admin/live/science' });
+      }
+    },
+    // 进入频道
+    inChannel(data) {
+      this.$router.push({ path: '/channelLive/index', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.list {
+  width: 379px;
+  border: 1px solid #ccc;
+  margin: 0 15px 10px 0;
+  border-radius: 10px;
+  .image {
+    height: 260px;
+    .el-image {
+      height: 260px;
+      border-top-left-radius: 10px;
+      border-top-right-radius: 10px;
+    }
+  }
+  .info {
+    padding: 5px 10px 10px 10px;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      padding: 0 0 5px 0;
+      span:nth-child(1) {
+        color: #ff0000;
+      }
+    }
+    .other {
+      font-size: 14px;
+      padding: 0 0 5px 0;
+    }
+    .btn {
+      text-align: center;
+    }
+  }
+}
+.list:nth-child(3n) {
+  margin: 0 0 10px 0;
+}
+</style>

+ 183 - 0
src/views/live/parts/trainLive.vue

@@ -0,0 +1,183 @@
+<template>
+  <div id="trainLive">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="8" class="list" v-for="(item, index) in list" :key="index">
+          <el-col :span="24" class="image">
+            <el-image :src="imgUrl"></el-image>
+          </el-col>
+          <el-col :span="24" class="info">
+            <el-col :span="24" class="title textOver">
+              <span>[{{ item.room_id }}]</span>
+              <span>{{ item.title }}</span>
+            </el-col>
+            <el-col :span="24" class="other">
+              <el-col :span="12" class="textOver"><i class="el-icon-location-outline"></i>{{ item.province }}-{{ item.place }} </el-col>
+              <el-col :span="12" class="textOver"><i class="el-icon-time"></i>{{ item.start_date }} </el-col>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-button type="warning" size="mini" @click="toAdmin(item.room_id)">管理进入</el-button>
+              <el-button type="primary" size="mini" @click="inChannel(item)">进入频道</el-button>
+              <el-button type="primary" size="mini" @click="logout">退出登录</el-button>
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog title="登陆" width="40%" :visible.sync="adminDialog" @closed="handleClose" :destroy-on-close="true">
+      <data-form :data="loginForm" :fields="loginFields" :rules="rules" @save="toSave" submitText="登陆"> </data-form>
+    </el-dialog>
+    <el-dialog title="用户登录" width="30%" :visible.sync="userDialog" @closed="handleClose" :destroy-on-close="true">
+      <data-form :data="userForm" :fields="userFields" :rules="{}" @save="userToSave"> </data-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import dataForm from '@common/src/components/frame/form.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: mapTrainLive } = createNamespacedHelpers('trainLive');
+const { mapActions: place } = createNamespacedHelpers('place');
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'trainLive',
+  props: {},
+  components: { dataForm },
+  data: function() {
+    return {
+      adminDialog: false,
+      imgUrl: require('@common/src/assets/train.jpg'),
+      list: [],
+      loginForm: {},
+      loginFields: [
+        { label: '房间号', model: 'room_id', type: 'text' },
+        { label: '登录密码', model: 'password', required: true, type: 'password' },
+      ],
+      rules: {
+        room_id: [{ required: true, trigger: 'blur', message: '请填写房间号' }],
+        password: [{ required: true, trigger: 'blur', message: '请填写密码' }],
+      },
+      // 用户登录
+      userDialog: false,
+      userForm: {},
+      userFields: [
+        { label: '账号', model: 'user_phone' },
+        { label: '密码', model: 'user_password', required: true, type: 'password' },
+      ],
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...mapTrainLive(['query', 'login', 'userLogin', 'userLogout']),
+    ...place({ queryName: 'queryName' }),
+    // 查询列表
+    async search() {
+      let res = await this.query();
+      if (this.$checkRes(res)) {
+        for (const val of res.data) this.searchPlace(val);
+        this.$set(this, `list`, res.data);
+      }
+    },
+    // 查询省市
+    async searchPlace(data) {
+      let nameData = { code: [data.province, data.place] };
+      let res = await this.queryName(nameData);
+      if (this.$checkRes(res)) {
+        data.province = res.data.find(i => i.code == data.province).name;
+        data.place = res.data.find(i => i.code == data.place).name;
+        return data;
+      }
+    },
+    // 管理进入
+    toAdmin(room_id) {
+      if (_.isEqual(_.get(this.user, 'room_id'), room_id)) this.$router.push({ path: '/admin/live/train' });
+      this.adminDialog = true;
+      this.loginForm.room_id = room_id;
+    },
+    async toSave() {
+      const res = await this.login(_.cloneDeep(this.loginForm));
+      if (res.errcode != '0') {
+        this.$message.error(res.errmsg);
+      } else {
+        this.$router.push({ path: '/admin/live/train' });
+      }
+    },
+    // 进入频道
+    inChannel(data) {
+      this.$set(this.userForm, `id`, data.id);
+      let user = this.user;
+      this.userDialog = true;
+      // if (user & user._id) {
+      //   this.$router.push({ path: '/trainLive/index', query: { id: data.id } });
+      // } else {
+      //   this.userDialog = true;
+      // }
+    },
+    // 用户登录提交
+    async userToSave({ data }) {
+      let res = await this.userLogin(data);
+      if (this.$checkRes(res)) {
+        this.$router.push({ path: '/trainLive/index', query: { id: data.id } });
+      }
+    },
+    // 退出登录
+    async logout() {
+      let res = await this.userLogout('6018c378d08f0821b7648d0c');
+    },
+    // 取消
+    handleClose() {
+      this.loginForm = {};
+      this.adminDialog = false;
+      this.userForm = {};
+      this.userDialog = false;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.list {
+  width: 379px;
+  border: 1px solid #ccc;
+  margin: 0 15px 10px 0;
+  border-radius: 10px;
+  .image {
+    height: 260px;
+    .el-image {
+      height: 260px;
+      border-top-left-radius: 10px;
+      border-top-right-radius: 10px;
+    }
+  }
+  .info {
+    padding: 5px 10px 10px 10px;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      padding: 0 0 5px 0;
+      span:nth-child(1) {
+        color: #ff0000;
+      }
+    }
+    .other {
+      font-size: 14px;
+      padding: 0 0 5px 0;
+    }
+    .btn {
+      text-align: center;
+    }
+  }
+}
+.list:nth-child(3n) {
+  margin: 0 0 10px 0;
+}
+</style>

+ 90 - 0
src/views/live/personal/list.vue

@@ -0,0 +1,90 @@
+<template>
+  <div id="list">
+    <el-row>
+      <el-col :span="24">
+        <el-col :span="8" class="list" v-for="(item, index) in personalList" :key="index">
+          <el-col :span="24" class="image">
+            <el-image :src="imgUrl"></el-image>
+          </el-col>
+          <el-col :span="24" class="info">
+            <el-col :span="24" class="title textOver">
+              <span>[{{ item.room_id }}]</span>
+              <span>{{ item.title }}</span>
+            </el-col>
+            <el-col :span="24" class="other">
+              <el-col :span="10" class="textOver"><i class="el-icon-location-outline"></i>{{ item.province }}-{{ item.place }} </el-col>
+              <el-col :span="14" class="textOver"><i class="el-icon-time"></i>{{ item.start_time }}-{{ item.end_time }} </el-col>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-button type="primary" size="mini" @click="inPersonal">进入对接会</el-button>
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list',
+  props: {
+    personalList: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {
+      imgUrl: require('@common/src/assets/personal.jpg'),
+    };
+  },
+  created() {},
+  methods: {
+    inPersonal() {
+      this.$router.push({ path: '/personalLive/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.list {
+  width: 379px;
+  border: 1px solid #ccc;
+  margin: 0 15px 10px 0;
+  border-radius: 10px;
+  .image {
+    height: 260px;
+    .el-image {
+      border-top-left-radius: 10px;
+      border-top-right-radius: 10px;
+      height: 260px;
+    }
+  }
+  .info {
+    padding: 5px 10px 10px 10px;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      padding: 0 0 5px 0;
+      span:nth-child(1) {
+        color: #ff0000;
+      }
+    }
+    .other {
+      font-size: 14px;
+      padding: 0 0 5px 0;
+    }
+    .btn {
+      text-align: center;
+    }
+  }
+}
+.list:nth-child(3n) {
+  margin: 0 0 10px 0;
+}
+</style>

+ 151 - 0
src/views/login.vue

@@ -0,0 +1,151 @@
+<template>
+  <div id="login">
+    <el-row>
+      <el-col :span="24" class="main">
+        <div class="w_1200">
+          <el-col :span="24" class="info">
+            <el-col :span="24" class="title">
+              中科在线(长春)-平台用户登录
+            </el-col>
+            <el-col :span="24" class="form">
+              <el-form :model="form" :rules="rules" ref="form" label-width="100px">
+                <el-form-item label="账号" prop="phone">
+                  <el-input v-model="form.phone" placeholder="请输入账号"></el-input>
+                </el-form-item>
+                <el-form-item label="密码" prop="password">
+                  <el-input v-model="form.password" placeholder="请输入密码" show-password></el-input>
+                </el-form-item>
+                <el-form-item label="用户类别" prop="type">
+                  <el-radio-group v-model="form.type">
+                    <el-radio label="4">个人用户</el-radio>
+                    <el-radio label="5">企业用户</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+                <el-col :span="24" class="loginBtn">
+                  <el-button type="danger" @click="resetForm()">取消登录</el-button>
+                  <el-button type="primary" @click="submitForm('form')">确认登录</el-button>
+                </el-col>
+              </el-form>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-link type="danger" @click="registerBtn">注册账号</el-link>
+            </el-col>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: personal } = createNamespacedHelpers('personal');
+const { mapActions: organization } = createNamespacedHelpers('organization');
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'login',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      form: {},
+      rules: {
+        phone: [{ required: true, message: '请输入账号', trigger: 'blur' }],
+        password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
+        type: [{ required: true, message: '请选择用户类别', trigger: 'change' }],
+      },
+    };
+  },
+  created() {},
+  methods: {
+    ...personal(['perLogin']),
+    ...organization(['orgLogin']),
+    // 确认登录
+    submitForm(formName) {
+      this.$refs[formName].validate(async valid => {
+        if (valid) {
+          let data = this.form;
+          if (data.type == '4') {
+            let res = await this.perLogin({ user: data });
+            if (this.$checkRes(res)) {
+              this.$message.success('登录成功');
+              this.$router.push('/userCenter');
+            }
+          } else if (data.type == '5') {
+            data.institution_code = data.phone;
+            let res = await this.orgLogin({ user: data });
+            if (this.$checkRes(res)) {
+              this.$message.success('登录成功');
+              this.$router.push('/userCenter');
+            }
+          }
+          // 存储用户类别
+          localStorage.setItem('type', data.type);
+        } else {
+          console.log('error submit!!');
+          return false;
+        }
+      });
+    },
+    // 取消登录
+    resetForm() {
+      this.$router.push({ path: '/' });
+    },
+    // 注册
+    registerBtn() {
+      this.$router.push({ path: '/register' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  background: url('~@common/src/assets/login.jpg');
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  height: 100%;
+  .info {
+    width: 40%;
+    height: 430px;
+    background: #ffffff;
+    position: relative;
+    margin: 21% 30%;
+    border-radius: 10px;
+    .title {
+      text-align: center;
+      font-family: fangsong;
+      color: #409eff;
+      font-size: 28px;
+      text-shadow: -1px 0 #fff, 0 1px #fff, 1px 0 #fff, 0 -1px #fff;
+      font-weight: 600;
+      padding: 30px 0;
+    }
+    .form {
+      padding: 0 25px;
+      height: 300px;
+      overflow: hidden;
+      /deep/.el-form-item__label {
+        font-size: 18px;
+      }
+      .loginBtn {
+        text-align: center;
+        padding: 20px 0;
+      }
+    }
+    .btn {
+      text-align: right;
+      padding: 0 10px;
+      .el-link {
+        font-size: 18px;
+      }
+    }
+  }
+}
+</style>

+ 153 - 0
src/views/market/index.vue

@@ -0,0 +1,153 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <div class="w_1200">
+          <el-col :span="24" class="one">
+            <el-col :span="12" class="left">
+              <achieve :list="achieveList"></achieve>
+            </el-col>
+            <el-col :span="12" class="right">
+              <patent :list="patentList"></patent>
+            </el-col>
+          </el-col>
+          <el-col :span="24" class="two">
+            <el-image :src="imgUrl"></el-image>
+          </el-col>
+          <el-col :span="24" class="one">
+            <el-col :span="12" class="left">
+              <business :list="businessList"></business>
+            </el-col>
+            <el-col :span="12" class="right">
+              <expert :list="expertList"></expert>
+            </el-col>
+          </el-col>
+          <el-col :span="24" class="two">
+            <el-image :src="imgUrl"></el-image>
+          </el-col>
+          <el-col :span="24" class="one">
+            <el-col :span="12" class="left">
+              <technology :list="technologyList"></technology>
+            </el-col>
+            <el-col :span="12" class="right">
+              <roadshow :list="roadshowList"></roadshow>
+            </el-col>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import achieve from './index/achieve.vue';
+import patent from './index/patent.vue';
+import business from './index/business.vue';
+import expert from './index/expert.vue';
+import technology from './index/technology.vue';
+import roadshow from './index/roadshow.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: product } = createNamespacedHelpers('product');
+const { mapActions: mapPatent } = createNamespacedHelpers('patent');
+const { mapActions: mapExpert } = createNamespacedHelpers('expert');
+const { mapActions: mapRoadShow } = createNamespacedHelpers('roadShow');
+
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'index',
+  props: {},
+  components: {
+    achieve,
+    patent,
+    business,
+    expert,
+    technology,
+    roadshow,
+  },
+  data: function() {
+    return {
+      imgUrl: require('@common/src/assets/center/gqxx.png'),
+      // 技术成果
+      achieveList: [],
+      // e专利
+      patentList: [],
+      // 商务服务
+      businessList: [],
+      // 专家智库
+      expertList: [],
+      // 科技需求
+      technologyList: [],
+      // 项目路演
+      roadshowList: [],
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...product({ productQuery: 'query' }),
+    ...mapPatent({ patentQuery: 'query' }),
+    ...mapExpert({ expertQuery: 'query' }),
+    ...mapRoadShow({ roadShowQuery: 'query' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      // 技术成果
+      let res = await this.productQuery({ skip, limit: 6, status: '2', type: '1', ...info });
+      if (this.$checkRes(res)) this.$set(this, `achieveList`, res.data);
+      // 科技需求
+      res = await this.productQuery({ skip, limit: 6, status: '2', type: '0', ...info });
+      if (this.$checkRes(res)) this.$set(this, `technologyList`, res.data);
+      // 商务服务
+      res = await this.productQuery({ skip, limit: 5, status: '2', type: '2', ...info });
+      if (this.$checkRes(res)) this.$set(this, `businessList`, res.data);
+      // e专利
+      res = await this.patentQuery({ skip, limit: 6, ...info });
+      if (this.$checkRes(res)) this.$set(this, `patentList`, res.data);
+      // 专家智库
+      res = await this.expertQuery({ skip, limit: 8, ...info });
+      if (this.$checkRes(res)) this.$set(this, `expertList`, res.data);
+      // 项目路演
+      res = await this.roadShowQuery({ skip, limit: 8, ...info });
+      if (this.$checkRes(res)) this.$set(this, `roadshowList`, res.data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  background-image: linear-gradient(#ffffff, #a3d5f6, #ffffff);
+  min-height: 557px;
+  padding: 10px 0;
+  .one {
+    margin: 0 0 10px 0;
+    .left {
+      width: 49%;
+      min-height: 530px;
+      background: #ffffff;
+      padding: 15px;
+      border-radius: 20px;
+      margin: 0 24px 0 0;
+      box-shadow: 0 0 5px #409eff;
+    }
+    .right {
+      width: 49%;
+      min-height: 530px;
+      background: #ffffff;
+      border-radius: 20px;
+      padding: 15px;
+      box-shadow: 0 0 5px #409eff;
+    }
+  }
+  .two {
+    height: 140px;
+    overflow: hidden;
+    margin: 0 0 10px 0;
+  }
+}
+</style>

+ 133 - 0
src/views/market/index/achieve.vue

@@ -0,0 +1,133 @@
+<template>
+  <div id="achieve">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-col :span="24" class="top">
+          <top :topInfo="topInfo" @moreBtn="moreBtn"></top>
+        </el-col>
+        <el-col :span="24" class="down">
+          <el-col :span="8" class="list" v-for="(item, index) in list" :key="index" @click.native="toDetail(item.id)">
+            <el-col :span="24" class="text">
+              <p class="name textOver">{{ item.name }}</p>
+              <p class="brief">{{ item.achievebrief }}</p>
+              <p class="other">
+                <span class="textOver">领域:{{ item.field }}</span>
+                <span class="textOver">联系人:{{ item.contacts }}</span>
+              </p>
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import top from './top.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'achieve',
+  props: {
+    list: { type: Array },
+  },
+  components: {
+    top,
+  },
+  data: function() {
+    return {
+      topInfo: {
+        title: '技术成果',
+        engTitle: 'achieve',
+      },
+    };
+  },
+  created() {},
+  methods: {
+    // 查看更多
+    moreBtn() {
+      this.$router.push({ path: '/market/list', query: { index: 0 } });
+    },
+    toDetail(id) {
+      this.$router.push({ path: '/market/list', query: { index: 0, id: id || 'testid' } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .top {
+    height: 50px;
+    overflow: hidden;
+  }
+  .down {
+    height: 450px;
+    position: relative;
+    .list {
+      position: relative;
+      overflow: hidden;
+      height: 220px;
+      margin: 0 10px 10px 0;
+      width: 32%;
+      .image {
+        height: 220px;
+        overflow: hidden;
+        .el-image {
+          width: 100%;
+          height: 220px;
+        }
+      }
+      .text {
+        width: 100%;
+        height: 220px;
+        background: url('~@common/src/assets/achieve.png');
+        background-size: 100% 100%;
+        background-repeat: no-repeat;
+        padding: 25px 19px;
+        .name {
+          font-size: 16px;
+          font-weight: bold;
+          margin: 0 0 5px 0;
+        }
+        .brief {
+          font-size: 12px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          -webkit-line-clamp: 7;
+          word-break: break-all;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          padding: 0 5px;
+        }
+        .other {
+          padding: 5px 0 0 0;
+          position: absolute;
+          bottom: 10px;
+          width: 83%;
+          span {
+            padding: 3px 0 0 0;
+            float: left;
+            width: 100%;
+            font-size: 12px;
+          }
+        }
+      }
+    }
+    .list:nth-child(3n) {
+      margin: 0 0 10px 0;
+    }
+    .list:hover {
+      cursor: pointer;
+      .text {
+        .name {
+          color: #409eff;
+        }
+      }
+    }
+  }
+}
+</style>

+ 116 - 0
src/views/market/index/business.vue

@@ -0,0 +1,116 @@
+<template>
+  <div id="business">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-col :span="24" class="top">
+          <top :topInfo="topInfo" @moreBtn="moreBtn"></top>
+        </el-col>
+        <el-col :span="24" class="down">
+          <el-col :span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="toDetail(item.id)">
+            <el-col :span="10" class="name textOver">
+              {{ item.name }}
+            </el-col>
+            <el-col :span="10" class="messbute"> 信息属性:{{ item.messattribute }} </el-col>
+            <el-col :span="4" class="date">
+              {{ getDate(item.create_time) }}
+            </el-col>
+            <el-col :span="24" class="info">
+              {{ item.informationdesc }}
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import top from './top.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+var moment = require('moment');
+export default {
+  name: 'business',
+  props: {
+    list: { type: Array },
+  },
+  components: {
+    top,
+  },
+  data: function() {
+    return {
+      topInfo: {
+        title: '商务服务',
+        engTitle: 'business',
+      },
+    };
+  },
+  created() {},
+  methods: {
+    // 查看更多
+    moreBtn() {
+      this.$router.push({ path: '/market/list', query: { index: 2 } });
+    },
+    toDetail(id) {
+      this.$router.push({ path: '/market/list', query: { index: 2, id } });
+    },
+    // 整理时间
+    getDate(date) {
+      let newsDate = moment(date).format('YYYY-MM-DD');
+      if (newsDate) return newsDate;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .top {
+    height: 50px;
+    overflow: hidden;
+  }
+  .down {
+    height: 450px;
+    position: relative;
+    .list {
+      padding: 11px 0;
+      border-bottom: 1px dashed #044b79;
+      .name {
+        font-size: 18px;
+        font-weight: bold;
+      }
+      .messbute {
+        font-size: 16px;
+      }
+      .date {
+        font-size: 16px;
+        text-align: center;
+      }
+      .info {
+        padding: 5px 0 0 0;
+        height: 45px;
+        font-size: 14px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        -webkit-line-clamp: 2;
+        word-break: break-all;
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+      }
+    }
+    .list:last-child {
+      border-bottom: none;
+    }
+    .list:hover {
+      cursor: pointer;
+      .name {
+        color: #0085d2;
+        font-weight: bold;
+      }
+    }
+  }
+}
+</style>

+ 120 - 0
src/views/market/index/expert.vue

@@ -0,0 +1,120 @@
+<template>
+  <div id="expert">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-col :span="24" class="top">
+          <top :topInfo="topInfo" @moreBtn="moreBtn"></top>
+        </el-col>
+        <el-col :span="24" class="down">
+          <el-col :span="12" class="list" v-for="(item, index) in list" :key="index" @click.native="toDetail(item.user_id)">
+            <el-col :span="8" class="expertimage">
+              <el-image v-if="item.img_path != null || undefined" :src="item.img_path"></el-image>
+              <el-image :src="imgUrl" v-else></el-image>
+            </el-col>
+            <el-col :span="16" class="rightInfo">
+              <el-col :span="24" class="name textOver">
+                {{ item.name }}
+              </el-col>
+              <el-col :span="24" class="school textOver"> {{ item.zwzc }} </el-col>
+              <el-col :span="24" class="company textOver">{{ item.company }} </el-col>
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import top from './top.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'expert',
+  props: {
+    list: { type: Array },
+  },
+  components: {
+    top,
+  },
+  data: function() {
+    return {
+      topInfo: {
+        title: '专家智库',
+        engTitle: 'expert',
+      },
+      imgUrl: require('@common/src/assets/expert.png'),
+    };
+  },
+  created() {},
+  methods: {
+    // 查看更多
+    moreBtn() {
+      this.$router.push({ path: '/market/list', query: { index: 3 } });
+    },
+    toDetail(id) {
+      this.$router.push({ path: '/market/list', query: { index: 3, id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .top {
+    height: 50px;
+    overflow: hidden;
+  }
+  .down {
+    height: 450px;
+    position: relative;
+    .list {
+      padding: 10px 0;
+      border-bottom: 1px dashed #044b79;
+      .expertimage {
+        .el-image {
+          width: 90px;
+          height: 90px;
+          border-radius: 90px;
+        }
+      }
+      .rightInfo {
+        padding: 0 10px;
+        .name {
+          font-size: 18px;
+          padding: 7px 0 0 0;
+          font-weight: bold;
+        }
+        .school {
+          font-size: 16px;
+          padding: 7px 0 0 0;
+        }
+        .edu {
+          font-size: 16px;
+          padding: 7px 0 0 0;
+        }
+        .company {
+          font-size: 16px;
+          padding: 7px 0 0 0;
+        }
+      }
+    }
+    .list:nth-child(7) {
+      border-bottom: none;
+    }
+    .list:nth-child(8) {
+      border-bottom: none;
+    }
+    .list:hover {
+      cursor: pointer;
+      .name {
+        color: #0085d2;
+        font-weight: bold;
+      }
+    }
+  }
+}
+</style>

+ 93 - 0
src/views/market/index/patent.vue

@@ -0,0 +1,93 @@
+<template>
+  <div id="patent">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-col :span="24" class="top">
+          <top :topInfo="topInfo" @moreBtn="moreBtn"></top>
+        </el-col>
+        <el-col :span="24" class="down">
+          <el-col :span="8" class="list" v-for="(item, index) in list" :key="index" @click.native="toDetail(item.id)">
+            <el-image :src="imgUrl"></el-image>
+            <p class="textOver">{{ item.name }}</p>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import top from './top.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'patent',
+  props: {
+    list: { type: Array },
+  },
+  components: {
+    top,
+  },
+  data: function() {
+    return {
+      topInfo: {
+        title: 'e专利',
+        engTitle: 'patent',
+      },
+      imgUrl: require('@common/src/assets/fmzl1.jpg'),
+    };
+  },
+  created() {},
+  methods: {
+    // 查看更多
+    moreBtn() {
+      this.$router.push({ path: '/market/list', query: { index: 1 } });
+    },
+    toDetail(id) {
+      this.$router.push({ path: '/market/list', query: { index: 1, id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .top {
+    height: 50px;
+    overflow: hidden;
+  }
+  .down {
+    height: 450px;
+    position: relative;
+    .list {
+      width: 32%;
+      height: 220px;
+      margin: 0 10px 10px 0;
+      box-shadow: 0 0 5px #ccc;
+      border-radius: 5px;
+      padding: 5px;
+      .el-image {
+        width: 100%;
+        height: 185px;
+      }
+      p {
+        font-size: 16px;
+        font-weight: bold;
+        text-align: center;
+      }
+    }
+    .list:nth-child(3n) {
+      margin: 0 0 10px 0;
+    }
+    .list:hover {
+      cursor: pointer;
+      p {
+        color: #409eff;
+      }
+    }
+  }
+}
+</style>

+ 114 - 0
src/views/market/index/roadshow.vue

@@ -0,0 +1,114 @@
+<template>
+  <div id="roadshow">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-col :span="24" class="top">
+          <top :topInfo="topInfo" @moreBtn="moreBtn"></top>
+        </el-col>
+        <el-col :span="24" class="down">
+          <el-col :span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="toDetail(item.id)">
+            <el-col :span="4" class="date">
+              <span>{{ item.publish_time || '暂无' }}</span>
+            </el-col>
+            <el-col :span="20" class="title textOver">
+              {{ item.title }}
+            </el-col>
+            <el-col :span="24" class="brief">
+              {{ item.titlejj || '暂无' }}
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import top from './top.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'roadshow',
+  props: {
+    list: { type: Array },
+  },
+  components: {
+    top,
+  },
+  data: function() {
+    return {
+      topInfo: {
+        title: '项目路演',
+        engTitle: 'roadshow',
+      },
+    };
+  },
+  created() {},
+  methods: {
+    // 查看更多
+    moreBtn() {
+      this.$router.push({ path: '/market/list', query: { index: 5 } });
+    },
+    toDetail(id) {
+      this.$router.push({ path: '/market/list', query: { index: 5, id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .top {
+    height: 50px;
+    overflow: hidden;
+  }
+  .down {
+    height: 450px;
+    position: relative;
+    .list {
+      border-bottom: 1px dashed #000;
+      border-bottom: 1px dashed #000;
+      padding: 12px 0;
+      .date {
+        text-align: center;
+        span {
+          background: #044b79;
+          padding: 2px 5px 4px 5px;
+          font-size: 14px;
+          border-radius: 5px;
+          font-weight: bold;
+          color: #fff;
+        }
+      }
+      .title {
+        font-size: 16px;
+        font-weight: bold;
+        padding: 0 0 0 5px;
+      }
+      .brief {
+        font-size: 14px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        -webkit-line-clamp: 2;
+        word-break: break-all;
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+        padding: 5px 0 0 0;
+        height: 43px;
+      }
+    }
+    .list:last-child {
+      border-bottom: none;
+    }
+    .list:hover {
+      cursor: pointer;
+      .title {
+        color: #409eff;
+      }
+    }
+  }
+}
+</style>

+ 101 - 0
src/views/market/index/technology.vue

@@ -0,0 +1,101 @@
+<template>
+  <div id="technology">
+    <el-row>
+      <el-col :span="24" class="style">
+        <el-col :span="24" class="top">
+          <top :topInfo="topInfo" @moreBtn="moreBtn"></top>
+        </el-col>
+        <el-col :span="24" class="down">
+          <el-col :span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="toDetail(item.id)">
+            <el-col :span="19" class="name textOver">
+              {{ item.name }}
+            </el-col>
+            <el-col :span="5" class="date">
+              {{ getDate(item.create_time) }}
+            </el-col>
+            <el-col :span="12" class="field"> 所属领域:{{ item.field }} </el-col>
+            <el-col :span="12" class="field"> 合作方式:{{ item.cooperation }} </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import top from './top.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+var moment = require('moment');
+export default {
+  name: 'technology',
+  props: {
+    list: { type: Array },
+  },
+  components: {
+    top,
+  },
+  data: function() {
+    return {
+      topInfo: {
+        title: '科技需求',
+        engTitle: 'technology',
+      },
+    };
+  },
+  created() {},
+  methods: {
+    // 查看更多
+    moreBtn() {
+      this.$router.push({ path: '/market/list', query: { index: 4 } });
+    },
+    toDetail(id) {
+      this.$router.push({ path: '/market/list', query: { index: 4, id } });
+    },
+    // 整理时间
+    getDate(date) {
+      let newsDate = moment(date).format('YYYY-MM-DD');
+      if (newsDate) return newsDate;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.style {
+  .top {
+    height: 50px;
+    overflow: hidden;
+  }
+  .down {
+    height: 450px;
+    position: relative;
+    .list {
+      padding: 10px 0;
+      border-bottom: 1px dashed #ccc;
+      .name {
+        font-size: 18px;
+        font-weight: bold;
+      }
+      .date {
+        text-align: center;
+        font-size: 16px;
+      }
+      .field {
+        padding: 10px 0 0 0;
+        font-size: 15px;
+      }
+    }
+    .list:hover {
+      cursor: pointer;
+      .name {
+        color: #0085d2;
+        font-weight: bold;
+      }
+    }
+  }
+}
+</style>

+ 77 - 0
src/views/market/index/top.vue

@@ -0,0 +1,77 @@
+<template>
+  <div id="topInfo">
+    <el-row>
+      <el-col :span="24" class="top">
+        <el-col :span="2" class="topImg">
+          <el-image :src="iconImage"></el-image>
+        </el-col>
+        <el-col :span="22" class="topTxt">
+          <span>{{ topInfo.title }}</span>
+          <span>{{ topInfo.engTitle }}</span>
+          <span @click="moreBtn">更多</span>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  name: 'topInfo',
+  props: {
+    topInfo: { typs: Object },
+  },
+  components: {},
+  data: function() {
+    return {
+      iconImage: require('@common/src/assets/square_big.png'),
+    };
+  },
+  created() {},
+  methods: {
+    moreBtn() {
+      this.$emit('moreBtn');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  watch: {},
+};
+</script>
+
+<style lang="less" scoped>
+.top {
+  height: 50px;
+  .topTxt {
+    height: 45px;
+    border-bottom: 3px solid #044b79;
+    line-height: 45px;
+    padding: 0 5px;
+    span:nth-child(1) {
+      font-size: 24px;
+      font-weight: 700;
+    }
+    span:nth-child(2) {
+      padding: 0 10px;
+      text-transform: Capitalize;
+      font-size: 18px;
+      color: #92959a;
+      font-weight: 700;
+    }
+    span:nth-child(3) {
+      float: right;
+      font-size: 16px;
+      font-weight: bold;
+    }
+    span:nth-child(3):hover {
+      cursor: pointer;
+      color: #409eff;
+    }
+  }
+}
+</style>

+ 129 - 0
src/views/market/list.vue

@@ -0,0 +1,129 @@
+<template>
+  <div id="list">
+    <el-row>
+      <el-col :span="24" class="main">
+        <div class="w_1200">
+          <el-col :span="5" class="menu">
+            <el-image :src="squareImage"></el-image>
+            <span class="menuTitle">Menu</span>
+            <el-col :span="24" class="menuList" v-for="(item, index) in menuList" :key="index">
+              <p @click="changeMenu(item.component, index)" :style="`color:${menuIndex == index ? menuColor : ''}`">{{ item.name }}</p>
+            </el-col>
+          </el-col>
+          <el-col :span="19" class="listInfo">
+            <component :is="component" v-bind="params"></component>
+            <!-- <keep-alive>
+              <component :is="component" v-bind="params"></component>
+            </keep-alive> -->
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list',
+  props: {},
+  components: {
+    achieve: () => import('./list/achieve.vue'),
+    patent: () => import('./list/patent.vue'),
+    business: () => import('./list/business.vue'),
+    technolgy: () => import('./list/technolgy.vue'),
+    expert: () => import('./list/expert.vue'),
+    roadshow: () => import('./list/roadshow.vue'),
+  },
+  data: function() {
+    return {
+      component: '',
+      menuList: [
+        { name: '技术成果', component: 'achieve', options: { useTab: true, listModel: 1 } },
+        { name: 'e专利', component: 'patent', options: { useTab: false, listModel: 2 } },
+        { name: '商务服务', component: 'business', options: { useTab: false, listModel: 3 } },
+        { name: '专家智库', component: 'expert', options: { useTab: true, listModel: 4 } },
+        { name: '科技需求', component: 'technolgy', options: { useTab: false, listModel: 3 } },
+        { name: '项目路演', component: 'roadshow', options: { useTab: false, listModel: 5 } },
+      ],
+      squareImage: require('@p/live/square_big.png'),
+      column_name: '',
+      params: {},
+      menuColor: 'rgb(254, 149, 14)',
+    };
+  },
+  created() {},
+  methods: {
+    changeMenu(component, index) {
+      if (index !== this.menuIndex) this.$router.push({ path: './list', query: { index } });
+      this.component = component;
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    menuIndex() {
+      const index = this.$route.query.index || 0;
+      const obj = this.menuList[index];
+      const params = {
+        title: _.get(obj, 'name'),
+        ..._.get(obj, 'options'),
+      };
+      this.$set(this, 'component', _.get(obj, 'component', 'achieve'));
+      this.$set(this, 'params', params);
+      return index;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  min-height: 500px;
+  padding: 10px 0;
+  .menu {
+    height: 600px;
+    overflow: hidden;
+    padding: 15px 10px;
+    background: no-repeat 100% 100%;
+    background-image: url('~@p/live/menu_back.jpg');
+    box-sizing: border-box;
+    box-shadow: 0 0 10px #bbbaba;
+    .menuTitle {
+      font-size: 24px;
+      color: #92959a;
+      font-weight: bold;
+      position: relative;
+      top: -10px;
+      left: 10px;
+    }
+    .menuList {
+      height: 60px;
+      line-height: 60px;
+      border-bottom: 1px solid #2d64b3;
+      p {
+        font-weight: bold;
+        font-size: 18px;
+        color: #044b79;
+      }
+    }
+    .menuList:hover {
+      cursor: pointer;
+    }
+  }
+  .listInfo {
+    float: right;
+    width: 78%;
+    min-height: 600px;
+    overflow: hidden;
+    box-shadow: 0 0 10px #2d64b3;
+    padding: 10px;
+  }
+}
+</style>

+ 0 - 0
src/views/market/list/achieve.vue


Some files were not shown because too many files changed in this diff