guhongwei vor 3 Jahren
Commit
5fa21e70cb
100 geänderte Dateien mit 6354 neuen und 0 gelöschten Zeilen
  1. 3 0
      .env
  2. 33 0
      .eslintrc.js
  3. 21 0
      .gitignore
  4. 0 0
      README.md
  5. 3 0
      babel.config.js
  6. 65 0
      package.json
  7. BIN
      public/favicon.ico
  8. 18 0
      public/index.html
  9. 17 0
      src/App.vue
  10. BIN
      src/assets/expert.png
  11. BIN
      src/assets/fmzl.jpg
  12. BIN
      src/assets/icon.jpg
  13. BIN
      src/assets/jnpx.jpg
  14. BIN
      src/assets/kjzx.jpg
  15. BIN
      src/assets/logo.png
  16. BIN
      src/assets/noimage.jpg
  17. BIN
      src/assets/test1.jpg
  18. 69 0
      src/components/c-select.vue
  19. 88 0
      src/components/upload.vue
  20. 61 0
      src/components/vcheckbox.vue
  21. 61 0
      src/components/vselect.vue
  22. 47 0
      src/layout/common/page.vue
  23. 99 0
      src/layout/common/top.vue
  24. 38 0
      src/layout/mobile-frame/foot.vue
  25. 115 0
      src/layout/mobile-frame/mobile-main.vue
  26. 43 0
      src/layout/mobile-frame/page.vue
  27. 9 0
      src/layout/mobile-frame/top.md
  28. 123 0
      src/layout/mobile-frame/top.vue
  29. 26 0
      src/main.js
  30. 19 0
      src/plugins/axios.js
  31. 38 0
      src/plugins/check-res.js
  32. 8 0
      src/plugins/components.js
  33. 5 0
      src/plugins/element.js
  34. 6 0
      src/plugins/filters.js
  35. 27 0
      src/plugins/loading.js
  36. 4 0
      src/plugins/meta.js
  37. 33 0
      src/plugins/methods.js
  38. 20 0
      src/plugins/setting.js
  39. 65 0
      src/plugins/stomp.js
  40. 5 0
      src/plugins/vant.js
  41. 25 0
      src/plugins/var.js
  42. 177 0
      src/router/index.js
  43. 56 0
      src/store/adminLogin.js
  44. 83 0
      src/store/index.js
  45. 25 0
      src/store/upload.js
  46. 117 0
      src/util/axios-wrapper.js
  47. 10 0
      src/util/filters.js
  48. 50 0
      src/util/methods-util.js
  49. 69 0
      src/util/user-util.js
  50. 93 0
      src/views/index.vue
  51. 60 0
      src/views/live/channel/detail.vue
  52. 73 0
      src/views/live/channel/index.vue
  53. 110 0
      src/views/live/onAchieve/detail.vue
  54. 91 0
      src/views/live/onAchieve/index.vue
  55. 270 0
      src/views/live/onAchieve/parts/chat_1.vue
  56. 109 0
      src/views/live/onAchieve/parts/imgtext_1.vue
  57. 94 0
      src/views/live/onAchieve/parts/project_1.vue
  58. 103 0
      src/views/live/onAchieve/parts/readshow_1.vue
  59. 79 0
      src/views/live/onAchieve/parts/video_1.vue
  60. 117 0
      src/views/live/parts/detail_1.vue
  61. 207 0
      src/views/live/parts/detail_2.vue
  62. 82 0
      src/views/live/parts/list_1.vue
  63. 89 0
      src/views/live/parts/list_2.vue
  64. 101 0
      src/views/live/parts/list_3.vue
  65. 82 0
      src/views/live/parts/list_4.vue
  66. 148 0
      src/views/live/parts/list_5.vue
  67. 60 0
      src/views/live/roadshow/detail.vue
  68. 62 0
      src/views/live/roadshow/index.vue
  69. 82 0
      src/views/live/trainInter/index.vue
  70. 60 0
      src/views/live/trainInter/oneDetail.vue
  71. 270 0
      src/views/live/trainInter/parts/chat_1.vue
  72. 135 0
      src/views/live/trainInter/parts/video_1.vue
  73. 89 0
      src/views/live/trainInter/twoDetail.vue
  74. 60 0
      src/views/market/achieve/detail.vue
  75. 64 0
      src/views/market/achieve/index.vue
  76. 60 0
      src/views/market/expert/detail.vue
  77. 62 0
      src/views/market/expert/index.vue
  78. 86 0
      src/views/market/parts/detail_1.vue
  79. 78 0
      src/views/market/parts/detail_2.vue
  80. 84 0
      src/views/market/parts/detail_3.vue
  81. 133 0
      src/views/market/parts/detail_4.vue
  82. 54 0
      src/views/market/parts/detail_5.vue
  83. 85 0
      src/views/market/parts/list_1.vue
  84. 82 0
      src/views/market/parts/list_2.vue
  85. 85 0
      src/views/market/parts/list_3.vue
  86. 102 0
      src/views/market/parts/list_4.vue
  87. 85 0
      src/views/market/parts/list_5.vue
  88. 60 0
      src/views/market/patent/detail.vue
  89. 62 0
      src/views/market/patent/index.vue
  90. 60 0
      src/views/market/service/detail.vue
  91. 64 0
      src/views/market/service/index.vue
  92. 60 0
      src/views/market/techol/detail.vue
  93. 64 0
      src/views/market/techol/index.vue
  94. 36 0
      src/views/user/account/index.vue
  95. 60 0
      src/views/user/news/detail.vue
  96. 62 0
      src/views/user/news/index.vue
  97. 60 0
      src/views/user/notice/detail.vue
  98. 74 0
      src/views/user/notice/index.vue
  99. 125 0
      src/views/user/parts/detail_2.vue
  100. 0 0
      src/views/user/parts/detail_3.vue

+ 3 - 0
.env

@@ -0,0 +1,3 @@
+VUE_APP_AXIOS_BASE_URL = ''
+VUE_APP_ROUTER="/platmobile"
+VUE_APP_LIVE_URL='http://101.36.174.25:8888/live?port=1935&app=live&stream='

+ 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: 500,
+      },
+    ],
+    'no-unused-vars': 'off',
+    'no-console': 'off',
+    'prettier/prettier': [
+      'warn',
+      {
+        singleQuote: true,
+        trailingComma: 'es5',
+        bracketSpacing: true,
+        jsxBracketSameLine: true,
+        printWidth: 160,
+      },
+    ],
+  },
+  parserOptions: {
+    parser: 'babel-eslint',
+  },
+};

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+package-lock.json
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 0 - 0
README.md


+ 3 - 0
babel.config.js

@@ -0,0 +1,3 @@
+module.exports = {
+  presets: ["@vue/cli-plugin-babel/preset"]
+};

+ 65 - 0
package.json

@@ -0,0 +1,65 @@
+{
+  "name": "",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "@stomp/stompjs": "^5.4.4",
+    "axios": "^0.19.2",
+    "core-js": "^3.6.4",
+    "element-ui": "^2.13.2",
+    "flv.js": "^1.5.0",
+    "jsonwebtoken": "^8.5.1",
+    "loadsh": "0.0.4",
+    "lodash": "^4.17.15",
+    "moment": "^2.26.0",
+    "naf-core": "^0.1.2",
+    "trtc-js-sdk": "^4.4.0",
+    "vant": "^2.8.5",
+    "vue": "^2.6.11",
+    "vue-meta": "^2.3.4",
+    "vue-router": "^3.3.2",
+    "vue-video-player": "^5.0.2",
+    "vuex": "^3.1.3"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.3.0",
+    "@vue/cli-plugin-eslint": "~4.3.0",
+    "@vue/cli-plugin-router": "~4.3.0",
+    "@vue/cli-plugin-vuex": "~4.3.0",
+    "@vue/cli-service": "~4.3.0",
+    "@vue/eslint-config-prettier": "^6.0.0",
+    "babel-eslint": "^10.1.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-prettier": "^3.1.1",
+    "eslint-plugin-vue": "^6.2.2",
+    "less": "^3.11.2",
+    "less-loader": "^5.0.0",
+    "prettier": "^1.19.1",
+    "vue-template-compiler": "^2.6.11"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/essential",
+      "eslint:recommended",
+      "@vue/prettier"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {}
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

BIN
public/favicon.ico


+ 18 - 0
public/index.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <script src="/platmobile/ckplayer/ckplayer/ckplayer.js"></script>
+    <title>加载中...</title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 17 - 0
src/App.vue

@@ -0,0 +1,17 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<style lang="less">
+p {
+  padding: 0;
+  margin: 0;
+}
+.textOver {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>

BIN
src/assets/expert.png


BIN
src/assets/fmzl.jpg


BIN
src/assets/icon.jpg


BIN
src/assets/jnpx.jpg


BIN
src/assets/kjzx.jpg


BIN
src/assets/logo.png


BIN
src/assets/noimage.jpg


BIN
src/assets/test1.jpg


+ 69 - 0
src/components/c-select.vue

@@ -0,0 +1,69 @@
+<template>
+  <div id="c-select">
+    <van-cell :title="label" @click="show = true">
+      <span v-if="mval" style="color:#000">{{ mval }}</span>
+      <span v-else>{{ `请选择${label}` }}</span>
+    </van-cell>
+    <van-popup v-model="show" show-toolbar position="bottom">
+      <van-picker v-if="type === 'select'" :value-key="valueKey" :title="label" show-toolbar :columns="list" @confirm="onConfirm" @cancel="show = false" />
+      <van-checkbox-group v-model="multi" v-else>
+        <van-picker :title="label" :show-toolbar="true" :columns="list" @confirm="onConfirm" @cancel="show = false">
+          <template #option="item">
+            <van-checkbox :name="item">{{ item }}</van-checkbox>
+          </template>
+        </van-picker>
+      </van-checkbox-group>
+    </van-popup>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+export default {
+  name: 'c-select',
+  props: {
+    label: { type: String },
+    mval: { type: String },
+    list: { type: Array, default: () => [] },
+    type: { type: String },
+    valueKey: { type: String },
+  },
+  model: {
+    prop: 'mval',
+    event: 'change',
+  },
+  components: {},
+  data: function() {
+    return {
+      show: false,
+      selectList: [],
+      multi: [],
+    };
+  },
+  created() {},
+  methods: {
+    onConfirm(value) {
+      if (this.type === 'select') this.$emit('change', value);
+      else {
+        const str = this.multi.join(',');
+        this.$emit('change', str);
+      }
+      this.show = false;
+    },
+  },
+  computed: {
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+/deep/.van-cell__value {
+  text-align: left;
+}
+</style>

+ 88 - 0
src/components/upload.vue

@@ -0,0 +1,88 @@
+<template>
+  <div id="upload">
+    <el-upload
+      v-if="url"
+      ref="upload"
+      :action="url"
+      :limit="limit"
+      :on-exceed="outLimit"
+      :on-preview="handlePictureCardPreview"
+      :before-remove="handleRemove"
+      :on-success="onSuccess"
+      :show-file-list="false"
+    >
+      <el-avatar :size="80" fit="fill" :src="`${fileUrl}?${new Date().getTime()}`"></el-avatar>
+    </el-upload>
+    <el-dialog :visible.sync="dialogVisible">
+      <img width="100%" :src="dialogImageUrl" alt="" />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'upload',
+  props: {
+    url: { type: null },
+    limit: { type: Number },
+    data: { type: null },
+    type: { type: String },
+  },
+  components: {},
+  data: () => ({
+    dialogVisible: false,
+    dialogImageUrl: '',
+    fileList: [],
+    fileUrl: '',
+  }),
+  created() {
+    if (this.data) {
+      this.defalutProcess(this.data);
+    }
+  },
+  watch: {
+    data: {
+      handler(val) {
+        this.defalutProcess(val);
+      },
+    },
+  },
+  computed: {},
+  methods: {
+    handlePictureCardPreview(file) {
+      this.dialogImageUrl = file.url;
+      this.dialogVisible = true;
+    },
+    handleRemove(file) {
+      return true;
+    },
+    outLimit() {
+      this.$message.error('只允许上传1张头像');
+    },
+    onSuccess(response, file, fileList) {
+      this.fileUrl = response.uri;
+      //将文件整理好传回父组件
+      this.$emit('upload', { type: this.type, data: response });
+    },
+    defalutProcess(val) {
+      this.fileUrl = this.data;
+      this.$set(this, `fileList`, [{ name: this.type, url: this.data }]);
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+/deep/.el-upload-list__item {
+  width: 5rem;
+  height: 5rem;
+}
+/deep/.el-upload.el-upload--picture-card {
+  width: 5rem;
+  height: 5rem;
+  line-height: 5rem;
+}
+/deep/.el-avatar img {
+  width: 100% !important;
+}
+</style>

+ 61 - 0
src/components/vcheckbox.vue

@@ -0,0 +1,61 @@
+<template>
+  <div id="vcheckbox">
+    <van-field :value="display" :label="label" :placeholder="`请选择${label}`" readonly @click="show = true" />
+    <van-popup v-model="show" show-toolbar position="bottom">
+      <van-picker :title="label" show-toolbar :value-key="labelKey" :columns="list" @confirm="onConfirm" @cancel="show = false" />
+    </van-popup>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'vcheckbox',
+  props: {
+    label: { type: String }, //字段中文
+    mval: { type: String }, //v-model
+    list: { type: Array }, //选择的列表
+    labelKey: { type: String, default: 'name' }, //如果选项为object,选择哪个字段作为显示的选项
+    prop: { type: String }, // 如果选项为object,选择哪个字段作为v-model的值,没有的话就把该项作为值给v-model
+  },
+  model: {
+    prop: 'mval',
+    event: 'change',
+  },
+  components: {},
+  data: function () {
+    return {
+      show: false,
+    };
+  },
+  created() {},
+  methods: {
+    onConfirm(value) {
+      if (this.prop) this.$emit('change', _.get(value, this.prop));
+      else this.$emit('change', value);
+      this.show = false;
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    display() {
+      let value = '';
+      if (this.mval) value = this.mval;
+      if (this.prop) {
+        const r = this.list.find((f) => f[this.prop] === value);
+        if (r) value = r[this.labelKey];
+      }
+      return value;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 61 - 0
src/components/vselect.vue

@@ -0,0 +1,61 @@
+<template>
+  <div id="vselect">
+    <van-field :value="display" :label="label" :placeholder="`请选择${label}`" readonly @click="show = true" />
+    <van-popup v-model="show" show-toolbar position="bottom">
+      <van-picker :title="label" show-toolbar :value-key="labelKey" :columns="list" @confirm="onConfirm" @cancel="show = false" />
+    </van-popup>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'vselect',
+  props: {
+    label: { type: String }, //字段中文
+    mval: { type: String }, //v-model
+    list: { type: Array }, //选择的列表
+    labelKey: { type: String, default: 'name' }, //如果选项为object,选择哪个字段作为显示的选项
+    prop: { type: String }, // 如果选项为object,选择哪个字段作为v-model的值,没有的话就把该项作为值给v-model
+  },
+  model: {
+    prop: 'mval',
+    event: 'change',
+  },
+  components: {},
+  data: function() {
+    return {
+      show: false,
+    };
+  },
+  created() {},
+  methods: {
+    onConfirm(value) {
+      if (this.prop) this.$emit('change', _.get(value, this.prop));
+      else this.$emit('change', value);
+      this.show = false;
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    display() {
+      let value = '';
+      if (this.mval) value = this.mval;
+      if (this.prop) {
+        const r = this.list.find(f => f[this.prop] === value);
+        if (r) value = r[this.labelKey];
+      }
+      return value;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 47 - 0
src/layout/common/page.vue

@@ -0,0 +1,47 @@
+<template>
+  <div id="page">
+    <el-row>
+      <el-col :span="24" class="main">
+        <van-pagination v-model="currentPage" @change="changePage" :total-items="total" :items-per-page="limit" :show-page-size="5" force-ellipses />
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'page',
+  props: {
+    total: { type: Number },
+    limit: { type: String, default: () => '6' },
+  },
+  components: {},
+  data: function() {
+    return {
+      currentPage: 1,
+    };
+  },
+  created() {},
+  methods: {
+    changePage(page) {
+      this.$emit('search', { skip: (page - 1) * this.limit });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 99 - 0
src/layout/common/top.vue

@@ -0,0 +1,99 @@
+<template>
+  <div id="top">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one" v-if="topType == '1'">
+          <van-search v-model="searchName" placeholder="请输入信息标题" @search="search" />
+        </el-col>
+        <el-col :span="24" class="two" v-else-if="topType == '2'">
+          <van-nav-bar :title="this.$route.meta.title" :left-arrow="leftArrow" @click-left="upBack">
+            <template #left>
+              <span v-if="leftArrow"><van-icon name="arrow-left" /><span style="color:#409eff">返回</span> </span></template
+            >
+          </van-nav-bar>
+        </el-col>
+        <el-col :span="24" class="thr" v-else-if="topType == '3'">
+          <el-col :span="4" class="back" @click.native="upBack"> <van-icon name="arrow-left" />返回 </el-col>
+          <el-col :span="20" class="search">
+            <van-search v-model="searchName" placeholder="请输入赠与人名称" @search="search" />
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'top',
+  props: {
+    topType: { type: String, default: () => '1' },
+    // 只有类型为2时,有用
+    leftArrow: { type: Boolean, default: () => true },
+  },
+  components: {},
+  data: function() {
+    return {
+      searchName: '',
+    };
+  },
+  created() {},
+  methods: {
+    // 查询
+    search() {
+      this.$emit('search', { searchName: this.searchName });
+    },
+    // 返回
+    upBack() {
+      this.$emit('upBack');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    /deep/.van-search {
+      padding: 3px 5px;
+    }
+  }
+  .two {
+    /deep/.van-nav-bar__content {
+      height: 40px;
+    }
+    /deep/.van-icon {
+      top: 2px;
+    }
+  }
+  .thr {
+    .back {
+      color: #409eff;
+      padding: 8px 0;
+      text-align: center;
+      .van-icon {
+        top: 3px;
+      }
+    }
+    .search {
+      /deep/.van-search {
+        padding: 2px 5px 2px 0px;
+      }
+    }
+  }
+}
+</style>

+ 38 - 0
src/layout/mobile-frame/foot.vue

@@ -0,0 +1,38 @@
+<template>
+  <div id="foot">
+    <van-tabbar route>
+      <van-tabbar-item v-for="(i, index) in menuList" :key="index" :to="i.index" :icon="i.icon">{{ i.name }}</van-tabbar-item>
+    </van-tabbar>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'foot',
+  props: {
+    menuList: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 115 - 0
src/layout/mobile-frame/mobile-main.vue

@@ -0,0 +1,115 @@
+<template>
+  <div id="mobile-main">
+    <van-row>
+      <van-col span="24" class="main" :style="{ height: client.height + 'px' }">
+        <van-col span="24" class="top" v-if="useTop">
+          <top :topType="topType" @search="topSearch" :leftArrow="leftArrow" @back="back" :rightArrow="rightArrow" @add="add">
+            <template v-slot:top>
+              <slot name="slotTop"></slot>
+            </template>
+          </top>
+        </van-col>
+        <van-col span="24" class="info" :style="{ height: getHeight() }">
+          <slot name="info"></slot>
+        </van-col>
+        <van-col span="24" class="page" v-if="usePage">
+          <page :limit="limit" :total="total" @search="search"></page>
+        </van-col>
+        <van-col span="24" class="foot" v-if="useNav"><foot :menuList="menuList"></foot></van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import top from './top.vue';
+import page from './page.vue';
+import foot from './foot.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'mobile-main',
+  props: {
+    // 头部
+    useTop: { type: Boolean, default: () => true },
+    topType: { type: String, default: () => '1' },
+    leftArrow: { type: Boolean, default: () => true },
+    rightArrow: { type: Boolean, default: () => true },
+    // 分页
+    usePage: { type: Boolean, default: () => true },
+    limit: { type: Number, default: () => 10 },
+    total: { type: Number, default: () => 0 },
+    // 底部菜单
+    useNav: { type: Boolean, default: () => true },
+  },
+  components: {
+    top,
+    page,
+    foot,
+  },
+  data: function() {
+    return {
+      client: {},
+      menuList: [
+        { name: '首页1', index: '/', icon: 'after-sale' },
+        { name: '首页2', index: '/index', icon: 'cart-o' },
+      ],
+    };
+  },
+  created() {},
+  methods: {
+    // 头部名称查询
+    topSearch(searchName) {
+      this.$emit('search', searchName);
+    },
+    // 分页查询
+    search(skip) {
+      this.$emit('search', skip);
+    },
+    // 返回
+    back() {
+      this.$emit('back');
+    },
+    // 添加
+    add() {
+      this.$emit('add');
+    },
+    // 计算中间高度
+    getHeight() {
+      let windowH = this.client.height;
+      if (this.useTop) windowH = windowH - 45;
+      else windowH = windowH - 0;
+      if (this.usePage) windowH = windowH - 40;
+      else windowH = windowH - 0;
+      if (this.useNav) windowH = windowH - 50;
+      else windowH = windowH - 0;
+      return windowH + 'px';
+    },
+  },
+  mounted() {
+    let client = {
+      height: document.documentElement.clientHeight || document.body.clientHeight,
+      width: document.documentElement.clientWidth || document.body.clientWidth,
+    };
+    this.$set(this, `client`, client);
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.info {
+  overflow-y: auto;
+}
+</style>

+ 43 - 0
src/layout/mobile-frame/page.vue

@@ -0,0 +1,43 @@
+<template>
+  <div id="page">
+    <van-pagination v-model="currentPage" @change="changePage" :total-items="total" :items-per-page="limit" :show-page-size="5" force-ellipses />
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'page',
+  props: {
+    total: { type: Number },
+    limit: { type: Number, default: () => 6 },
+  },
+  components: {},
+  data: function() {
+    return {
+      currentPage: 1,
+    };
+  },
+  created() {},
+  methods: {
+    changePage(page) {
+      this.$emit('search', { skip: (page - 1) * this.limit });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 9 - 0
src/layout/mobile-frame/top.md

@@ -0,0 +1,9 @@
+<!-- 头部类型 -->
+# topType
+## topType=="1" 只有查询 search
+
+## topType=="2" 带有返回,标题,添加,返回方法:back,添加方法:add
+
+## topType=="3" 带有返回和查询,返回方法:back,查询方法:search
+
+## topType=="4" 自定义 slotTop

+ 123 - 0
src/layout/mobile-frame/top.vue

@@ -0,0 +1,123 @@
+<template>
+  <div id="top">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="common type_1" v-if="topType == '1'">
+          <van-search v-model="searchName" placeholder="请输入信息标题" @search="search" />
+        </van-col>
+        <van-col span="24" class="common type_2" v-else-if="topType == '2'">
+          <van-nav-bar :title="this.$route.meta.title">
+            <template #left>
+              <van-col span="24" v-if="leftArrow" @click="back" class="leftArrow">
+                <van-icon name="arrow-left" />
+                <span>返回</span>
+              </van-col>
+            </template>
+            <template #right>
+              <van-col span="24" v-if="rightArrow" @click="add"><van-icon name="plus"/></van-col>
+            </template>
+          </van-nav-bar>
+        </van-col>
+        <van-col span="24" class="common type_3" v-else-if="topType == '3'">
+          <van-col span="4" class="left" @click.native="back">
+            <van-icon name="arrow-left" />
+            <span>返回</span>
+          </van-col>
+          <van-col span="20" class="right">
+            <van-search v-model="searchName" placeholder="请输入信息标题" @search="search" />
+          </van-col>
+        </van-col>
+        <van-col span="24" class="common type_4" v-else-if="topType == '4'">
+          <slot name="top"></slot>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'top',
+  props: {
+    topType: { typ: String },
+    leftArrow: { typ: Boolean },
+    rightArrow: { typ: Boolean },
+  },
+  components: {},
+  data: function() {
+    return {
+      searchName: '',
+    };
+  },
+  created() {},
+  methods: {
+    // 搜索
+    search() {
+      this.$emit('search', { searchName: this.searchName });
+    },
+    // 返回
+    back() {
+      this.$emit('back');
+    },
+    // 添加
+    add() {
+      this.$emit('add');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .common {
+    height: 45px;
+    overflow: hidden;
+    border-bottom: 1px solid #f1f1f1;
+  }
+  .type_1 {
+    padding: 5px;
+    /deep/.van-search {
+      padding: 0;
+    }
+  }
+  .type_2 {
+    .leftArrow {
+      span {
+        color: #409eff;
+        position: relative;
+        top: -2px;
+      }
+    }
+  }
+  .type_3 {
+    padding: 5px;
+    /deep/.van-search {
+      padding: 0;
+    }
+    .left {
+      color: #409eff;
+      font-size: 15px;
+      text-align: center;
+      padding: 8px 0;
+      span {
+        position: relative;
+        top: -2px;
+      }
+    }
+  }
+}
+</style>

+ 26 - 0
src/main.js

@@ -0,0 +1,26 @@
+import Vue from 'vue';
+import App from './App.vue';
+import router from './router';
+import store from './store';
+import '@/plugins/element.js';
+import '@/plugins/vant';
+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 '@/plugins/components';
+import InitStomp from '@/plugins/stomp';
+Vue.config.productionTip = false;
+new Vue({
+  router,
+  store,
+  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 });

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

@@ -0,0 +1,38 @@
+/* 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 { Notify } from 'vant';
+
+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) {
+          Notify({ type: 'success', message: _okText });
+        }
+        return true;
+      }
+      if (_.isFunction(_errText)) {
+        return _errText();
+      }
+      Notify({ type: 'danger', message: _okText });
+      return false;
+    };
+  },
+};
+
+Vue.use(Plugin);

+ 8 - 0
src/plugins/components.js

@@ -0,0 +1,8 @@
+import Vue from 'vue';
+import mobileMain from '@/layout/mobile-frame/mobile-main.vue';
+const Plugin = vue => {
+  vue.prototype.$dev_mode = process.env.NODE_ENV === 'development';
+  vue.component('mobileMain', mobileMain);
+};
+
+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://10.16.8.209:9005',
+};
+
+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);
+};

+ 5 - 0
src/plugins/vant.js

@@ -0,0 +1,5 @@
+import Vue from 'vue';
+import Vant from 'vant';
+import 'vant/lib/index.css';
+
+Vue.use(Vant);

+ 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);

+ 177 - 0
src/router/index.js

@@ -0,0 +1,177 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import store from '@/store/index';
+const jwt = require('jsonwebtoken');
+const originalPush = VueRouter.prototype.push;
+VueRouter.prototype.push = function push(location, onResolve, onReject) {
+  if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject);
+  return originalPush.call(this, location).catch(err => err);
+};
+Vue.use(VueRouter);
+const onLive = [
+  {
+    path: '/live/onAchieve/index',
+    meta: { title: '直播大厅-在线展会' },
+    component: () => import('../views/live/onAchieve/index.vue'),
+  },
+  {
+    path: '/live/onAchieve/detail',
+    meta: { title: '在线展会-详细信息' },
+    component: () => import('../views/live/onAchieve/detail.vue'),
+  },
+  {
+    path: '/live/channel/index',
+    meta: { title: '直播大厅-科技频道' },
+    component: () => import('../views/live/channel/index.vue'),
+  },
+  {
+    path: '/live/channel/detail',
+    meta: { title: '科技频道-详细信息' },
+    component: () => import('../views/live/channel/detail.vue'),
+  },
+  {
+    path: '/live/trainInter/index',
+    meta: { title: '直播大厅-培训访谈' },
+    component: () => import('../views/live/trainInter/index.vue'),
+  },
+  {
+    path: '/live/trainInter/oneDetail',
+    meta: { title: '嘉宾访谈-详细信息' },
+    component: () => import('../views/live/trainInter/oneDetail.vue'),
+  },
+  {
+    path: '/live/trainInter/twoDetail',
+    meta: { title: '技能培训-详细信息' },
+    component: () => import('../views/live/trainInter/twoDetail.vue'),
+  },
+  {
+    path: '/live/roadshow/index',
+    meta: { title: '直播大厅-项目路演' },
+    component: () => import('../views/live/roadshow/index.vue'),
+  },
+  {
+    path: '/live/roadshow/detail',
+    meta: { title: '项目路演-详细信息' },
+    component: () => import('../views/live/roadshow/detail.vue'),
+  },
+];
+const market = [
+  {
+    path: '/market/achieve/index',
+    meta: { title: '科技超市-技术成果' },
+    component: () => import('../views/market/achieve/index.vue'),
+  },
+  {
+    path: '/market/achieve/detail',
+    meta: { title: '技术成果-详细信息' },
+    component: () => import('../views/market/achieve/detail.vue'),
+  },
+  {
+    path: '/market/patent/index',
+    meta: { title: '科技超市-E-专利' },
+    component: () => import('../views/market/patent/index.vue'),
+  },
+  {
+    path: '/market/patent/detail',
+    meta: { title: 'E-专利-详细信息' },
+    component: () => import('../views/market/patent/detail.vue'),
+  },
+  {
+    path: '/market/techol/index',
+    meta: { title: '科技超市-科技需求' },
+    component: () => import('../views/market/techol/index.vue'),
+  },
+  {
+    path: '/market/techol/detail',
+    meta: { title: '科技需求-详细信息' },
+    component: () => import('../views/market/techol/detail.vue'),
+  },
+  {
+    path: '/market/expert/index',
+    meta: { title: '科技超市-专家智库' },
+    component: () => import('../views/market/expert/index.vue'),
+  },
+  {
+    path: '/market/expert/detail',
+    meta: { title: '专家智库-详细信息' },
+    component: () => import('../views/market/expert/detail.vue'),
+  },
+  {
+    path: '/market/service/index',
+    meta: { title: '科技超市-在线服务' },
+    component: () => import('../views/market/service/index.vue'),
+  },
+  {
+    path: '/market/service/detail',
+    meta: { title: '在线服务-详细信息' },
+    component: () => import('../views/market/service/detail.vue'),
+  },
+];
+const user = [
+  {
+    path: '/user/question/index',
+    meta: { title: '个人中心-调查问卷' },
+    component: () => import('../views/user/question/index.vue'),
+  },
+  {
+    path: '/user/notice/index',
+    meta: { title: '个人中心-通知通告' },
+    component: () => import('../views/user/notice/index.vue'),
+  },
+  {
+    path: '/user/notice/detail',
+    meta: { title: '通知通告-详细信息' },
+    component: () => import('../views/user/notice/detail.vue'),
+  },
+  {
+    path: '/user/news/index',
+    meta: { title: '个人中心-新闻资讯' },
+    component: () => import('../views/user/news/index.vue'),
+  },
+  {
+    path: '/user/news/detail',
+    meta: { title: '新闻资讯-详细信息' },
+    component: () => import('../views/user/news/detail.vue'),
+  },
+  {
+    path: '/user/account/index',
+    meta: { title: '个人中心-账号管理' },
+    component: () => import('../views/user/account/index.vue'),
+  },
+];
+const routes = [
+  {
+    path: '/',
+    meta: { title: '首页' },
+    component: () => import('../views/index.vue'),
+  },
+  ...onLive,
+  ...market,
+  ...user,
+];
+
+const router = new VueRouter({
+  mode: 'history',
+  base: process.env.NODE_ENV === 'development' ? '' : process.env.VUE_APP_ROUTER,
+  routes,
+});
+// router.beforeEach((to, from, next) => {
+//   document.title = `${to.meta.title} `;
+//   let token = localStorage.getItem('token');
+//   console.log(token);
+//   if (to.path == '/account/index') {
+//     if (token == null) {
+//       next('/login');
+//     } else {
+//       let user = jwt.decode(token);
+//       store.commit('setUser', user, { root: true });
+//       next();
+//     }
+//   } else {
+//     let user = jwt.decode(token);
+//     store.commit('setUser', user, { root: true });
+//     next();
+//   }
+// });
+
+export default router;

+ 56 - 0
src/store/adminLogin.js

@@ -0,0 +1,56 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+const jwt = require('jsonwebtoken');
+Vue.use(Vuex);
+const api = {
+  adminLoginInfo: `/api/live/v0/users/admin`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async query({ commit }, { skip = 0, limit, ...info } = {}) {
+    const res = await this.$axios.$get(`${api.adminLoginInfo}`, {
+      skip,
+      limit,
+      ...info,
+    });
+    return res;
+  },
+  async create({ commit }, payload) {
+    const res = await this.$axios.$post(`${api.adminLoginInfo}`, payload);
+    return res;
+  },
+  async fetch({ commit }, payload) {
+    const res = await this.$axios.$get(`${api.adminLoginInfo}/${payload}`);
+    return res;
+  },
+  async update({ commit }, { id, ...data }) {
+    const res = await this.$axios.$post(`${api.adminLoginInfo}/update/${id}`, data);
+    return res;
+  },
+  async delete({ commit }, payload) {
+    const res = await this.$axios.$delete(`${api.adminLoginInfo}/${payload}`);
+    return res;
+  },
+  async login({ commit }, { user }) {
+    const res = await this.$axios.$post(`${api.adminLoginInfo}/login`, user);
+    if (res.errcode === 0) {
+      localStorage.setItem('token', res.data);
+      user = jwt.decode(res.data);
+      commit('setUser', user, { root: true });
+    }
+    return res;
+  },
+  async updatePwd({ commit }, { id, ...data }) {
+    const res = await this.$axios.$post(`${api.adminLoginInfo}/password/${id}`, data);
+    return res;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 83 - 0
src/store/index.js

@@ -0,0 +1,83 @@
+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 adminLogin from './adminLogin';
+// 个人
+import personal from '@common/src/store/personal';
+// 机构
+import organization from '@common/src/store/organization';
+// 专家
+import expert from '@common/src/store/expert';
+// // 展会
+import dock from '@common/src/store/dock/dock';
+// 展会图文
+import dockImgtxt from '@common/src/store/dock/dockImgtxt';
+// 公共聊天
+import dockChat from '@common/src/store/dock/dockChat';
+// 科技频道
+import channel from '@common/src/store/channel';
+import channelVideo from '@common/src/store/channelVideo';
+// 嘉宾访谈
+import interview from '@common/src/store/interview';
+// 培训问诊
+import trainLive from '@common/src/store/trainLive';
+import trainchat from '@common/src/store/trainchat';
+// 项目路演
+import roadShow from '@common/src/store/roadShow';
+// 产品
+import product from '@common/src/store/product';
+// 交易备案
+// import transaction from '@common/src/store/transaction';
+// e专利
+import patent from '@common/src/store/patent';
+// 技术新闻
+import column from '@common/src/store/column';
+import news from '@common/src/store/news';
+// 科技新闻
+import science from '@common/src/store/science';
+// 字典表
+import category from '@common/src/store/category';
+import code from '@common/src/store/code';
+import place from '@common/src/store/place';
+import upload from './upload';
+import questionnaire from '@common/src/store/question/questionnaire';
+import answer from '@common/src/store/question/answer';
+// 统计
+import statistics from '@common/src/store/statistics';
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+  state: { ...ustate },
+  mutations: { ...umutations },
+  actions: {},
+  modules: {
+    adminLogin,
+    personal,
+    organization,
+    expert,
+    dock,
+    dockImgtxt,
+    dockChat,
+    channel,
+    channelVideo,
+    interview,
+    trainLive,
+    trainchat,
+    roadShow,
+    product,
+    // transaction,
+    patent,
+    column,
+    news,
+    science,
+    category,
+    code,
+    place,
+    upload,
+    questionnaire,
+    answer,
+    statistics,
+  },
+});

+ 25 - 0
src/store/upload.js

@@ -0,0 +1,25 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import _ from 'lodash';
+import axios from 'axios';
+Vue.use(Vuex);
+const api = {
+  upload: dir => `/files/article/${dir}/upload`,
+};
+const state = () => ({});
+const mutations = {};
+
+const actions = {
+  async upload({ commit }, { file, dir }) {
+    var formdata = new FormData();
+    formdata.append('file', file, file.name);
+    const res = await axios.post(api.upload(dir), formdata, { headers: { 'Content-Type': 'multipart/form-data' } });
+    return res.data;
+  },
+};
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions,
+};

+ 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));
+  },
+};

+ 93 - 0
src/views/index.vue

@@ -0,0 +1,93 @@
+<template>
+  <div id="index">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-button v-for="(i, index) in menu" :key="`menu-${index}`" @click="$router.push(i.router)">{{ i.label }}</van-button>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      menu: [
+        {
+          label: '直播大厅-在线展会',
+          router: '/live/onAchieve/index',
+        },
+        {
+          label: '直播大厅-科技频道',
+          router: '/live/channel/index',
+        },
+        {
+          label: '直播大厅-培训访谈',
+          router: '/live/trainInter/index',
+        },
+        {
+          label: '直播大厅-项目路演',
+          router: '/live/roadshow/index',
+        },
+        {
+          label: '科技超市-技术成果',
+          router: '/market/achieve/index',
+        },
+        {
+          label: '科技超市-e专利',
+          router: '/market/patent/index',
+        },
+        {
+          label: '科技超市-科技需求',
+          router: '/market/techol/index',
+        },
+        {
+          label: '科技超市-专家智库',
+          router: '/market/expert/index',
+        },
+        {
+          label: '科技超市-在线服务',
+          router: '/market/service/index',
+        },
+        {
+          label: '个人中心-调查问卷',
+          router: '/user/question/index',
+        },
+        {
+          label: '个人中心-通知通告',
+          router: '/user/notice/index',
+        },
+        {
+          label: '个人中心-新闻资讯',
+          router: '/user/news/index',
+        },
+        {
+          label: '个人中心-账号管理',
+          router: '/user/account/index',
+        },
+      ],
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 60 - 0
src/views/live/channel/detail.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="detail">
+    <mobileMain :useNav="false" :usePage="false" topType="2" :rightArrow="false" @back="back">
+      <template v-slot:info>
+        <detail-frame :form="form"></detail-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import detailFrame from '../parts/detail_2.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: channel } = createNamespacedHelpers('channel');
+export default {
+  name: 'detail',
+  props: {},
+  components: {
+    detailFrame,
+  },
+  data: function() {
+    return {
+      form: {},
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...channel(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    back() {
+      this.$router.push({ path: '/live/channel/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 73 - 0
src/views/live/channel/index.vue

@@ -0,0 +1,73 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <list-frame :list="list"></list-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import listFrame from '../parts/list_3.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+import imgtext_1Vue from '../onAchieve/parts/imgtext_1.vue';
+const { mapActions: code } = createNamespacedHelpers('code');
+const { mapActions: channel } = createNamespacedHelpers('channel');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+  },
+  data: function() {
+    return {
+      list: [],
+      total: 0,
+      limit: 10,
+      typList: [],
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...channel(['query']),
+    ...code({ codeQuery: 'query' }),
+    async search({ skip = 0, limit = 5, searchName, ...info } = {}) {
+      if (searchName) info.title = searchName;
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        for (const val of res.data) {
+          this.searchType(val);
+        }
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 查询字典表
+    async searchType(data) {
+      let res = await this.codeQuery({ category: '04' });
+      if (this.$checkRes(res)) {
+        let typeInfo = res.data.find(i => i._id == data.type);
+        if (typeInfo) return (data.type = typeInfo.name);
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 110 - 0
src/views/live/onAchieve/detail.vue

@@ -0,0 +1,110 @@
+<template>
+  <div id="detail">
+    <mobileMain :useNav="false" :useTop="false" :usePage="false">
+      <template v-slot:info>
+        <van-col span="24" class="video">
+          <video-frame></video-frame>
+        </van-col>
+        <van-col span="24" class="down">
+          <van-tabs v-model="active">
+            <van-tab title="图文直播">
+              <van-col span="24" class="downInfo" :style="{ height: client.height - 250 + 'px' }">
+                <imgtext-frame></imgtext-frame>
+              </van-col>
+            </van-tab>
+            <van-tab title="公共聊天">
+              <van-col span="24" class="downInfo" :style="{ height: client.height - 250 + 'px' }">
+                <chat-frame></chat-frame>
+              </van-col>
+            </van-tab>
+            <van-tab title="参展项目">
+              <van-col span="24" class="downInfo" :style="{ height: client.height - 250 + 'px' }">
+                <project-frame></project-frame>
+              </van-col>
+            </van-tab>
+            <van-tab title="项目路演">
+              <van-col span="24" class="downInfo" :style="{ height: client.height - 250 + 'px' }">
+                <readshow-frame></readshow-frame>
+              </van-col>
+            </van-tab>
+          </van-tabs>
+        </van-col>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import videoFrame from './parts/video_1.vue';
+import imgtextFrame from './parts/imgtext_1.vue';
+import chatFrame from './parts/chat_1.vue';
+import projectFrame from './parts/project_1.vue';
+import readshowFrame from './parts/readshow_1.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: mapDock } = createNamespacedHelpers('dock');
+export default {
+  name: 'detail',
+  props: {},
+  components: {
+    videoFrame,
+    imgtextFrame,
+    chatFrame,
+    projectFrame,
+    readshowFrame,
+  },
+  data: function() {
+    return {
+      client: {},
+      dockInfo: {},
+      active: 0,
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...mapDock(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `dockInfo`, res.data);
+      }
+    },
+  },
+  mounted() {
+    let client = {
+      height: document.documentElement.clientHeight || document.body.clientHeight,
+      width: document.documentElement.clientWidth || document.body.clientWidth,
+    };
+    this.$set(this, `client`, client);
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.video {
+  height: 210px;
+  overflow: hidden;
+}
+.down {
+  .downInfo {
+    overflow-y: auto;
+  }
+}
+</style>

+ 91 - 0
src/views/live/onAchieve/index.vue

@@ -0,0 +1,91 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <van-tabs v-model="active" @click="changeActive">
+          <van-tab title="正在直播" name="1">
+            <list-frame :list="list" @detail="detail"></list-frame>
+          </van-tab>
+          <van-tab title="下期预告" name="0">
+            <list-frame :list="list" @detail="detail"></list-frame>
+          </van-tab>
+          <van-tab title="已往直播" name="-1">
+            <list-frame :list="list" @detail="detail"></list-frame>
+          </van-tab>
+        </van-tabs>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import listFrame from '../parts/list_2.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: mapDock } = createNamespacedHelpers('dock');
+const { mapActions: place } = createNamespacedHelpers('place');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+  },
+  data: function() {
+    return {
+      active: '1',
+      list: [],
+      total: 0,
+      limit: 5,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...mapDock(['query']),
+    ...place({ queryName: 'queryName' }),
+    async search({ skip = 0, limit = this.limit, searchName, ...info } = {}) {
+      info.status = this.active;
+      if (searchName) info.title = searchName;
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        for (const val of res.data) this.searchPlace(val);
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    changeActive(name) {
+      this.$set(this, `active`, name);
+      this.search();
+    },
+    // 查询省市
+    async searchPlace(data) {
+      let nameData = { code: [data.province, data.city] };
+      let res = await this.queryName(nameData);
+      if (this.$checkRes(res)) {
+        data.province = res.data.find(i => i.code == data.province).name;
+        data.city = res.data.find(i => i.code == data.city).name;
+        return data;
+      }
+    },
+    // 详情
+    detail(data) {
+      this.$router.push({ path: '/live/onAchieve/detail', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 270 - 0
src/views/live/onAchieve/parts/chat_1.vue

@@ -0,0 +1,270 @@
+<template>
+  <div id="chat_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(i, index) in list" :key="index">
+          <span v-if="!isEmotion(i.content)">
+            <van-col span="24" class="one">
+              [<span>{{ i.send_time | getTime }}</span
+              >]
+              <span>{{ i.sender_name }}</span>
+            </van-col>
+            <van-col span="24" class="two">
+              <span> {{ i.content }}</span>
+            </van-col>
+          </span>
+          <span v-else>
+            <van-col span="24" class="thr">
+              <span class="textOver">{{ i.sender_name }}</span>
+              <span>送出一朵/个</span>
+              <span v-html="i.content"></span>
+            </van-col>
+          </span>
+        </van-col>
+      </van-col>
+    </van-row>
+    <van-col span="24" class="sendtxt">
+      <van-button icon="chat" type="info" round @click="show = true" />
+    </van-col>
+    <van-popup v-model="show" position="bottom" class="popup">
+      <van-col span="24" class="one">
+        <p>快捷发言</p>
+        <p>
+          <span v-for="(i, index) in kjfyList" :key="index" @click="changekjfy(i.name)">{{ i.name }}</span>
+        </p>
+      </van-col>
+      <van-col span="24" class="two">
+        <van-col span="19">
+          <van-field v-model="text" placeholder="请输入内容" />
+        </van-col>
+        <van-col span="5" style="text-align:center;">
+          <van-button round type="info" @click="send()">发言</van-button>
+        </van-col>
+      </van-col>
+    </van-popup>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dockChat } = createNamespacedHelpers('dockChat');
+var moment = require('moment');
+import _ from 'lodash';
+export default {
+  name: 'chat_1',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      dock_id: '',
+      list: [],
+      // 发言
+      show: false,
+      text: '',
+      // 快捷发言列表
+      kjfyList: [{ name: '欢迎欢迎' }, { name: '科技创新' }, { name: '大咖云集' }],
+    };
+  },
+  async created() {
+    if (this.id) {
+      this.$set(this, `dock_id`, this.id);
+      await this.search();
+    }
+  },
+  mounted() {
+    this.channel();
+  },
+  methods: {
+    ...dockChat(['query', 'create']),
+    async search() {
+      let res = await this.query({ dock_id: this.dock_id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+      }
+    },
+    isEmotion(word) {
+      return word.startsWith('<img');
+    },
+    channel() {
+      this.$stomp({
+        [`/exchange/public_chat/${this.dock_id}`]: this.onMessage,
+      });
+    },
+    onMessage(message) {
+      let body = _.get(message, 'body');
+      if (body) {
+        body = JSON.parse(body);
+        this.list.push(body);
+        this.text = '';
+      }
+      this.search();
+    },
+    // 发言
+    async send() {
+      if (this.text != '') {
+        let data = {
+          dock_id: this.dock_id,
+          content: this.text,
+          sender_id: this.user.id || this.getData(),
+          sender_name: this.user.name || this.getData() + '游客',
+          send_time: moment().format('yyyy-MM-DD HH:mm:ss'),
+        };
+        let res = await this.create(data);
+        this.text = '';
+        this.show = false;
+        this.$checkRes(res, null, res.errmsg || '发言失败');
+      } else this.$message.error('请输入信息后发送');
+    },
+    // 选择快捷发言
+    changekjfy(data) {
+      this.$set(this, `text`, data);
+      this.send();
+    },
+    // 获取时间戳
+    getData() {
+      let date = moment(new Date()).valueOf();
+      if (date) return date;
+    },
+  },
+  filters: {
+    getTime(date) {
+      if (!date) return '很久以前';
+      let today = moment().format('YYYY-MM-DD');
+      let dd = moment(date).format('YYYY-MM-DD');
+      let time;
+      if (today == dd) time = moment(date).format('HH:mm');
+      else time = moment(date).format('YYYY-MM-DD');
+      return time;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 0 5px;
+  .list {
+    padding: 5px 0;
+    .one {
+      padding: 5px 0;
+      span:nth-child(1) {
+        color: #ff0000;
+      }
+      span:nth-child(2) {
+        font-size: 16px;
+        color: #999;
+      }
+    }
+    .two {
+      min-height: 30px;
+      span {
+        font-size: 15px;
+        background: #f1f1f1;
+        padding: 5px;
+        border-radius: 5px;
+        min-width: 30%;
+        display: inline-block;
+      }
+    }
+    .thr {
+      text-align: center;
+      background: #f1f1f1;
+      border-radius: 25px;
+      width: 80%;
+      margin: 0 10%;
+      padding: 5px 0;
+      span:nth-child(1) {
+        color: #db7093;
+        font-weight: bold;
+        position: relative;
+        top: -3px;
+        font-size: 15px;
+        display: inline-block;
+        width: 45%;
+      }
+      span:nth-child(2) {
+        color: #000000;
+        position: relative;
+        top: -7px;
+        font-size: 15px;
+      }
+      span:nth-child(3) {
+        position: relative;
+        top: 2px;
+        left: 5px;
+      }
+    }
+  }
+}
+// 发言
+.sendtxt {
+  position: fixed;
+  bottom: 10px;
+  right: 10px;
+  width: 100%;
+  z-index: 999;
+  text-align: right;
+}
+.popup {
+  .one {
+    color: #fff;
+    p:nth-child(1) {
+      text-align: center;
+      padding: 25px 0;
+      font-size: 20px;
+    }
+    p:nth-child(2) {
+      padding: 0 10px;
+      text-align: center;
+      span {
+        background-color: #409eff9f;
+        display: inline-block;
+        border-radius: 25px;
+        margin: 0 10px 10px 0;
+        font-size: 15px;
+        padding: 5px 10px;
+      }
+      span:nth-child(4n) {
+        margin: 0 0 10px 0;
+      }
+    }
+  }
+  .two {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    background: #fff;
+    padding: 5px 5px;
+    height: 40px;
+    /deep/.van-cell {
+      padding: 3px 5px;
+      border-radius: 5px;
+      border: 1px solid #ccc;
+      width: 98%;
+    }
+    /deep/.van-button {
+      height: 31px;
+    }
+  }
+}
+/deep/.van-popup {
+  height: 30%;
+  background-color: #0000005f !important;
+}
+</style>

+ 109 - 0
src/views/live/onAchieve/parts/imgtext_1.vue

@@ -0,0 +1,109 @@
+<template>
+  <div id="imgtext_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(item, index) in list" :key="index">
+          <van-col span="24" class="txt">
+            <span> <van-icon name="underway" />{{ getDate(item.meta.createdAt) }}</span>
+            <span>展会负责人</span>
+          </van-col>
+          <van-col span="24" class="brief">
+            {{ item.content }}
+          </van-col>
+          <van-col span="24" class="image">
+            <el-image :src="item.img_url" class="imagelist"></el-image>
+          </van-col>
+          <van-col span="24" class="video" v-if="item.file_url">
+            <el-link :href="item.file_url" target="_blank" :underline="false">视频播放</el-link>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: dockImgtxt } = createNamespacedHelpers('dockImgtxt');
+import moment from 'moment';
+export default {
+  name: 'imgtext_1',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      list: [],
+    };
+  },
+  async created() {
+    if (this.id) await this.search();
+  },
+  methods: {
+    ...dockImgtxt(['query']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      info.dock_id = this.id;
+      let res = await this.query({ ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+      }
+    },
+    // 过滤时间
+    getDate(val) {
+      let newDate = moment(val).format('hh:mm');
+      if (newDate) return newDate;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 0 5px;
+  .list {
+    padding: 10px 0;
+    .txt {
+      span:nth-child(1) {
+        color: #ff0000;
+        i {
+          margin: 0 5px 0 0;
+          top: 2px;
+        }
+      }
+      span:nth-child(2) {
+        font-size: 14px;
+        padding: 4px 5px;
+        background: #f1f1f1;
+        margin: 0 0 0 10px;
+        border-radius: 10px;
+        color: #666;
+        font-weight: bold;
+      }
+    }
+    .brief {
+      font-size: 14px;
+      padding: 5px 0;
+    }
+    .image {
+      .imagelist {
+        width: 100%;
+      }
+    }
+  }
+}
+</style>

+ 94 - 0
src/views/live/onAchieve/parts/project_1.vue

@@ -0,0 +1,94 @@
+<template>
+  <div id="project_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col span="19" class="textOver name">
+            {{ item.name }}
+          </van-col>
+          <van-col span="5" class="textOver type">
+            {{ item.type == '0' ? '科技需求' : '技术成果' }}
+          </van-col>
+          <van-col span="24" class="textOver other">
+            领域:<span>{{ item.field }}</span>
+          </van-col>
+          <van-col span="24" class="textOver other">
+            联系人:<span>{{ item.contacts }}</span>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: statistics } = createNamespacedHelpers('statistics');
+export default {
+  name: 'project_1',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      list: [],
+    };
+  },
+  async created() {
+    if (this.id) await this.search();
+  },
+  methods: {
+    ...statistics(['dockProduct']),
+    async search({ skip = 0, limit = 100, ...info } = {}) {
+      info.dock_id = this.id;
+      let res = await this.dockProduct({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+      }
+    },
+    // 详情页面
+    detail(data) {},
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 0 5px;
+  .list {
+    padding: 5px 0;
+    border-bottom: 1px dashed #ccc;
+    .name {
+      font-size: 16px;
+      font-weight: bold;
+    }
+    .type {
+      font-size: 14px;
+      text-align: center;
+    }
+    .other {
+      font-size: 14px;
+      padding: 5px 0 0 0;
+      color: #666;
+      span {
+        color: #000;
+      }
+    }
+  }
+}
+</style>

+ 103 - 0
src/views/live/onAchieve/parts/readshow_1.vue

@@ -0,0 +1,103 @@
+<template>
+  <div id="readshow_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col :span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col span="19" class="textOver title">
+            {{ item.title }}
+          </van-col>
+          <van-col span="5" class="date">
+            {{ item.publish_time }}
+          </van-col>
+          <van-col span="24" class="textOver orgin">
+            来源:<span>{{ item.origin }} </span>
+          </van-col>
+          <van-col span="24" class="titlejj">
+            简介:<span>{{ item.brief }}</span>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: roadShow } = createNamespacedHelpers('roadShow');
+export default {
+  name: 'readshow_1',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      list: [],
+    };
+  },
+  async created() {
+    if (this.id) await this.search();
+  },
+  methods: {
+    ...roadShow(['query']),
+    async search({ skip = 0, limit = 100, ...info } = {}) {
+      let res = await this.query({ skip, dock_id: this.dock_id, ...info });
+      if (this.$checkRes(res)) this.$set(this, `list`, res.data);
+    },
+    // 详情页面
+    detail(data) {},
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 0 5px;
+  .list {
+    padding: 5px 0;
+    border-bottom: 1px dashed #ccc;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+    }
+    .date {
+      font-size: 14px;
+      text-align: right;
+      padding: 3px 0 0 0;
+    }
+    .orgin {
+      font-size: 14px;
+      padding: 5px 0;
+      span {
+        color: #999;
+      }
+    }
+    .titlejj {
+      font-size: 14px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      -webkit-line-clamp: 2;
+      word-break: break-all;
+      display: -webkit-box;
+      -webkit-box-orient: vertical;
+      span {
+        color: #999;
+      }
+    }
+  }
+}
+</style>

+ 79 - 0
src/views/live/onAchieve/parts/video_1.vue

@@ -0,0 +1,79 @@
+<template>
+  <div id="video_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <video id="videoElement" controls autoplay class="h5video"></video>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: mapDock } = createNamespacedHelpers('dock');
+import flvjs from 'flv.js';
+export default {
+  name: 'video_1',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      dockInfo: {},
+    };
+  },
+  async created() {
+    if (this.id) await this.search();
+    this.$nextTick(() => {
+      this.searchLive();
+    });
+  },
+  methods: {
+    ...mapDock(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `dockInfo`, res.data);
+      }
+    },
+    searchLive() {
+      console.log(`${process.env.VUE_APP_LIVE_URL + this.dockInfo.room_id}`);
+      let player = document.getElementById('videoElement');
+      let rmtpUrl = `${process.env.VUE_APP_LIVE_URL + this.dockInfo.room_id}`;
+      if (flvjs.isSupported()) {
+        var flvPlayer = flvjs.createPlayer({
+          isLive: true,
+          type: 'flv',
+          url: rmtpUrl,
+        });
+        flvPlayer.attachMediaElement(player);
+        flvPlayer.load(); //加载
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    dockInfo: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .h5video {
+    width: 100%;
+    height: 210px;
+  }
+}
+</style>

+ 117 - 0
src/views/live/parts/detail_1.vue

@@ -0,0 +1,117 @@
+<template>
+  <div id="detail_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="title">{{ form.title }}</van-col>
+        <van-col span="24" class="other">
+          <van-col span="12" class="otherInfo textOver">
+            发布时间:<span>{{ form.publish_time || '暂无时间' }}</span>
+          </van-col>
+          <van-col span="12" class="otherInfo textOver">
+            信息来源:<span>{{ form.origin || '系统管理员' }}</span>
+          </van-col>
+        </van-col>
+        <van-col span="24" class="image" v-if="form.picture">
+          <van-image :src="form.picture">
+            <template v-slot:error>加载失败</template>
+          </van-image>
+        </van-col>
+        <van-col span="24" class="video" v-if="form.filepath">
+          <video
+            autoplay="autoplay"
+            controls="controls"
+            preload="meta"
+            x-webkit-airplay="true"
+            webkit-playsinline="true"
+            playsinline="true"
+            x5-video-player-type="h5"
+            x5-video-player-fullscreen="true"
+            controlsList="nodownload"
+            :src="form.filepath"
+            loop="loop"
+          >
+            <source src="movie.ogg" type="video/ogg" />
+            <source src="movie.mp4" type="video/mp4" />
+          </video>
+        </van-col>
+        <van-col span="24" class="content">
+          <p v-html="form.content"></p>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'detail_1',
+  props: {
+    form: { type: Object },
+  },
+  components: {},
+  data: function() {
+    return {
+      picture: require('@a/jnpx.jpg'),
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 0 8px;
+  .title {
+    font-size: 16px;
+    font-weight: bold;
+    margin: 0 0 5px 0;
+    padding: 15px 0;
+    text-align: center;
+  }
+  .other {
+    margin: 0 0 15px 0;
+    .otherInfo {
+      font-size: 14px;
+      color: #666;
+      text-align: center;
+      span {
+        color: #000;
+      }
+    }
+  }
+  .image {
+    margin: 0 0 5px 0;
+    height: 200px;
+    overflow: hidden;
+    /deep/.van-image {
+      width: 100%;
+    }
+    /deep/.van-image__img {
+      height: 200px;
+      overflow: hidden;
+    }
+  }
+  .video {
+    video {
+      width: 100%;
+      height: 200px;
+      overflow: hidden;
+    }
+  }
+}
+</style>

+ 207 - 0
src/views/live/parts/detail_2.vue

@@ -0,0 +1,207 @@
+<template>
+  <div id="detail_2">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="video">
+          <video
+            autoplay="autoplay"
+            controls="controls"
+            preload="meta"
+            x-webkit-airplay="true"
+            webkit-playsinline="true"
+            playsinline="true"
+            x5-video-player-type="h5"
+            x5-video-player-fullscreen="true"
+            controlsList="nodownload"
+            :src="videoUrl"
+            loop="loop"
+          >
+            <source src="movie.ogg" type="video/ogg" />
+            <source src="movie.mp4" type="video/mp4" />
+          </video>
+        </van-col>
+        <van-col span="24" class="text">
+          <van-collapse v-model="activeNames">
+            <van-collapse-item name="1">
+              <template #title>
+                <div class="title">{{ form.title }}</div>
+              </template>
+              <van-col span="24" class="con">
+                <p><span>更新时间:</span>{{ form.create_time }}</p>
+                <p><span>来源:</span>{{ form.orgin }}</p>
+                <p><span>简介:</span>{{ form.desc }}</p>
+              </van-col>
+            </van-collapse-item>
+            <van-collapse-item name="2">
+              <template #title>
+                <div class="title">视频列表</div>
+              </template>
+              <van-col span="24" class="videodata">
+                <van-col span="12" class="videolist" v-for="(item, index) in videodata" :key="index" @click.native="changevideo(item, index)">
+                  <p :style="`color:${menuIndex == index ? menuColor : ''}`">{{ item.title }}</p>
+                  <p :style="`color:${menuIndex == index ? menuColor : ''}`">{{ item.start_time }}</p>
+                </van-col>
+              </van-col>
+            </van-collapse-item>
+          </van-collapse>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: channelVideo } = createNamespacedHelpers('channelVideo');
+var moment = require('moment');
+const _ = require('lodash');
+export default {
+  name: 'detail_2',
+  props: {
+    form: { type: Object },
+  },
+  components: {},
+  data: function() {
+    return {
+      activeNames: ['1', '2'],
+      // 视频列表
+      videodata: [],
+      // 视频路径
+      videoUrl: '',
+      // 选集参数
+      menuIndex: '',
+      menuColor: 'rgb(64,158,255)',
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...channelVideo({ videoquery: 'query' }),
+    async search() {
+      let res = await this.videoquery({ channel_id: this.id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `videodata`, _.orderBy(res.data, ['start_time'], ['asc']));
+      }
+    },
+    changevideo(item, index) {
+      if (item) {
+        this.menuIndex = index;
+        this.$set(this, `videoUrl`, item.file_path);
+      }
+    },
+    searchvideo() {
+      let data = this.videodata;
+      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.changevideo(arr, index);
+      } else {
+        this.changevideo(data[0], '0');
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    videodata: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        if (val) {
+          this.searchvideo();
+        }
+      },
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .video {
+    video {
+      width: 100%;
+      height: 200px;
+      background-color: #000;
+    }
+    video::-webkit-media-controls-mute-button {
+      display: none !important;
+    }
+  }
+  .text {
+    padding: 5px 10px;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+    }
+    .con {
+      p {
+        span {
+          font-weight: bold;
+          padding: 10px 0 0 0;
+        }
+      }
+    }
+    .videodata {
+      margin: 5px 0 0 0;
+      .videolist {
+        text-align: center;
+        background: #f1f1f1;
+        margin: 0 10px 10px 0;
+        border-radius: 10px;
+        height: 100px;
+        width: 48%;
+        padding: 15px 0;
+        p:nth-child(1) {
+          font-size: 16px;
+          font-weight: bold;
+          padding: 0 10px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          -webkit-line-clamp: 2;
+          word-break: break-all;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+        }
+        p:nth-child(2) {
+          font-size: 14px;
+          font-weight: bold;
+        }
+      }
+      .videolist:nth-child(2n) {
+        margin: 0 0 10px 0;
+      }
+    }
+  }
+}
+/deep/.van-cell {
+  background-color: transparent;
+  padding: 5px 10px;
+}
+/deep/.van-collapse-item__content {
+  background-color: transparent;
+  color: black;
+  padding: 0 10px;
+}
+/deep/.van-swipe__indicators {
+  display: none;
+}
+/deep/.van-collapse-item {
+  border-bottom: 1px solid #f1f1f1;
+}
+/deep/.van-cell::after {
+  border: 0;
+}
+/deep/[class*='van-hairline']::after {
+  border: 0;
+}
+</style>

+ 82 - 0
src/views/live/parts/list_1.vue

@@ -0,0 +1,82 @@
+<template>
+  <div id="list_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col span="24" class="title">
+            {{ item.title }}
+          </van-col>
+          <van-col span="24" class="other">
+            <van-col span="24" class="otherInfo textOver">
+              更新时间:<span>{{ item.publish_time }}</span>
+            </van-col>
+            <van-col span="24" class="otherInfo textOver">
+              信息来源:<span>{{ item.origin }}</span>
+            </van-col>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list_1',
+  props: {
+    list: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    detail(data) {
+      this.$emit('detail', data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 8px 8px 0 8px;
+  background-color: #f9f9f9;
+  .list {
+    padding: 8px;
+    margin: 0 0 10px 0;
+    border-radius: 8px;
+    background-color: #fff;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      margin: 0 0 5px 0;
+    }
+    .other {
+      .otherInfo {
+        font-size: 14px;
+        color: #666;
+        margin: 0 0 5px 0;
+        span {
+          color: #000;
+        }
+      }
+    }
+  }
+}
+</style>

+ 89 - 0
src/views/live/parts/list_2.vue

@@ -0,0 +1,89 @@
+<template>
+  <div id="list_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col span="24" class="title">
+            {{ item.title }}
+          </van-col>
+          <van-col span="24" class="other">
+            <van-col span="24" class="otherInfo textOver">
+              举办城市:<span>{{ item.province }}-{{ item.city }}</span>
+            </van-col>
+            <van-col span="24" class="otherInfo textOver">
+              直播时间:<span>{{ item.start_time }}</span>
+            </van-col>
+          </van-col>
+          <van-col span="24" class="btn" v-if="item.status == '1'">
+            <van-button type="info" size="small" @click="detail(item)">进入房间</van-button>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list_1',
+  props: {
+    list: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    detail(data) {
+      this.$emit('detail', data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 8px 8px 0 8px;
+  background-color: #f9f9f9;
+  .list {
+    padding: 8px;
+    margin: 0 0 10px 0;
+    border-radius: 8px;
+    background-color: #fff;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      margin: 0 0 5px 0;
+    }
+    .other {
+      margin: 0 0 5px 0;
+      .otherInfo {
+        font-size: 14px;
+        color: #666;
+        margin: 0 0 5px 0;
+        span {
+          color: #000;
+        }
+      }
+    }
+    .btn {
+      text-align: center;
+    }
+  }
+}
+</style>

+ 101 - 0
src/views/live/parts/list_3.vue

@@ -0,0 +1,101 @@
+<template>
+  <div id="list_3">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col :span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col :span="11" class="image">
+            <el-image :src="noimage"> </el-image>
+          </van-col>
+          <van-col :span="13" class="text">
+            <p class="title textOver">
+              <span style="color:#ff0000">[{{ item.room_id }}]</span>{{ item.title }}
+            </p>
+            <p class="other textOver">所属类型:{{ item.type || '暂无' }}</p>
+            <p class="other textOver">更新时间:{{ item.create_time || '暂无' }}</p>
+            <p class="desc">{{ item.desc || '暂无' }}</p>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list_3',
+  props: {
+    list: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {
+      noimage: require('@/assets/kjzx.jpg'),
+    };
+  },
+  created() {},
+  methods: {
+    detail(data) {
+      this.$router.push({ path: '/live/channel/detail', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 10px 10px 0 10px;
+  background-color: #f9f9f9;
+  .list {
+    background-color: #fff;
+    margin: 0 0 10px 0;
+    padding: 8px;
+    .image {
+      height: 100px;
+      overflow: hidden;
+      .el-image {
+        width: 100%;
+        height: 100%;
+        border-radius: 10px;
+        /deep/.image-slot {
+          width: 100%;
+          height: 100%;
+        }
+      }
+    }
+    .text {
+      padding: 0 5px;
+      .title {
+        font-size: 16px;
+        font-weight: bold;
+      }
+      .other {
+        font-size: 12px;
+        margin: 5px 0;
+      }
+      .desc {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        display: -webkit-box;
+        -webkit-line-clamp: 2;
+        -webkit-box-orient: vertical;
+        font-size: 12px;
+        color: #666;
+      }
+    }
+  }
+}
+</style>

+ 82 - 0
src/views/live/parts/list_4.vue

@@ -0,0 +1,82 @@
+<template>
+  <div id="list_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col span="24" class="title">
+            {{ item.title }}
+          </van-col>
+          <van-col span="24" class="other">
+            <van-col span="24" class="otherInfo textOver">
+              更新时间:<span>{{ item.publish_time }}</span>
+            </van-col>
+            <van-col span="24" class="otherInfo textOver">
+              信息来源:<span>{{ item.origin }}</span>
+            </van-col>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list_1',
+  props: {
+    list: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    detail(data) {
+      this.$router.push({ path: '/live/trainInter/oneDetail', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 8px 8px 0 8px;
+  background-color: #f9f9f9;
+  .list {
+    padding: 8px;
+    margin: 0 0 10px 0;
+    border-radius: 8px;
+    background-color: #fff;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      margin: 0 0 5px 0;
+    }
+    .other {
+      .otherInfo {
+        font-size: 14px;
+        color: #666;
+        margin: 0 0 5px 0;
+        span {
+          color: #000;
+        }
+      }
+    }
+  }
+}
+</style>

+ 148 - 0
src/views/live/parts/list_5.vue

@@ -0,0 +1,148 @@
+<template>
+  <div id="list_5">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col :span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="toLogin(item)">
+          <van-col :span="11" class="image">
+            <el-image :src="noimage"> </el-image>
+          </van-col>
+          <van-col :span="13" class="text">
+            <p class="title textOver">
+              <span style="color:#ff0000">[{{ item.room_id }}]</span>{{ item.title }}
+            </p>
+            <p class="other textOver">所属类型:{{ item.type }}</p>
+            <p class="other textOver">更新时间:{{ item.create_time }}</p>
+            <p class="desc">{{ item.brief }}</p>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+    <van-dialog v-model="dialog" title="登陆" @closed="toClose" :show-confirm-button="false">
+      <van-form ref="vantForm" @submit="login">
+        <van-field v-model="info.user_phone" name="user_phone" label="用户名" placeholder="用户名" :rules="[{ required: true, message: '请填写用户名' }]" />
+        <van-field
+          v-model="info.user_password"
+          type="password"
+          name="user_password"
+          label="密码"
+          placeholder="密码"
+          :rules="[{ required: true, message: '请填写密码' }]"
+        />
+        <van-row style="padding:10px" gutter="20" type="flex" justify="space-around">
+          <van-col span="12">
+            <van-button round block type="warning" @click="toClose">取消</van-button>
+          </van-col>
+          <van-col span="12">
+            <van-button round block type="info" native-type="submit">提交</van-button>
+          </van-col>
+        </van-row>
+      </van-form>
+    </van-dialog>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainLive } = createNamespacedHelpers('trainLive');
+export default {
+  name: 'list_5',
+  props: {
+    list: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {
+      noimage: require('@/assets/jnpx.jpg'),
+      dialog: false,
+      selectId: undefined,
+      info: {},
+    };
+  },
+  created() {},
+  methods: {
+    ...trainLive(['userLogin']),
+    /**
+     * 点击培训问诊函数
+     */
+    async toLogin({ id }) {
+      if (!id) this.$toast.fail('未获取到此数据的信息');
+      this.selectId = id;
+      this.dialog = true;
+    },
+    /**
+     * 培训问诊登陆
+     */
+    async login() {
+      const res = await this.userLogin({ id: this.selectId, ..._.cloneDeep(this.info) });
+      if (res) this.$router.push({ path: '/live/trainInter/twoDetail', query: { id: this.selectId, type: '3' } });
+    },
+    /**
+     * 关闭弹框后的函数
+     */
+    toClose() {
+      this.dialog = false;
+      this.$refs.vantForm.resetValidation();
+      this.selectId = undefined;
+      this.info = {};
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 10px 10px 0 10px;
+  background-color: #f9f9f9;
+  .list {
+    background-color: #fff;
+    margin: 0 0 10px 0;
+    padding: 8px;
+    .image {
+      height: 100px;
+      overflow: hidden;
+      .el-image {
+        width: 100%;
+        height: 100%;
+        border-radius: 10px;
+        /deep/.image-slot {
+          width: 100%;
+          height: 100%;
+        }
+      }
+    }
+    .text {
+      padding: 0 5px;
+      .title {
+        font-size: 16px;
+        font-weight: bold;
+      }
+      .other {
+        font-size: 12px;
+        margin: 5px 0;
+      }
+      .desc {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        display: -webkit-box;
+        -webkit-line-clamp: 2;
+        -webkit-box-orient: vertical;
+        font-size: 12px;
+        color: #666;
+      }
+    }
+  }
+}
+</style>

+ 60 - 0
src/views/live/roadshow/detail.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="detail">
+    <mobileMain :useNav="false" :usePage="false" topType="2" :rightArrow="false" @back="back">
+      <template v-slot:info>
+        <detail-frame :form="form"></detail-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import detailFrame from '../parts/detail_1.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: roadShow } = createNamespacedHelpers('roadShow');
+export default {
+  name: 'detail',
+  props: {},
+  components: {
+    detailFrame,
+  },
+  data: function() {
+    return {
+      form: {},
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...roadShow(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    back() {
+      this.$router.push({ path: '/live/roadshow/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 62 - 0
src/views/live/roadshow/index.vue

@@ -0,0 +1,62 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <list-frame :list="list" @detail="detail"></list-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import listFrame from '../parts/list_1.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: roadShow } = createNamespacedHelpers('roadShow');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+  },
+  data: function() {
+    return {
+      list: [],
+      total: 0,
+      limit: 10,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...roadShow(['query']),
+    async search({ skip = 0, limit = this.limit, searchName, ...info } = {}) {
+      if (searchName) info.title = searchName;
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 详情
+    detail(data) {
+      this.$router.push({ path: '/live/roadshow/detail', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 82 - 0
src/views/live/trainInter/index.vue

@@ -0,0 +1,82 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <van-tabs v-model="active" @click="changeActive">
+          <van-tab title="嘉宾访谈" name="0">
+            <one-frame :list="list"></one-frame>
+          </van-tab>
+          <van-tab title="技能培训" name="1">
+            <two-frame :list="list"></two-frame>
+          </van-tab>
+        </van-tabs>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import oneFrame from '../parts/list_4.vue';
+import twoFrame from '../parts/list_5.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: interview } = createNamespacedHelpers('interview');
+const { mapActions: trainLive } = createNamespacedHelpers('trainLive');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    oneFrame,
+    twoFrame,
+  },
+  data: function() {
+    return {
+      active: 0,
+      list: [],
+      total: 0,
+      limit: 10,
+    };
+  },
+  created() {
+    this.changeActive();
+  },
+  methods: {
+    ...interview(['query']),
+    ...trainLive({ getTrainlive: 'query' }),
+    async search({ skip = 0, limit = this.limit, searchName, ...info } = {}) {
+      if (searchName) info.title = searchName;
+      if (this.active == 0) {
+        let res = await this.query({ skip, limit, ...info });
+        if (this.$checkRes(res)) {
+          this.$set(this, `list`, res.data);
+          this.$set(this, `total`, res.total);
+        }
+      } else if (this.active == 1) {
+        let res = await this.getTrainlive({ skip, limit, ...info });
+        if (this.$checkRes(res)) {
+          this.$set(this, `list`, res.data);
+          this.$set(this, `total`, res.total);
+        }
+      }
+    },
+    changeActive(name) {
+      this.$set(this, `active`, name || 0);
+      this.search();
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 60 - 0
src/views/live/trainInter/oneDetail.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="oneDetail">
+    <mobileMain :useNav="false" :usePage="false" topType="2" :rightArrow="false" @back="back">
+      <template v-slot:info>
+        <detail-frame :form="form"></detail-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import detailFrame from '../parts/detail_1.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: interview } = createNamespacedHelpers('interview');
+export default {
+  name: 'oneDetail',
+  props: {},
+  components: {
+    detailFrame,
+  },
+  data: function() {
+    return {
+      form: {},
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...interview(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    back() {
+      this.$router.push({ path: '/live/trainInter/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 270 - 0
src/views/live/trainInter/parts/chat_1.vue

@@ -0,0 +1,270 @@
+<template>
+  <div id="chat_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(i, index) in list" :key="index">
+          <span v-if="!isEmotion(i.content)">
+            <van-col span="24" class="one">
+              [<span>{{ i.send_time | getTime }}</span
+              >]
+              <span>{{ i.sender_name }}</span>
+            </van-col>
+            <van-col span="24" class="two">
+              <span> {{ i.content }}</span>
+            </van-col>
+          </span>
+          <span v-else>
+            <van-col span="24" class="thr">
+              <span class="textOver">{{ i.sender_name }}</span>
+              <span>送出一朵/个</span>
+              <span v-html="i.content"></span>
+            </van-col>
+          </span>
+        </van-col>
+      </van-col>
+    </van-row>
+    <van-col span="24" class="sendtxt">
+      <van-button icon="chat" type="info" round @click="show = true" />
+    </van-col>
+    <van-popup v-model="show" position="bottom" class="popup">
+      <van-col span="24" class="one">
+        <p>快捷发言</p>
+        <p>
+          <span v-for="(i, index) in kjfyList" :key="index" @click="changekjfy(i.name)">{{ i.name }}</span>
+        </p>
+      </van-col>
+      <van-col span="24" class="two">
+        <van-col span="19">
+          <van-field v-model="text" placeholder="请输入内容" />
+        </van-col>
+        <van-col span="5" style="text-align:center;">
+          <van-button round type="info" @click="send()">发言</van-button>
+        </van-col>
+      </van-col>
+    </van-popup>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainchat } = createNamespacedHelpers('trainchat');
+var moment = require('moment');
+import _ from 'lodash';
+export default {
+  name: 'chat_1',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      dock_id: '',
+      list: [],
+      // 发言
+      show: false,
+      text: '',
+      // 快捷发言列表
+      kjfyList: [{ name: '欢迎欢迎' }, { name: '科技创新' }, { name: '大咖云集' }],
+    };
+  },
+  async created() {
+    if (this.id) {
+      this.$set(this, `dock_id`, this.id);
+      await this.search();
+    }
+  },
+  mounted() {
+    this.channel();
+  },
+  methods: {
+    ...trainchat(['query', 'create']),
+    async search() {
+      let res = await this.query({ dock_id: this.dock_id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+      }
+    },
+    isEmotion(word) {
+      return word.startsWith('<img');
+    },
+    channel() {
+      this.$stomp({
+        [`/exchange/train_live/${this.dock_id}`]: this.onMessage,
+      });
+    },
+    onMessage(message) {
+      let body = _.get(message, 'body');
+      if (body) {
+        body = JSON.parse(body);
+        this.list.push(body);
+        this.text = '';
+      }
+      this.search();
+    },
+    // 发言
+    async send() {
+      if (this.text != '') {
+        let data = {
+          unit_id: this.dock_id,
+          content: this.text,
+          sender_id: this.user.id || this.getData(),
+          sender_name: this.user.name || this.getData() + '游客',
+          send_time: moment().format('yyyy-MM-DD HH:mm:ss'),
+        };
+        let res = await this.create(data);
+        this.text = '';
+        this.show = false;
+        this.$checkRes(res, null, res.errmsg || '发言失败');
+      } else this.$message.error('请输入信息后发送');
+    },
+    // 选择快捷发言
+    changekjfy(data) {
+      this.$set(this, `text`, data);
+      this.send();
+    },
+    // 获取时间戳
+    getData() {
+      let date = moment(new Date()).valueOf();
+      if (date) return date;
+    },
+  },
+  filters: {
+    getTime(date) {
+      if (!date) return '很久以前';
+      let today = moment().format('YYYY-MM-DD');
+      let dd = moment(date).format('YYYY-MM-DD');
+      let time;
+      if (today == dd) time = moment(date).format('HH:mm');
+      else time = moment(date).format('YYYY-MM-DD');
+      return time;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 0 5px;
+  .list {
+    padding: 5px 0;
+    .one {
+      padding: 5px 0;
+      span:nth-child(1) {
+        color: #ff0000;
+      }
+      span:nth-child(2) {
+        font-size: 16px;
+        color: #999;
+      }
+    }
+    .two {
+      min-height: 30px;
+      span {
+        font-size: 15px;
+        background: #f1f1f1;
+        padding: 5px;
+        border-radius: 5px;
+        min-width: 30%;
+        display: inline-block;
+      }
+    }
+    .thr {
+      text-align: center;
+      background: #f1f1f1;
+      border-radius: 25px;
+      width: 80%;
+      margin: 0 10%;
+      padding: 5px 0;
+      span:nth-child(1) {
+        color: #db7093;
+        font-weight: bold;
+        position: relative;
+        top: -3px;
+        font-size: 15px;
+        display: inline-block;
+        width: 45%;
+      }
+      span:nth-child(2) {
+        color: #000000;
+        position: relative;
+        top: -7px;
+        font-size: 15px;
+      }
+      span:nth-child(3) {
+        position: relative;
+        top: 2px;
+        left: 5px;
+      }
+    }
+  }
+}
+// 发言
+.sendtxt {
+  position: fixed;
+  bottom: 10px;
+  right: 10px;
+  width: 100%;
+  z-index: 999;
+  text-align: right;
+}
+.popup {
+  .one {
+    color: #fff;
+    p:nth-child(1) {
+      text-align: center;
+      padding: 25px 0;
+      font-size: 20px;
+    }
+    p:nth-child(2) {
+      padding: 0 10px;
+      text-align: center;
+      span {
+        background-color: #409eff9f;
+        display: inline-block;
+        border-radius: 25px;
+        margin: 0 10px 10px 0;
+        font-size: 15px;
+        padding: 5px 10px;
+      }
+      span:nth-child(4n) {
+        margin: 0 0 10px 0;
+      }
+    }
+  }
+  .two {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    background: #fff;
+    padding: 5px 5px;
+    height: 40px;
+    /deep/.van-cell {
+      padding: 3px 5px;
+      border-radius: 5px;
+      border: 1px solid #ccc;
+      width: 98%;
+    }
+    /deep/.van-button {
+      height: 31px;
+    }
+  }
+}
+/deep/.van-popup {
+  height: 30%;
+  background-color: #0000005f !important;
+}
+</style>

+ 135 - 0
src/views/live/trainInter/parts/video_1.vue

@@ -0,0 +1,135 @@
+<template>
+  <div id="video_1">
+    <el-row>
+      <el-col :span="24" class="main">
+        <van-col span="24" class="video">
+          <video
+            autoplay="autoplay"
+            controls="controls"
+            preload="meta"
+            x-webkit-airplay="true"
+            webkit-playsinline="true"
+            playsinline="true"
+            x5-video-player-type="h5"
+            x5-video-player-fullscreen="true"
+            :src="videoPath"
+            v-if="videoData.length > 0"
+            loop="loop"
+          >
+            <source src="movie.ogg" type="video/ogg" />
+            <source src="movie.mp4" type="video/mp4" />
+          </video>
+          <p v-else>{{ form.title }}</p>
+        </van-col>
+        <van-col span="24" class="btn">
+          <el-button type="primary" size="mini" @click="back()">返回列表</el-button>
+          <el-button type="primary" size="mini" @click="showPopup()">选择视频</el-button>
+        </van-col>
+      </el-col>
+    </el-row>
+    <van-popup v-model="show" position="bottom">
+      <van-picker
+        title="视频信息"
+        v-model="show"
+        show-toolbar
+        :columns="videoData"
+        @confirm="onConfirm"
+        @cancel="onCancel"
+        @change="onChange"
+        value-key="videointro"
+      />
+    </van-popup>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const _ = require('lodash');
+export default {
+  name: 'video_1',
+  props: {
+    form: { type: Object },
+  },
+  components: {},
+  data: function() {
+    return {
+      videoData: [],
+      videoPath: '',
+      show: false,
+    };
+  },
+  created() {},
+  methods: {
+    searchVideo(data) {
+      let videoData = data.video_data.map(i => i.video_title);
+      if (videoData) {
+        this.$set(this, `videoData`, videoData);
+        this.$set(this, `videoPath`, _.get(_.head(data.video_data), 'video_url'));
+      }
+    },
+    back() {
+      this.$router.push({ path: '/live/trainInter/index' });
+    },
+    showPopup() {
+      this.show = true;
+    },
+    // 选择视频
+    changeMenu(index, value) {
+      const item = this.form.video_data[index];
+      if (item) {
+        this.$set(this, `videoPath`, item.video_url);
+      }
+    },
+    // 确定选择
+    onConfirm(value, index) {
+      this.changeMenu(index, value);
+      this.onCancel();
+    },
+    onChange(value, index) {
+      this.changeMenu(index, value);
+    },
+    // 取消
+    onCancel() {
+      this.show = false;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    form: {
+      deep: true,
+      immediate: true,
+      handler(val) {
+        if (val) {
+          this.searchVideo(val);
+        }
+      },
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .video {
+    height: 210px;
+    overflow: hidden;
+    .h5video {
+      width: 100%;
+      height: 210px;
+    }
+    p {
+      font-size: 18px;
+    }
+  }
+  .btn {
+    text-align: center;
+    height: 40px;
+    padding: 5px 0;
+  }
+}
+</style>

+ 89 - 0
src/views/live/trainInter/twoDetail.vue

@@ -0,0 +1,89 @@
+<template>
+  <div id="twoDetail">
+    <mobileMain :useNav="false" :usePage="false" :useTop="false">
+      <template v-slot:info>
+        <van-col span="24" class="video">
+          <video-frame :form="form"></video-frame>
+        </van-col>
+        <van-col span="24" class="down">
+          <van-tabs v-model="active">
+            <van-tab title="公共聊天" name="1">
+              <van-col span="24" class="downInfo" :style="{ height: client.height - 290 + 'px' }">
+                <chat-frame></chat-frame>
+              </van-col>
+            </van-tab>
+          </van-tabs>
+        </van-col>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import videoFrame from './parts/video_1.vue';
+import chatFrame from './parts/chat_1.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainLive } = createNamespacedHelpers('trainLive');
+export default {
+  name: 'twoDetail',
+  props: {},
+  components: {
+    videoFrame,
+    chatFrame,
+  },
+  data: function() {
+    return {
+      client: {},
+      form: {},
+      active: '1',
+    };
+  },
+  async created() {
+    if (this.id) await this.search();
+  },
+  methods: {
+    ...trainLive(['fetch']),
+    async search() {
+      const res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, 'form', res.data);
+      }
+    },
+  },
+  mounted() {
+    let client = {
+      height: document.documentElement.clientHeight || document.body.clientHeight,
+      width: document.documentElement.clientWidth || document.body.clientWidth,
+    };
+    this.$set(this, `client`, client);
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.video {
+  height: 250px;
+  overflow: hidden;
+}
+.down {
+  .downInfo {
+    overflow-y: auto;
+  }
+}
+</style>

+ 60 - 0
src/views/market/achieve/detail.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="detail">
+    <mobileMain :useNav="false" :usePage="false" topType="2" :rightArrow="false" @back="back">
+      <template v-slot:info>
+        <detail-frame :form="form"></detail-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import detailFrame from '../parts/detail_1.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: product } = createNamespacedHelpers('product');
+export default {
+  name: 'detail',
+  props: {},
+  components: {
+    detailFrame,
+  },
+  data: function() {
+    return {
+      form: {},
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...product(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    back() {
+      this.$router.push({ path: '/market/achieve/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

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

@@ -0,0 +1,64 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <list-frame :list="list" @detail="detail"></list-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import listFrame from '../parts/list_1.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: product } = createNamespacedHelpers('product');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+  },
+  data: function() {
+    return {
+      list: [],
+      total: 0,
+      limit: 10,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...product(['query']),
+    async search({ skip = 0, limit = this.limit, searchName, ...info } = {}) {
+      if (searchName) info.name = searchName;
+      info.type = '1';
+      info.status = '2';
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 详情
+    detail(data) {
+      this.$router.push({ path: '/market/achieve/detail', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 60 - 0
src/views/market/expert/detail.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="detail">
+    <mobileMain :useNav="false" :usePage="false" topType="2" :rightArrow="false" @back="back">
+      <template v-slot:info>
+        <detail-frame :form="form"></detail-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import detailFrame from '../parts/detail_4.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: expert } = createNamespacedHelpers('expert');
+export default {
+  name: 'detail',
+  props: {},
+  components: {
+    detailFrame,
+  },
+  data: function() {
+    return {
+      form: {},
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...expert(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    back() {
+      this.$router.push({ path: '/market/expert/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

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

@@ -0,0 +1,62 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <list-frame :list="list" @detail="detail"></list-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import listFrame from '../parts/list_4.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: expert } = createNamespacedHelpers('expert');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+  },
+  data: function() {
+    return {
+      list: [],
+      total: 0,
+      limit: 10,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...expert(['query']),
+    async search({ skip = 0, limit = this.limit, searchName, ...info } = {}) {
+      if (searchName) info.name = searchName;
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 详情
+    detail(data) {
+      this.$router.push({ path: '/market/expert/detail', query: { id: data.user_id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 86 - 0
src/views/market/parts/detail_1.vue

@@ -0,0 +1,86 @@
+<template>
+  <div id="detail_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="image">
+          <van-swipe style="height: 220px;">
+            <template v-if="form.image && form.image.length > 0">
+              <van-swipe-item v-for="(item, index) in form.image" :key="index">
+                <van-image :src="item.url" />
+              </van-swipe-item>
+            </template>
+            <template v-else>
+              <van-image :src="noimage" />
+            </template>
+          </van-swipe>
+        </van-col>
+        <van-col span="24" class="info">
+          <van-form label-width="4rem">
+            <van-field v-model="form.name" label="成果名称" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.company" label="成果单位" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.field" label="所属领域" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.cooperation" label="合作方式" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.achievestatus" label="成果状态" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.achievesource" label="成果来源" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.achieveown" label="成果权属" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.intentionprice" label="意向价格" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.contacts" label="联系人" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.email" label="联系方式" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.roadshow" label="项目路演" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.achievebrief" label="成果简介" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.features" label="技术特点" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.team" label="技术团队" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.expect" label="商业预期" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.condition" label="合作要求" rows="1" autosize type="textarea" readonly />
+          </van-form>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'detail_1',
+  props: {
+    form: { type: Object },
+  },
+  components: {},
+  data: function() {
+    return {
+      noimage: require('@/assets/noimage.jpg'),
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .image {
+    height: 220px;
+    overflow: hidden;
+    .van-image {
+      width: 100%;
+    }
+    /deep/.van-swipe__track {
+      width: 100% !important;
+    }
+  }
+}
+</style>

+ 78 - 0
src/views/market/parts/detail_2.vue

@@ -0,0 +1,78 @@
+<template>
+  <div id="detail_2">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="image">
+          <van-image :src="form.img_url">
+            <template v-slot:error>
+              <van-image :src="noimage"></van-image>
+            </template>
+            <template v-slot:loading>
+              <van-image :src="noimage"></van-image>
+            </template>
+          </van-image>
+        </van-col>
+        <van-col span="24" class="info">
+          <van-form label-width="5rem">
+            <van-field v-model="form.term" label="专利有效性" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.name" label="名称" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.create_number" label="申请号" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.create_date" label="申请日" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.success_number" label="公开(公告)号" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.success_date" label="公开(公告)日" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.type" label="专利类型" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.inventor" label="发明人" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.address" label="发明人地址" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.apply_personal" label="申请人" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.agent_personal" label="代理人" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.agent" label="代理机构" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.abstract" label="摘要" rows="1" autosize type="textarea" readonly />
+          </van-form>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'detail_2',
+  props: {
+    form: { type: Object },
+  },
+  components: {},
+  data: function() {
+    return {
+      noimage: require('@a/fmzl.jpg'),
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .image {
+    .van-image {
+      width: 100%;
+      height: 220px;
+      border-bottom: 1px solid #f1f1f1;
+    }
+  }
+}
+</style>

+ 84 - 0
src/views/market/parts/detail_3.vue

@@ -0,0 +1,84 @@
+<template>
+  <div id="detail_3">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="image">
+          <van-swipe style="height: 220px;">
+            <template v-if="form.image && form.image.length > 0">
+              <van-swipe-item v-for="(item, index) in form.image" :key="index">
+                <van-image :src="item.url" />
+              </van-swipe-item>
+            </template>
+            <template v-else>
+              <van-image :src="noimage" />
+            </template>
+          </van-swipe>
+        </van-col>
+        <van-col span="24" class="info">
+          <van-form label-width="4rem">
+            <van-field v-model="form.name" label="成果名称" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.company" label="需求方" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.field" label="所属领域" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.cooperation" label="合作方式" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.demand" label="紧急程度" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.budget" label="投资预算" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.contacts" label="联系人" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.qqwx" label="QQ/微信" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.phone" label="联系电话" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.email" label="电子邮箱" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.requirementdesc" label="需求说明" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.expect" label="预期目标" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.present" label="需求现状" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.condition" label="合作要求" rows="1" autosize type="textarea" readonly />
+          </van-form>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'detail_3',
+  props: {
+    form: { type: Object },
+  },
+  components: {},
+  data: function() {
+    return {
+      noimage: require('@/assets/noimage.jpg'),
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    form: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .image {
+    height: 220px;
+    overflow: hidden;
+    .van-image {
+      width: 100%;
+    }
+    /deep/.van-swipe__track {
+      width: 100% !important;
+    }
+  }
+}
+</style>

+ 133 - 0
src/views/market/parts/detail_4.vue

@@ -0,0 +1,133 @@
+<template>
+  <div id="detail_4">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="one">
+          <van-col span="6" class="left">
+            <van-image :src="form.img_path">
+              <template v-slot:error>加载失败</template>
+            </van-image>
+          </van-col>
+          <van-col span="18" class="right">
+            <van-col span="24" class="right_1 textOver">{{ form.name || '暂无' }}</van-col>
+            <van-col span="24" class="right_1 textOver">{{ form.email || '暂无' }}</van-col>
+          </van-col>
+        </van-col>
+        <van-col span="24" class="two">
+          <van-form label-width="4rem">
+            <van-tabs type="card" color="#409eff">
+              <van-tab title="基本资料">
+                <van-col span="24" class="two_1" :style="{ height: client.height - 217 + 'px' }">
+                  <van-field v-model="form.company" label="工作单位" rows="1" autosize type="textarea" readonly />
+                  <van-field v-model="form.school" label="毕业院校" rows="1" autosize type="textarea" readonly />
+                  <van-field v-model="form.education" label="最高学历" rows="1" autosize type="textarea" readonly />
+                  <van-field v-model="form.birthDate" label="出生日期" rows="1" autosize type="textarea" readonly />
+                  <van-field v-model="form.zwzc" label="职务职称" rows="1" autosize type="textarea" readonly />
+                  <van-field v-model="form.qqwx" label="QQ/微信" rows="1" autosize type="textarea" readonly />
+                  <van-field v-model="form.email" label="电子邮箱" rows="1" autosize type="textarea" readonly />
+                  <van-field v-model="form.expertise" label="擅长领域" rows="1" autosize type="textarea" readonly />
+                </van-col>
+              </van-tab>
+              <van-tab title="工作经历">
+                <van-col span="24" class="two_1" :style="{ height: client.height - 217 + 'px' }">
+                  <van-field v-model="form.workexperience" rows="1" autosize type="textarea" readonly />
+                </van-col>
+              </van-tab>
+              <van-tab title="技术能力">
+                <van-col span="24" class="two_1" :style="{ height: client.height - 217 + 'px' }">
+                  <van-field v-model="form.scientific" label="科研综述" rows="1" autosize type="textarea" readonly />
+                  <van-field v-model="form.undertakingproject" label="承担项目" rows="1" autosize type="textarea" readonly />
+                  <van-field v-model="form.scienceaward" label="科技奖励" rows="1" autosize type="textarea" readonly />
+                  <van-field v-model="form.social" label="社会任职" rows="1" autosize type="textarea" readonly />
+                </van-col>
+              </van-tab>
+            </van-tabs>
+          </van-form>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'detail_4',
+  props: {
+    form: { type: Object },
+  },
+  components: {},
+  data: function() {
+    return {
+      client: {},
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  mounted() {
+    let client = {
+      height: document.documentElement.clientHeight || document.body.clientHeight,
+      width: document.documentElement.clientWidth || document.body.clientWidth,
+    };
+    this.$set(this, `client`, client);
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    height: 140px;
+    background: url('~@/assets/test1.jpg');
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    .left {
+      text-align: center;
+      height: 140px;
+      line-height: 200px;
+      .van-image {
+        width: 70px;
+        height: 70px;
+      }
+      /deep/.van-image__img {
+        border-radius: 90px;
+      }
+      /deep/.van-image__loading {
+        border-radius: 90px;
+      }
+    }
+    .right {
+      height: 140px;
+      padding: 40px 10px;
+      .right_1 {
+        margin: 0 0 8px 0;
+        // color: #fff;
+      }
+      .right_1:first-child {
+        font-weight: bold;
+      }
+    }
+  }
+  .two {
+    /deep/.van-tabs__nav--card {
+      margin: 0;
+    }
+    .two_1 {
+      overflow-y: auto;
+    }
+  }
+}
+</style>

+ 54 - 0
src/views/market/parts/detail_5.vue

@@ -0,0 +1,54 @@
+<template>
+  <div id="detail_5">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="one">
+          <van-form label-width="4rem">
+            <van-field v-model="form.name" label="服务名称" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.demand" label="需求程度" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.messattribute" label="信息属性" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.contacts" label="联系人" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.qqwx" label="QQ/微信" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.phone" label="联系电话" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.email" label="电子邮箱" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.informationdesc" label="信息描述" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.coreelements" label="核心要素" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.priceinfo" label="价格信息" rows="1" autosize type="textarea" readonly />
+            <van-field v-model="form.expect" label="商务预期" rows="1" autosize type="textarea" readonly />
+          </van-form>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'detail_5',
+  props: {
+    form: { type: Object },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 85 - 0
src/views/market/parts/list_1.vue

@@ -0,0 +1,85 @@
+<template>
+  <div id="list_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col span="24" class="title">
+            {{ item.name }}
+          </van-col>
+          <van-col span="24" class="other">
+            <van-col span="24" class="otherInfo textOver">
+              成果单位:<span>{{ item.company || '暂无' }}</span>
+            </van-col>
+            <van-col span="24" class="otherInfo textOver">
+              所属领域:<span>{{ item.field || '暂无' }}</span>
+            </van-col>
+            <van-col span="24" class="otherInfo textOver">
+              合作方式:<span>{{ item.cooperation || '暂无' }}</span>
+            </van-col>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list_1',
+  props: {
+    list: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    detail(data) {
+      this.$emit('detail', data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 8px 8px 0 8px;
+  background-color: #f9f9f9;
+  .list {
+    padding: 8px;
+    margin: 0 0 10px 0;
+    border-radius: 8px;
+    background-color: #fff;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      margin: 0 0 5px 0;
+    }
+    .other {
+      .otherInfo {
+        font-size: 14px;
+        color: #666;
+        margin: 0 0 5px 0;
+        span {
+          color: #000;
+        }
+      }
+    }
+  }
+}
+</style>

+ 82 - 0
src/views/market/parts/list_2.vue

@@ -0,0 +1,82 @@
+<template>
+  <div id="list_2">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col span="24" class="title">
+            {{ item.name }}
+          </van-col>
+          <van-col span="24" class="other">
+            <van-col span="24" class="otherInfo textOver">
+              专利有效性:<span>{{ item.term || '暂无' }}</span>
+            </van-col>
+            <van-col span="24" class="otherInfo textOver">
+              颁发时间:<span>{{ item.success_date || '暂无' }}</span>
+            </van-col>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list_2',
+  props: {
+    list: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    detail(data) {
+      this.$emit('detail', data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 8px 8px 0 8px;
+  background-color: #f9f9f9;
+  .list {
+    padding: 8px;
+    margin: 0 0 10px 0;
+    border-radius: 8px;
+    background-color: #fff;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      margin: 0 0 5px 0;
+    }
+    .other {
+      .otherInfo {
+        font-size: 14px;
+        color: #666;
+        margin: 0 0 5px 0;
+        span {
+          color: #000;
+        }
+      }
+    }
+  }
+}
+</style>

+ 85 - 0
src/views/market/parts/list_3.vue

@@ -0,0 +1,85 @@
+<template>
+  <div id="list_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col span="24" class="title">
+            {{ item.name }}
+          </van-col>
+          <van-col span="24" class="other">
+            <van-col span="24" class="otherInfo textOver">
+              需求单位:<span>{{ item.company || '暂无' }}</span>
+            </van-col>
+            <van-col span="24" class="otherInfo textOver">
+              所属领域:<span>{{ item.field || '暂无' }}</span>
+            </van-col>
+            <van-col span="24" class="otherInfo textOver">
+              合作方式:<span>{{ item.cooperation || '暂无' }}</span>
+            </van-col>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list_1',
+  props: {
+    list: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    detail(data) {
+      this.$emit('detail', data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 8px 8px 0 8px;
+  background-color: #f9f9f9;
+  .list {
+    padding: 8px;
+    margin: 0 0 10px 0;
+    border-radius: 8px;
+    background-color: #fff;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      margin: 0 0 5px 0;
+    }
+    .other {
+      .otherInfo {
+        font-size: 14px;
+        color: #666;
+        margin: 0 0 5px 0;
+        span {
+          color: #000;
+        }
+      }
+    }
+  }
+}
+</style>

+ 102 - 0
src/views/market/parts/list_4.vue

@@ -0,0 +1,102 @@
+<template>
+  <div id="list_1">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col span="6" class="left">
+            <van-image :src="item.img_path">
+              <template v-slot:error>加载失败</template>
+            </van-image>
+          </van-col>
+          <van-col span="18" class="right">
+            <van-col span="24" class="title">
+              {{ item.name }}
+            </van-col>
+            <van-col span="24" class="other">
+              <van-col span="24" class="otherInfo textOver">
+                职务职称:<span>{{ item.zwzc || '暂无' }}</span>
+              </van-col>
+              <van-col span="24" class="otherInfo textOver">
+                工作单位:<span>{{ item.company || '暂无' }}</span>
+              </van-col>
+            </van-col>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list_1',
+  props: {
+    list: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    detail(data) {
+      this.$emit('detail', data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 8px 8px 0 8px;
+  background-color: #f9f9f9;
+  .list {
+    padding: 8px;
+    margin: 0 0 10px 0;
+    border-radius: 8px;
+    background-color: #fff;
+    .left {
+      text-align: center;
+      .van-image {
+        width: 80px;
+        height: 80px;
+      }
+      /deep/.van-image__img {
+        border-radius: 90px;
+      }
+    }
+    .right {
+      padding: 0 0 0 5px;
+      .title {
+        font-size: 16px;
+        font-weight: bold;
+        margin: 0 0 5px 0;
+      }
+      .other {
+        .otherInfo {
+          font-size: 14px;
+          color: #666;
+          margin: 0 0 5px 0;
+          span {
+            color: #000;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 85 - 0
src/views/market/parts/list_5.vue

@@ -0,0 +1,85 @@
+<template>
+  <div id="list_5">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="list" v-for="(item, index) in list" :key="index" @click.native="detail(item)">
+          <van-col span="24" class="title">
+            {{ item.name }}
+          </van-col>
+          <van-col span="24" class="other">
+            <van-col span="24" class="otherInfo textOver">
+              成果单位:<span>{{ item.company || '暂无' }}</span>
+            </van-col>
+            <van-col span="24" class="otherInfo textOver">
+              信息属性:<span>{{ item.messattribute || '暂无' }}</span>
+            </van-col>
+            <van-col span="24" class="otherInfo textOver">
+              联系电话:<span>{{ item.qqwx || '暂无' }}</span>
+            </van-col>
+          </van-col>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'list_5',
+  props: {
+    list: { type: Array },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    detail(data) {
+      this.$emit('detail', data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 8px 8px 0 8px;
+  background-color: #f9f9f9;
+  .list {
+    padding: 8px;
+    margin: 0 0 10px 0;
+    border-radius: 8px;
+    background-color: #fff;
+    .title {
+      font-size: 16px;
+      font-weight: bold;
+      margin: 0 0 5px 0;
+    }
+    .other {
+      .otherInfo {
+        font-size: 14px;
+        color: #666;
+        margin: 0 0 5px 0;
+        span {
+          color: #000;
+        }
+      }
+    }
+  }
+}
+</style>

+ 60 - 0
src/views/market/patent/detail.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="detail">
+    <mobileMain :useNav="false" :usePage="false" topType="2" :rightArrow="false" @back="back">
+      <template v-slot:info>
+        <detail-frame :form="form"></detail-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import detailFrame from '../parts/detail_2.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: patent } = createNamespacedHelpers('patent');
+export default {
+  name: 'detail',
+  props: {},
+  components: {
+    detailFrame,
+  },
+  data: function() {
+    return {
+      form: {},
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...patent(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    back() {
+      this.$router.push({ path: '/market/patent/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

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

@@ -0,0 +1,62 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <list-frame :list="list" @detail="detail"></list-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import listFrame from '../parts/list_2.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: patent } = createNamespacedHelpers('patent');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+  },
+  data: function() {
+    return {
+      list: [],
+      total: 0,
+      limit: 10,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...patent(['query']),
+    async search({ skip = 0, limit = this.limit, searchName, ...info } = {}) {
+      if (searchName) info.name = searchName;
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 详情
+    detail(data) {
+      this.$router.push({ path: '/market/patent/detail', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 60 - 0
src/views/market/service/detail.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="detail">
+    <mobileMain :useNav="false" :usePage="false" topType="2" :rightArrow="false" @back="back">
+      <template v-slot:info>
+        <detail-frame :form="form"></detail-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import detailFrame from '../parts/detail_5.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: product } = createNamespacedHelpers('product');
+export default {
+  name: 'detail',
+  props: {},
+  components: {
+    detailFrame,
+  },
+  data: function() {
+    return {
+      form: {},
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...product(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    back() {
+      this.$router.push({ path: '/market/service/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 64 - 0
src/views/market/service/index.vue

@@ -0,0 +1,64 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <list-frame :list="list" @detail="detail"></list-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import listFrame from '../parts/list_5.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: product } = createNamespacedHelpers('product');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+  },
+  data: function() {
+    return {
+      list: [],
+      total: 0,
+      limit: 10,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...product(['query']),
+    async search({ skip = 0, limit = this.limit, searchName, ...info } = {}) {
+      if (searchName) info.name = searchName;
+      info.type = '2';
+      info.status = '2';
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 详情
+    detail(data) {
+      this.$router.push({ path: '/market/service/detail', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 60 - 0
src/views/market/techol/detail.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="detail">
+    <mobileMain :useNav="false" :usePage="false" topType="2" :rightArrow="false" @back="back">
+      <template v-slot:info>
+        <detail-frame :form="form"></detail-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import detailFrame from '../parts/detail_3.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: product } = createNamespacedHelpers('product');
+export default {
+  name: 'detail',
+  props: {},
+  components: {
+    detailFrame,
+  },
+  data: function() {
+    return {
+      form: {},
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...product(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    back() {
+      this.$router.push({ path: '/market/techol/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 64 - 0
src/views/market/techol/index.vue

@@ -0,0 +1,64 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <list-frame :list="list" @detail="detail"></list-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import listFrame from '../parts/list_3.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: product } = createNamespacedHelpers('product');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+  },
+  data: function() {
+    return {
+      list: [],
+      total: 0,
+      limit: 10,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...product(['query']),
+    async search({ skip = 0, limit = this.limit, searchName, ...info } = {}) {
+      if (searchName) info.name = searchName;
+      info.type = '0';
+      info.status = '2';
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 详情
+    detail(data) {
+      this.$router.push({ path: '/market/techol/detail', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 36 - 0
src/views/user/account/index.vue

@@ -0,0 +1,36 @@
+<template>
+  <div id="index">
+    <van-row>
+      <van-col span="24" class="main"> test </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 60 - 0
src/views/user/news/detail.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="detail">
+    <mobileMain :useNav="false" :usePage="false" topType="2" :rightArrow="false" @back="back">
+      <template v-slot:info>
+        <detail-frame :form="form"></detail-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import detailFrame from '../parts/detail_3.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: science } = createNamespacedHelpers('science');
+export default {
+  name: 'detail',
+  props: {},
+  components: {
+    detailFrame,
+  },
+  data: function() {
+    return {
+      form: {},
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...science(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    back() {
+      this.$router.push({ path: '/user/news/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 62 - 0
src/views/user/news/index.vue

@@ -0,0 +1,62 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <list-frame :list="list" @detail="detail"></list-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import listFrame from '../parts/list_3.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: science } = createNamespacedHelpers('science');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+  },
+  data: function() {
+    return {
+      list: [],
+      total: 0,
+      limit: 10,
+    };
+  },
+  async created() {
+    await this.search();
+  },
+  methods: {
+    ...science(['query']),
+    async search({ skip = 0, limit = this.limit, searchName, ...info } = {}) {
+      if (searchName) info.title = searchName;
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 详情
+    detail(data) {
+      this.$router.push({ path: '/user/news/detail', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 60 - 0
src/views/user/notice/detail.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="detail">
+    <mobileMain :useNav="false" :usePage="false" topType="2" :rightArrow="false" @back="back">
+      <template v-slot:info>
+        <detail-frame :form="form"></detail-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import detailFrame from '../parts/detail_2.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: news } = createNamespacedHelpers('news');
+export default {
+  name: 'detail',
+  props: {},
+  components: {
+    detailFrame,
+  },
+  data: function() {
+    return {
+      form: {},
+    };
+  },
+  created() {
+    if (this.id) this.search();
+  },
+  methods: {
+    ...news(['fetch']),
+    async search() {
+      let res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    back() {
+      this.$router.push({ path: '/user/notice/index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 74 - 0
src/views/user/notice/index.vue

@@ -0,0 +1,74 @@
+<template>
+  <div id="index">
+    <mobileMain :useNav="false" @search="search" :total="total" :limit="limit">
+      <template v-slot:info>
+        <list-frame :list="list" @detail="detail"></list-frame>
+      </template>
+    </mobileMain>
+  </div>
+</template>
+
+<script>
+import listFrame from '../parts/list_2.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: column } = createNamespacedHelpers('column');
+const { mapActions: news } = createNamespacedHelpers('news');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+  },
+  data: function() {
+    return {
+      column_id: '',
+      list: [],
+      total: 0,
+      limit: 10,
+    };
+  },
+  async created() {
+    await this.searchOther();
+    await this.search();
+  },
+  methods: {
+    ...column({ columnQuery: 'query' }),
+    ...news(['query']),
+    async search({ skip = 0, limit = this.limit, searchName, ...info } = {}) {
+      if (searchName) info.title = searchName;
+      info.column_id = this.column_id;
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 查询栏目
+    async searchOther() {
+      let res = await this.columnQuery();
+      if (this.$checkRes(res)) {
+        this.$set(this, `column_id`, res.data.find(i => i.site == 'tgtg').id);
+      }
+    },
+    // 详情
+    detail(data) {
+      this.$router.push({ path: '/user/notice/detail', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 125 - 0
src/views/user/parts/detail_2.vue

@@ -0,0 +1,125 @@
+<template>
+  <div id="detail_2">
+    <van-row>
+      <van-col span="24" class="main">
+        <van-col span="24" class="one">
+          {{ form.title }}
+        </van-col>
+        <van-col span="24" class="two">
+          <span>信息来源:{{ form.origin || '暂无' }}</span>
+          <span>发布时间:{{ form.publish_time || '暂无' }}</span>
+        </van-col>
+        <van-col span="24" v-if="form.picture" class="thr">
+          <van-image :src="form.picture">
+            <div slot="error" class="image-slot">
+              <i class="el-icon-picture-outline"></i>
+            </div>
+          </van-image>
+        </van-col>
+        <van-col span="24" v-if="form.video" class="four">
+          <video
+            autoplay="autoplay"
+            controls="controls"
+            preload="meta"
+            x-webkit-airplay="true"
+            webkit-playsinline="true"
+            playsinline="true"
+            x5-video-player-type="h5"
+            x5-video-player-fullscreen="true"
+            controlsList="nodownload"
+            :src="form.video"
+            loop="loop"
+          >
+            <source src="movie.ogg" type="video/ogg" />
+            <source src="movie.mp4" type="video/mp4" />
+          </video>
+        </van-col>
+        <van-col span="24" class="five">
+          <p v-html="form.content"></p>
+        </van-col>
+        <van-col span="24" class="six" v-if="form.filepath">
+          <p>附件:</p>
+          <el-link :href="form.filepath" :underline="false">{{ form.filepathname || '附件下载' }}</el-link>
+        </van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'detail_2',
+  props: {
+    form: { type: Object },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  padding: 0 10px 10px 10px;
+  .one {
+    text-align: center;
+    font-size: 16px;
+    padding: 15px 0;
+    font-weight: bold;
+  }
+  .two {
+    font-size: 14px;
+    text-align: center;
+    color: #666;
+    margin: 0 0 10px 0;
+    span {
+      padding: 0 10px;
+    }
+  }
+  .thr {
+    margin: 0 0 10px 0;
+    .van-image {
+      width: 100%;
+      height: 200px;
+    }
+  }
+  .four {
+    margin: 0 0 10px 0;
+    video {
+      width: 100%;
+      height: 200px;
+    }
+  }
+  .five {
+    margin: 0 0 10px 0;
+    p {
+      /deep/table {
+        width: 100% !important;
+      }
+    }
+  }
+  .six {
+    p {
+      font-weight: bold;
+      margin: 0 0 5px 0;
+    }
+  }
+}
+</style>

+ 0 - 0
src/views/user/parts/detail_3.vue


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.