dusenyao 4 rokov pred
rodič
commit
c98545e184

+ 0 - 1
src/api/app.js

@@ -42,7 +42,6 @@ export function GetWordPack(Parameter) {
 /**
  * @description 上传文件
  * @param {String} SecurityLevel 保密级别
- * @returns
  */
 export function fileUpload(SecurityLevel, file) {
   const { session_id, user_code, user_type } = getUserInfo();

+ 3 - 0
src/icons/svg/brush-shape.svg

@@ -0,0 +1,3 @@
+<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0.736158 11.3318C1.03003 11.3318 1.18009 11.1692 1.23011 10.9191L2.31181 5.47939H5.68819L6.76989 10.9191C6.81991 11.1692 6.96997 11.3318 7.26384 11.3318C7.68276 11.3318 7.83283 11.0317 7.7578 10.694L6.64484 5.11049C6.56355 4.72283 6.23842 4.43521 5.85701 4.43521H5.85076L4.53147 0.533607C4.44393 0.277251 4.26886 0.133442 4.01251 0.133442C3.73739 0.133442 3.54982 0.277251 3.46228 0.533607L2.14299 4.43521H2.13673C1.76158 4.43521 1.43645 4.72283 1.35516 5.11049L0.242205 10.694C0.167174 11.0379 0.317236 11.3318 0.736158 11.3318Z" fill="#242424"/>
+</svg>

+ 3 - 0
src/icons/svg/clear.svg

@@ -0,0 +1,3 @@
+<svg width="11" height="14" viewBox="0 0 11 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9.62747 9.13684L9.62747 12.382C9.62747 13.3282 9.05713 13.872 8.08446 13.8808L7.63792 13.8852C7.76613 12.8905 7.18695 10.9849 6.71388 10.9893C6.63872 10.9849 6.55472 11.0601 6.55472 11.175C6.55472 12.1831 6.39113 13.0098 5.66605 13.8852L1.36861 13.8852C0.550677 13.8897 -0.00639853 13.0408 0.555098 12.0902C1.35976 10.755 1.74883 9.88845 2.00526 9.13684H9.62747ZM5.96669 0.00256889C7.28864 -0.00185207 8.15078 0.851445 8.15078 2.15129C8.15078 3.16817 7.50528 4.79519 7.29748 5.7546L9.3003 5.75018C9.93696 5.75018 10.3216 6.13482 10.3172 6.74053L10.3128 7.46119C10.3083 8.08459 9.94138 8.44271 9.3003 8.44713H2.63308C1.992 8.44271 1.62062 8.08017 1.62062 7.45235L1.62062 6.74495C1.62062 6.12598 1.992 5.74575 2.63308 5.75018L4.63148 5.75017C4.43252 4.79961 3.7826 3.16817 3.7826 2.15129C3.78702 0.847025 4.64474 -0.00185245 5.96669 0.00256889ZM6.52819 1.6959C6.24523 1.41294 5.78984 1.40852 5.50688 1.69148C5.22392 1.97444 5.22392 2.43425 5.50688 2.71721C5.78542 2.99575 6.24523 2.99575 6.52819 2.71279C6.81115 2.42983 6.80673 1.97444 6.52819 1.6959Z" fill="white"/>
+</svg>

+ 5 - 0
src/icons/svg/clock.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M24 0H0V24H24V0Z" fill="white" fill-opacity="0.01"/>
+<path d="M12 22C17.5229 22 22 17.5229 22 12C22 6.47715 17.5229 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5229 6.47715 22 12 22Z" stroke="#333333" stroke-width="1.5" stroke-linejoin="round"/>
+<path d="M12.0041 6L12.0035 12.0044L16.2432 16.2441" stroke="#333333" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 5 - 0
src/icons/svg/task-notification.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M24 0H0V24H24V0Z" fill="white" fill-opacity="0.01"/>
+<path d="M2 19H22M5 19V9C5 5.134 8.134 2 12 2C15.866 2 19 5.134 19 9V19H5Z" stroke="#333333" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12 22C13.3807 22 14.5 20.8807 14.5 19.5V19H9.5V19.5C9.5 20.8807 10.6193 22 12 22Z" stroke="#333333" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 5 - 0
src/icons/svg/teaching-manager.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12 7C10.8971 7 10 7.89712 10 9C10 10.1029 10.8971 11 12 11C13.1029 11 14 10.1029 14 9C14 7.89712 13.1029 7 12 7Z" stroke="#FF9900" stroke-width="1.5"/>
+<path d="M7.30148 5.31565L11.4234 2.40688C11.7691 2.16294 12.2309 2.16294 12.5766 2.40688L16.6985 5.31565C16.964 5.50302 17.122 5.80773 17.122 6.1327V11.3614C17.122 11.9137 17.5697 12.3614 18.122 12.3614H21C21.5523 12.3614 22 12.8092 22 13.3614V21C22 21.5523 21.5523 22 21 22H3C2.44772 22 2 21.5523 2 21V13.3614C2 12.8092 2.44771 12.3614 3 12.3614H5.87805C6.43033 12.3614 6.87805 11.9137 6.87805 11.3614V6.1327C6.87805 5.80773 7.03596 5.50302 7.30148 5.31565Z" stroke="#2C2C2C" stroke-width="1.5"/>
+<rect x="9" y="14" width="6" height="8" rx="1" stroke="#2C2C2C" stroke-width="1.5"/>
+</svg>

+ 35 - 0
src/layouts/components/BreadCrumb.vue

@@ -0,0 +1,35 @@
+<template>
+  <div v-show="isShow" class="breadcrumb">
+    <div class="breadcrumb-container">fsadfsa</div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'BreadCrumb',
+  props: {
+    isShow: {
+      default: false,
+      type: Boolean
+    }
+  },
+  watch: {
+    $router: {}
+  },
+  methods: {}
+};
+</script>
+
+<style lang="scss">
+.breadcrumb {
+  width: 100%;
+  height: 56px;
+  line-height: 56px;
+  background-color: #f6ecdd;
+  &-container {
+    width: $basicWidth;
+    min-width: $basicWidth;
+    margin: 0 auto;
+  }
+}
+</style>

+ 0 - 87
src/layouts/components/Header.vue

@@ -1,87 +0,0 @@
-<template>
-  <div class="fixed-header">
-    <el-header>
-      <el-row>
-        <el-col :span="1">
-          <span>logo</span>
-        </el-col>
-        <el-col :span="2" :push="20">
-          <el-dropdown @command="setI18nLang">
-            <span class="el-dropdown-link">
-              切换语言<i class="el-icon-arrow-down el-icon--right"></i>
-            </span>
-            <el-dropdown-menu slot="dropdown">
-              <el-dropdown-item
-                v-for="list in languageList"
-                :key="list.language_type"
-                :command="list.language_type"
-              >
-                {{ list.language_name }}
-              </el-dropdown-item>
-            </el-dropdown-menu>
-          </el-dropdown>
-        </el-col>
-        <el-col :span="1" :push="20">
-          <div class="sign-out" @click="signOut">{{ userType }}退出</div>
-        </el-col>
-      </el-row>
-    </el-header>
-  </div>
-</template>
-
-<script>
-import { GetLanguageList } from '@/api/app';
-import { setI18nLang } from '@/utils/i18n';
-
-export default {
-  props: {
-    userType: {
-      type: String,
-      default: ''
-    }
-  },
-  data() {
-    return {
-      languageList: []
-    };
-  },
-  mounted() {
-    const that = this;
-    GetLanguageList().then(function (data) {
-      that.languageList = data.language_list;
-    });
-  },
-  methods: {
-    async signOut() {
-      await this.$store.dispatch('user/signOut');
-      this.$router.push(`/login?redirect=${this.$route.fullPath}`);
-    },
-    async setI18nLang(lang) {
-      await setI18nLang(lang);
-      // 为了触发 created 生命周期的获取单词列表事件
-      this.$router.go(0);
-    }
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.fixed-header {
-  position: fixed;
-  top: 0;
-  right: 0;
-  width: 100%;
-  z-index: 9;
-
-  .el-header {
-    height: $header-h !important;
-    line-height: $header-h;
-    text-align: center;
-
-    .sign-out {
-      cursor: pointer;
-      font-size: 12px;
-    }
-  }
-}
-</style>

+ 168 - 0
src/layouts/components/LayoutHeader.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class="header">
+    <div class="header-container">
+      <div class="header-container-left">
+        <span class="logo">LOGO</span>
+        <span class="home">主页</span>
+        <span class="menu">
+          <el-dropdown placement="top">
+            <span class="el-dropdown-link">
+              <span class="el-dropdown-current-name">{{ curDropdown }}</span
+              ><i class="el-icon-arrow-down el-icon--right"></i>
+            </span>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item v-for="item in dropdownList" :key="item.id">
+                <svg-icon icon-class="teaching-manager" /><span>{{ item.name }}</span>
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </span>
+      </div>
+      <div class="header-container-right">
+        <!-- <el-dropdown @command="setI18nLang">
+            <span class="el-dropdown-link">
+              切换语言<i class="el-icon-arrow-down el-icon--right"></i>
+            </span>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item
+                v-for="list in languageList"
+                :key="list.language_type"
+                :command="list.language_type"
+              >
+                {{ list.language_name }}
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown> -->
+        <el-input prefix-icon="el-icon-search" placeholder="输入关键字搜索" />
+        <el-avatar icon="el-icon-user" />
+        <el-dropdown class="header-container-right-name" placement="bottom">
+          <span>NAME</span>
+          <el-dropdown-menu slot="dropdown">
+            <el-dropdown-item @click.native="signOut">退出</el-dropdown-item>
+          </el-dropdown-menu>
+        </el-dropdown>
+        <el-badge :is-dot="isDot" class="header-container-right-bell">
+          <i class="el-icon-bell"></i>
+        </el-badge>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { GetLanguageList } from '@/api/app';
+import { setI18nLang } from '@/utils/i18n';
+
+export default {
+  name: 'LayoutHeader',
+  props: {
+    userType: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      curDropdown: '教学管理系统',
+      dropdownList: [
+        {
+          id: 1,
+          name: '教学管理系统',
+          icon: 'el-icon-search'
+        }
+      ],
+      isDot: false,
+      languageList: []
+    };
+  },
+  computed: {
+    realName() {
+      let user = this.$store.state.user;
+      return user.user_real_name ? user.user_real_name : user.user_name;
+    }
+  },
+  mounted() {
+    GetLanguageList().then(data => {
+      this.languageList = data.language_list;
+    });
+  },
+  methods: {
+    async signOut() {
+      await this.$store.dispatch('user/signOut');
+      this.$router.push(`/login?redirect=${this.$route.fullPath}`);
+    },
+    async setI18nLang(lang) {
+      await setI18nLang(lang);
+      // 为了触发 created 生命周期的获取单词列表事件
+      this.$router.go(0);
+    }
+  }
+};
+</script>
+
+<style lang="scss">
+.header {
+  height: $header-h;
+  line-height: $header-h;
+  padding: 0 24px;
+  box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.15);
+  background-color: #fff;
+
+  &-container {
+    height: $header-h;
+    display: flex;
+    justify-content: space-between;
+
+    .logo {
+      font-size: 30px;
+      font-weight: 600;
+      margin: 30px;
+    }
+
+    .home {
+      vertical-align: top;
+      margin-right: 24px;
+    }
+
+    .menu {
+      @extend .home;
+
+      .el-dropdown {
+        height: 48px;
+
+        &-current-name {
+          color: $buttonColor;
+        }
+      }
+    }
+
+    &-right {
+      .el-input {
+        width: 300px;
+        margin-right: 40px;
+      }
+
+      .el-avatar {
+        vertical-align: middle;
+      }
+
+      &-name {
+        height: 48px;
+        vertical-align: top;
+        margin-left: 8px;
+        cursor: pointer;
+      }
+
+      & > &-bell {
+        margin-left: 26px;
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss" scoped>
+.el-dropdown-menu .svg-icon {
+  margin-right: 18px;
+}
+</style>

+ 28 - 11
src/layouts/index.vue

@@ -1,6 +1,9 @@
 <template>
-  <div class="app-container">
-    <Header :user-type="userType" />
+  <div class="app">
+    <div class="app-top">
+      <layout-header :user-type="userType" />
+      <bread-crumb :is-show="isShow" />
+    </div>
     <section class="app-main">
       <transition name="fade-transfrom" mode="out-in">
         <router-view :key="key"></router-view>
@@ -10,12 +13,19 @@
 </template>
 
 <script>
-import Header from './components/Header';
+import LayoutHeader from './components/LayoutHeader';
+import BreadCrumb from './components/BreadCrumb';
 
 export default {
   name: 'Layout',
   components: {
-    Header
+    LayoutHeader,
+    BreadCrumb
+  },
+  data() {
+    return {
+      isShow: false
+    };
   },
   computed: {
     key() {
@@ -28,16 +38,23 @@ export default {
 };
 </script>
 
-<style lang="scss" scoped>
-.app-container {
+<style lang="scss">
+.app {
   height: 100%;
   padding-top: $header-h;
-  .app-main {
-    height: 100%;
-    min-height: calc(100vh - #{$header-h});
+
+  &-top {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+  }
+
+  &-main {
     width: 100%;
-    position: relative;
-    // background-color: #f9f9f9;
+    height: calc(100vh - #{$header-h});
+    overflow-y: auto;
+    background-color: $bacColor;
   }
 }
 </style>

+ 2 - 1
src/store/modules/user.js

@@ -4,13 +4,14 @@ import { resetRouter } from '@/router';
 import { login, updateLanguageType } from '@/api/user';
 
 const getDefaultSate = () => {
-  const { session_id, user_code, user_real_name, user_type } = getUserInfo();
+  const { session_id, user_code, user_real_name, user_type, user_name } = getUserInfo();
 
   return {
     session_id: session_id,
     user_code: user_code,
     user_real_name: user_real_name,
     user_type: user_type,
+    user_name: user_name,
     language_type: localStorage.getItem('language_type') || 'ZH'
   };
 };

+ 3 - 2
src/styles/index.scss

@@ -6,9 +6,10 @@ body {
   -moz-osx-font-smoothing: grayscale;
   -webkit-font-smoothing: antialiased;
   text-rendering: optimizeLegibility;
+  overflow: hidden;
   /* stylelint-disable-next-line */
-  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial,
-    sans-serif;
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, '微软雅黑',
+    Arial, sans-serif;
 }
 
 label {

+ 3 - 2
src/styles/mixin.scss

@@ -1,7 +1,8 @@
 @mixin container {
-  width: 1000px;
+  width: $basicWidth;
+  min-width: $basicWidth;
   margin: 0 auto;
-  padding: 30px 0 4px;
+  padding: 32px 0 4px;
 }
 
 @mixin el-tag {

+ 5 - 3
src/styles/variables.scss

@@ -1,3 +1,5 @@
-$header-h: 49px;
-$bacColor: #c4c4c4;
-$borderColor: #999;
+$header-h: 74px;
+$basicWidth: 1200px;
+
+$bacColor: #f5f5f5;
+$buttonColor: #f90;

+ 1 - 1
src/utils/auth.js

@@ -1,6 +1,6 @@
 import Cookies from 'js-cookie';
 
-const userInfoList = ['user_code', 'session_id', 'user_type', 'user_real_name'];
+const userInfoList = ['user_code', 'session_id', 'user_type', 'user_real_name', 'user_name'];
 
 export function getUserInfo() {
   let userInfo = {};

+ 17 - 0
src/utils/request.js

@@ -1,4 +1,5 @@
 import axios from 'axios';
+import store from '@/store';
 import { Message } from 'element-ui';
 import { getUserInfo } from '@/utils/auth';
 
@@ -38,6 +39,22 @@ service.interceptors.response.use(
         type: 'error',
         duration: 3 * 1000
       });
+
+      return Promise.reject(new Error(res.error || 'Error'));
+    }
+
+    // 无效的操作用户
+    if (res.status === -1) {
+      Message({
+        message: res.error,
+        type: 'error',
+        duration: 3 * 1000
+      });
+
+      store.dispatch('user/resetSessionID').then(() => {
+        location.reload();
+      });
+
       return Promise.reject(new Error(res.error || 'Error'));
     }
     return res;

+ 239 - 7
src/views/live/index.vue

@@ -10,14 +10,44 @@
     </div>
     <div class="live-container">
       <div class="live-container-left">
-        <div id="draw-parent"></div>
+        <div id="draw-parent">
+          <div v-show="isDrawSetting" class="draw-setting">
+            <span class="brush-shape">
+              <svg-icon icon-class="brush-shape" />
+            </span>
+            <span
+              v-for="item in drawColorList"
+              :key="item"
+              :class="['draw-color', item === '#FF4747' ? 'current' : '']"
+              :style="{ 'background-color': item }"
+            ></span>
+            <span v-for="item in drawThicknessList" :key="item" class="draw-thickness">
+              <span :style="{ width: item * 2 + 'px', height: item * 2 + 'px' }"></span>
+            </span>
+            <span class="brush-clear">
+              <svg-icon icon-class="clear" />
+            </span>
+          </div>
+        </div>
         <div class="button-group">
           <div>
             <el-dropdown placement="top" @command="invite">
               <el-button>语音连麦</el-button>
               <el-dropdown-menu slot="dropdown">
                 <el-dropdown-item
-                  v-for="item in speakData.onlineUsers"
+                  v-for="item in roomContext.onlineUsers"
+                  :key="item.id"
+                  :command="item.uid"
+                >
+                  {{ item.name }}
+                </el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+            <el-dropdown placement="top" @command="invite">
+              <el-button>视屏连麦</el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item
+                  v-for="item in roomContext.onlineUsers"
                   :key="item.id"
                   :command="item.uid"
                 >
@@ -25,15 +55,38 @@
                 </el-dropdown-item>
               </el-dropdown-menu>
             </el-dropdown>
-            <el-button>视频连麦</el-button>
             <!-- <el-button>群组讨论</el-button>
             <el-button>推送课件</el-button> -->
-            <el-button>屏幕画笔</el-button>
+            <el-button @click="showDrawSetting">屏幕画笔</el-button>
           </div>
           <div>
             <el-button @click="publishShareStream">共享屏幕</el-button>
           </div>
         </div>
+        <div class="live-container-left-chat">
+          <div class="chat-top">
+            <span>聊天窗口</span>
+            <a @click="chatBans">群体禁言</a>
+          </div>
+          <div class="chat-window">
+            <ul class="chat-window-ul">
+              <li v-for="(item, i) in chatList" :key="i">
+                <div class="msg-normal">
+                  <span>{{ item.username }}: </span>
+                  <span>{{ item.msg }}</span>
+                </div>
+              </li>
+            </ul>
+          </div>
+          <div class="chat-speak">
+            <el-input
+              v-model="msg"
+              placeholder="输入发言"
+              maxlength="400"
+              @keydown.enter.native="sendMsg"
+            ></el-input>
+          </div>
+        </div>
       </div>
       <div class="live-container-right">
         <div class="live-teacher-lens">
@@ -53,11 +106,11 @@ export default {
     return {
       userid: '53A22FC29AD2216D',
       roomid: 'DCDD385394BFEDEB9C33DC5901307461',
-      sessionid: 'CF58A9D16DA6EE252C274DD0F18E7307C048EE85FE61DFFC251D7324CD2B85D0',
+      sessionid: '958244269B68153D59A89B76263163F718F9C0729ED1220093D5B392FC86FFF6',
       rtc: null,
       roomData: {
         desc: '直播间标题',
-        name: '教师姓名',
+        name: '姓名',
         user: {
           id: '',
           name: '',
@@ -71,7 +124,12 @@ export default {
       },
       loadedNumber: 0,
       speakData: {},
-      roomContext: {}
+      roomContext: {},
+      msg: '',
+      chatList: [],
+      isDrawSetting: false,
+      drawColorList: ['#FF4747', '#343434', '#628EFF', '#FFCA0E'],
+      drawThicknessList: ['1', '3', '5']
     };
   },
   watch: {
@@ -85,6 +143,29 @@ export default {
   created() {
     this.downloadWebSDK();
   },
+  mounted() {
+    document.addEventListener(
+      'click',
+      e => {
+        let target = e.target;
+        let isHasClass = false;
+        do {
+          if (target === null) {
+            break;
+          }
+          if (target.className === 'draw-setting') {
+            isHasClass = true;
+          }
+          target = target.parentElement;
+        } while (!isHasClass);
+
+        if (!isHasClass) {
+          this.isDrawSetting = false;
+        }
+      },
+      true
+    );
+  },
   methods: {
     // 加载直播所需 SDK,加载完成后才能初始化
     downloadWebSDK() {
@@ -166,6 +247,32 @@ export default {
 
     invite(uid) {
       common.invite(uid);
+    },
+
+    sendMsg() {
+      common.sendMsg(this.msg);
+      this.msg = '';
+    },
+
+    chatBans() {
+      common.roomUpdate({
+        allow_chat: false,
+        roomUpdateSuccess: function (data) {
+          console.log(data, '房间模板配置更新请求成功!');
+        },
+        roomUpdateFailed: function (data) {
+          console.log(data, '房间模板配置更新请求失败! 请稍后再试!');
+        }
+      });
+    },
+
+    // 画笔变更
+    drawChange(action, value) {
+      common.drawChange(action, value);
+    },
+
+    showDrawSetting() {
+      this.isDrawSetting = !this.isDrawSetting;
     }
   }
 };
@@ -180,13 +287,16 @@ export default {
   &-page-title {
     font-size: 12px;
   }
+
   &-desc {
     margin: 17px 0 8px;
   }
+
   &-info {
     display: flex;
     justify-content: space-between;
   }
+
   &-name {
     opacity: 0.4;
     line-height: 30px;
@@ -207,11 +317,125 @@ export default {
         height: 331px;
         margin-bottom: 14px;
         border: 1px solid #ccc;
+        position: relative;
+        background-color: #3d3938;
+
+        // 设置屏幕画笔
+        .draw-setting {
+          position: absolute;
+          bottom: 13px;
+          left: 22px;
+          z-index: 9999;
+          background-color: #a0a0a0;
+          padding: 6px;
+          border-radius: 40px;
+          height: 40px;
+          width: 275px;
+          line-height: 28px;
+
+          & > span {
+            display: inline-block;
+            text-align: center;
+            margin-right: 10px;
+          }
+
+          & > span.brush-shape {
+            width: 28px;
+            height: 28px;
+            border-radius: 50%;
+            background-color: #fff;
+            cursor: pointer;
+          }
+
+          .draw-color {
+            position: relative;
+            top: 5px;
+            height: 18px;
+            width: 18px;
+            border-radius: 50%;
+            cursor: pointer;
+          }
+
+          .current::after {
+            content: '';
+            position: absolute;
+            bottom: -7px;
+            left: 7px;
+            width: 4px;
+            height: 4px;
+            border-radius: 50%;
+            background-color: #292929;
+          }
+
+          .draw-thickness {
+            height: 18px;
+            width: 18px;
+            cursor: pointer;
+            display: inline-flex;
+            flex-direction: column;
+            justify-content: center;
+            vertical-align: middle;
+            align-items: center;
+
+            span {
+              border-radius: 50%;
+              background-color: #000;
+            }
+          }
+
+          & > .brush-clear {
+            width: 28px;
+            height: 28px;
+            border-radius: 50%;
+            background-color: #666;
+            cursor: pointer;
+            margin-right: 0;
+          }
+        }
       }
+
       .button-group {
         display: flex;
         justify-content: space-between;
       }
+
+      // 聊天窗口
+      &-chat {
+        margin-top: 15px;
+        height: 278px;
+        border: 1px solid #ccc;
+        border-radius: 8px;
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+
+        .chat-top {
+          display: flex;
+          justify-content: space-between;
+          padding: 15px 15px 10px;
+          border-bottom: 1px solid #e6e6e6;
+        }
+
+        .chat-window {
+          position: relative;
+          width: 100%;
+          height: 100%;
+          overflow: hidden;
+
+          &-ul {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            overflow: auto;
+
+            .msg-normal {
+              padding: 7px 16px;
+            }
+          }
+        }
+      }
     }
 
     &-right {
@@ -227,5 +451,13 @@ export default {
     border-radius: 8px;
     padding: 7px 12px;
   }
+
+  .el-dropdown + .el-dropdown {
+    margin-left: 10px;
+  }
+
+  .el-dropdown + .el-button {
+    margin-left: 10px;
+  }
 }
 </style>

+ 52 - 27
src/views/live/live.js

@@ -45,6 +45,13 @@ export function publishStream() {
 }
 
 /**
+ * @description 查询直播是否开启
+ */
+export function getLiveStat(obj) {
+  rtc.getLiveStat(obj);
+}
+
+/**
  * @method startLive
  * @description 开启直播
  */
@@ -83,12 +90,23 @@ export function createLocalStream() {
       console.log('创建本地流成功', stream);
       // 创建本地流成功,将流展示到id为 live 的dom元素盒子中
       stream.show('live');
-      startLive();
+      getLiveStat({
+        success: function (data) {
+          if (!data.started) {
+            startLive();
+          }
+        },
+        fail: function (str) {
+          startLive();
+        }
+      });
     },
     fail: function (data) {
       // 创建本地流失败,应用层处理
-      console.log(data);
-      alert('创建本地流失败');
+      Message({
+        type: 'error',
+        message: '创建本地流失败:' + data
+      });
     }
   });
 }
@@ -106,7 +124,7 @@ export function initListener(vue) {
     });
     vue.roomData = data;
     let canvasInitData = {
-      allowDraw: true, // 是否具有书写画笔权限(讲师权限) true / false
+      allowDraw: data.user.role === 'presenter', // 是否具有书写画笔权限(讲师权限) true / false
       id: 'draw-parent',
       pptDisplay: 0 // 文档展示方式。默认0,按窗口  1, 按宽度
     };
@@ -145,10 +163,6 @@ export function initListener(vue) {
 
   rtc.on('publish_stream', str => {
     console.log('直播已开启', str);
-    Message({
-      message: '直播已开启',
-      type: 'success'
-    });
   });
 
   rtc.on('end_stream', str => {
@@ -199,10 +213,21 @@ export function initListener(vue) {
   // 监听聊天事件
   rtc.on('chat_message', data => {
     let dat = JSON.parse(data);
-    // data数据需要解析
+    console.log(dat);
+    // 敏感词过滤:如果发送的聊天消息被系统判定包含敏感词,则只有发送者能收到本条消息,房间内其他人都不会收到这条聊天消息。
+    // 如果返回消息中有 isFilterChat 字段(消息不包含敏感词返回数据中无isFilterChat字段),且isFilterChat的值为1,则说明该消息包含敏感字,除发送者外其他人不会收到这条消息。
     if (dat.isFilterChat && dat.isFilterChat === 1) {
       return;
     }
+    vue.chatList.push(dat);
+  });
+
+  rtc.on('allowChatChange', function (data) {
+    let msg = data.settings.allow_chat ? '开言' : '禁言';
+    Message({
+      type: 'success',
+      message: `全体${msg}成功`
+    });
   });
 }
 
@@ -212,11 +237,17 @@ export function initListener(vue) {
  */
 export function stopLive() {
   rtc.stopLive({
-    success(data) {
-      alert('结束直播成功:' + JSON.stringify(data));
+    success() {
+      Message({
+        type: 'success',
+        message: '结束直播成功'
+      });
     },
     fail(data) {
-      alert('结束直播失败:' + JSON.stringify(data));
+      Message({
+        type: 'error',
+        message: '结束直播失败:' + JSON.stringify(data)
+      });
     }
   });
 }
@@ -310,21 +341,6 @@ export function playVideo() {
 }
 
 /**
- * @description 查询直播是否开启
- */
-export function getLiveStat() {
-  rtc.getLiveStat({
-    success: function (data) {
-      console.log('直播开启状态');
-      console.log(data);
-    },
-    fail: function (str) {
-      console.log('直播关闭状态或查询直播失败', str);
-    }
-  });
-}
-
-/**
  * @description 推送桌面共享
  */
 export function publishShareStream() {
@@ -407,3 +423,12 @@ export function sendMsg(msg) {
 export function roomUpdate(option) {
   rtc.roomUpdate(option);
 }
+
+// 文档
+
+export function drawChange(action, value) {
+  rtc.drawChange({
+    action,
+    value
+  });
+}

+ 1 - 1
src/views/login/index.vue

@@ -85,7 +85,7 @@ export default {
     return {
       loginForm: {
         user_name: 'teacher1',
-        password: '1234567a',
+        password: '123456',
         user_type: ''
       },
       loginRules: {

+ 0 - 1
src/views/teacher/add_course/AddCourse.vue

@@ -141,7 +141,6 @@ export default {
       this.$refs.form.validate(valid => {
         if (valid) {
           createCourse(this.form).then(response => {
-            console.log(response);
             if (response.status === 1) {
               this.$message.success('创建课程成功!');
               this.$router.push('/main');

+ 7 - 2
src/views/teacher/main/CurriculaManager.vue → src/views/teacher/main/CurriculaList.vue

@@ -56,7 +56,7 @@ import { updateWordPack } from '@/utils/i18n';
 import { pageQueryMyCourseList } from '@/api/table';
 
 export default {
-  name: 'CurriculaManager',
+  name: 'CurriculaList',
   data() {
     return {
       input: '',
@@ -78,7 +78,7 @@ export default {
         {
           id: '3',
           name: '开课时间',
-          sortType: 'onclassTime',
+          sortType: 'onClassTime',
           order: false
         }
       ],
@@ -146,6 +146,7 @@ $main-w: 953px;
       width: 97px;
       height: 52px;
     }
+
     .el-button + .el-button {
       margin-left: 0;
     }
@@ -188,16 +189,20 @@ $main-w: 953px;
       background-color: #eee;
       padding: 20px 20px 0;
     }
+
     &-value {
       display: flex;
       justify-content: space-between;
     }
+
     .date-horizontal-bar {
       margin: 0 15px;
     }
+
     &-create {
       padding-top: 20px;
     }
+
     &-teacher {
       margin-left: 40px;
     }

+ 0 - 199
src/views/teacher/main/TaskKanban.vue

@@ -1,199 +0,0 @@
-<template>
-  <div class="task-kanban">
-    <div class="left-container">
-      <div class="panel">
-        <!-- 用户信息 -->
-        <div>
-          <el-row class="avatar">
-            <el-col :span="6"><el-avatar :size="65"></el-avatar></el-col>
-            <el-col :span="18">
-              <el-row>
-                <el-col :span="24" class="teacher-name">教师姓名</el-col>
-              </el-row>
-              <el-row>
-                <el-col :span="24" class="corporate-name">公司名称</el-col>
-              </el-row>
-            </el-col>
-          </el-row>
-        </div>
-        <!--日历-->
-        <div class="calendar">
-          <el-calendar v-model="dateTime"></el-calendar>
-        </div>
-        <!-- 数据化面板 -->
-        <div class="data-panel"></div>
-        <!-- 消息中心 -->
-        <div class="message-center">
-          <div>消息中心</div>
-        </div>
-      </div>
-    </div>
-    <!-- 任务看板 -->
-    <div class="right-container">
-      <div
-        v-for="item in cs_item_list"
-        :key="item.id"
-        class="cs-item"
-        :style="{
-          'background-color':
-            item.finish_status === 0 ? '#E7F8E1' : item.finish_status === 1 ? '#FFFBD8' : '#F7DFDF'
-        }"
-        @click.prevent="routerPush(item.id)"
-      >
-        <el-row class="task-date">
-          <el-col :span="12">{{ item.date_stamp }}&nbsp;{{ item.minute_space }}</el-col>
-          <el-col :span="12" :style="{ 'text-align': 'right' }">
-            {{
-              item.finish_status === 0
-                ? $t('Learn_Task_No_Started')
-                : item.finish_status === 1
-                ? $t('Learn_Task_Has_Begun')
-                : $t('Learn_Task_Over')
-            }}
-          </el-col>
-        </el-row>
-        <el-row class="task-name">
-          <el-col :span="24">{{ item.name }}</el-col>
-        </el-row>
-        <el-row class="task-class-name">
-          <el-col :span="24">{{ item.class_name }}</el-col>
-        </el-row>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import { getMyCsItemsDateTeacher } from '@/api/table';
-import { formatDate } from '@/utils';
-
-export default {
-  name: 'Taskkanban',
-  data() {
-    return {
-      date: new Date(),
-      cs_item_list: {}
-    };
-  },
-  computed: {
-    dateTime: {
-      get: function () {
-        return this.date;
-      },
-      set: function (newValue) {
-        this.date = newValue;
-        this.getCSItemList();
-      }
-    }
-  },
-  mounted() {
-    this.getCSItemList();
-  },
-  methods: {
-    getCSItemList() {
-      getMyCsItemsDateTeacher({ date_stamp: formatDate(this.date) }).then(response => {
-        this.cs_item_list = response.cs_item_list;
-      });
-    },
-    // 跳转到讲次详情
-    routerPush(id) {
-      this.$router.push({ path: `/cs_item_detail/index/${id}` });
-    }
-  }
-};
-</script>
-
-<style lang="scss">
-.task-kanban {
-  .el-calendar__header .el-button {
-    padding: 7px 10px;
-  }
-  .el-calendar-table div.el-calendar-day {
-    height: 29px;
-  }
-  .el-calendar__body {
-    thead th {
-      padding: 0 0 4px 0;
-    }
-  }
-}
-</style>
-
-<style lang="scss" scoped>
-@mixin panel {
-  font-size: 12px;
-  width: $left-item-h;
-  margin-top: 18px;
-  background-color: #c4c4c4;
-  padding: 24px 23px;
-}
-
-$left-item-h: 322px;
-$right-w: 641px;
-// 99px = 95px 顶部导航菜单高度 + 底部 4px
-$container-h: calc(100vh - #{$header-h} - 99px);
-
-.task-kanban {
-  min-height: $container-h;
-  overflow: hidden;
-  padding: 40px 0 4px;
-  display: flex;
-  .left-container {
-    width: 341px;
-    min-height: $container-h;
-    margin-right: 2px;
-    .panel {
-      height: 100%;
-      padding-right: 19px;
-      .avatar {
-        width: $left-item-h;
-      }
-      .corporate-name {
-        line-height: 22px;
-        margin-top: 8px;
-        color: #9b9b9b;
-      }
-      .teacher-name {
-        line-height: 22px;
-        margin-top: 8px;
-        font-weight: 700;
-      }
-    }
-    .calendar {
-      width: $left-item-h;
-      height: 268px;
-      overflow: hidden;
-    }
-    .data-panel {
-      @include panel;
-
-      height: 146px;
-    }
-    .message-center {
-      @include panel;
-
-      height: 122px;
-    }
-  }
-
-  .right-container {
-    width: $right-w + 18px;
-    min-width: $right-w + 18px;
-    padding-right: 18px;
-    min-height: $container-h;
-    .cs-item {
-      cursor: pointer;
-      margin-top: 24px;
-      width: $right-w;
-      height: 122px;
-      padding: 15px 30px;
-      .task-name {
-        padding: 20px 0;
-      }
-      .task-class-name {
-        color: #a4a4a4;
-      }
-    }
-  }
-}
-</style>

+ 114 - 0
src/views/teacher/main/TaskList.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class="task">
+    <div class="task-top">
+      <el-calendar v-model="date" />
+      <div class="task-top-notification">
+        <div class="task-top-notification-title">
+          <div>
+            <svg-icon icon-class="task-notification" />
+            <span class="title">任务通知</span>
+          </div>
+          <div></div>
+        </div>
+        <div class="task-top-notification-container"></div>
+      </div>
+    </div>
+    <div class="task-container">
+      <div class="task-container-title">
+        <svg-icon icon-class="clock" />
+        <span>时间轴</span>
+        <i class="el-icon-arrow-left" />
+        <i class="el-icon-arrow-right" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { getMyCsItemsDateTeacher } from '@/api/table';
+import { formatDate } from '@/utils';
+
+export default {
+  name: 'TaskList',
+  data() {
+    return {
+      date: new Date(),
+      cs_item_list: {}
+    };
+  },
+  computed: {
+    dateTime: {
+      get: function () {
+        return this.date;
+      },
+      set: function (newValue) {
+        this.date = newValue;
+        this.getCSItemList();
+      }
+    }
+  },
+  mounted() {
+    this.getCSItemList();
+  },
+  methods: {
+    getCSItemList() {
+      getMyCsItemsDateTeacher({ date_stamp: formatDate(this.date) }).then(response => {
+        this.cs_item_list = response.cs_item_list;
+      });
+    },
+    // 跳转到讲次详情
+    routerPush(id) {
+      this.$router.push({ path: `/cs_item_detail/index/${id}` });
+    }
+  }
+};
+</script>
+
+<style lang="scss">
+.task {
+  &-top {
+    display: flex;
+    justify-content: space-between;
+
+    .el-calendar {
+      width: 334px;
+      height: 300px;
+
+      &__header .el-button {
+        padding: 7px 10px;
+      }
+
+      &-table div.el-calendar-day {
+        height: 29px;
+      }
+
+      &__body {
+        thead th {
+          padding: 0 0 4px 0;
+        }
+      }
+    }
+
+    &-notification {
+      background-color: #fff;
+      flex: 1;
+      height: 300px;
+      margin-left: 16px;
+      padding: 20px 24px;
+
+      &-title {
+        height: 56px;
+      }
+    }
+  }
+
+  &-container {
+    margin-top: 16px;
+    border-radius: 8px;
+    width: 100%;
+    min-height: calc(100vh - 515px);
+    padding: 16px;
+    background-color: #fff;
+  }
+}
+</style>

+ 33 - 29
src/views/teacher/main/index.vue

@@ -1,13 +1,13 @@
 <template>
   <div class="main-container">
-    <div class="nav-menu">
+    <div class="menu">
       <span
-        v-for="item in menu"
+        v-for="item in menuList"
         :key="item.id"
         v-t="item.name"
-        :class="['tab-menu', { active: currentTab === item.tab }]"
+        :class="['menu-tab', { active: currentTab === item.tab }]"
         @click="currentTab = item.tab"
-      ></span>
+      />
     </div>
     <keep-alive>
       <component :is="currentTabComponent" />
@@ -16,29 +16,29 @@
 </template>
 
 <script>
-import TaskKanban from './TaskKanban';
-import CurriculaManager from './CurriculaManager';
+import TaskList from './TaskList';
+import CurriculaList from './CurriculaList';
 import { updateWordPack } from '@/utils/i18n';
 
 export default {
   name: 'Main',
   components: {
-    TaskKanban,
-    CurriculaManager
+    TaskList,
+    CurriculaList
   },
   data() {
     return {
-      currentTab: 'TaskKanban',
-      menu: [
+      currentTab: 'TaskList',
+      menuList: [
         {
           id: 1,
-          name: this.$i18n.t('Learn_Task_Panel'),
-          tab: 'TaskKanban'
+          name: '任务列表',
+          tab: 'TaskList'
         },
         {
           id: 2,
-          name: this.$i18n.t('Learn_Course_Management'),
-          tab: 'CurriculaManager'
+          name: '课程列表',
+          tab: 'CurriculaList'
         }
       ]
     };
@@ -70,24 +70,28 @@ export default {
 
   height: calc(100% - 19px);
 
-  .nav-menu {
-    width: 100%;
-    display: flex;
-    justify-content: center;
-    text-align: center;
+  // 切换菜单
+  .menu {
+    width: 318px;
+    border-radius: 30px;
+    margin-bottom: 32px;
+    background-color: #ebebeb;
 
-    .tab-menu {
-      min-width: 70px;
+    &-tab {
+      display: inline-block;
+      height: 48px;
+      line-height: 30px;
+      font-size: 20px;
+      padding: 9px 39px;
+      border-radius: 30px;
+      background-color: #ebebeb;
+      color: #9f9f9f;
       cursor: pointer;
-      padding-bottom: 4px;
-      &:first-child {
-        margin-right: 20px;
-      }
-      &:not(:first-child) {
-        margin-left: 20px;
-      }
+
       &.active {
-        border-bottom: 1px solid black;
+        color: #fff;
+        font-weight: 700;
+        background-color: $buttonColor;
       }
     }
   }

+ 0 - 2
stylelint.config.js

@@ -8,8 +8,6 @@ module.exports = {
     'order/order': ['at-rules', 'custom-properties', 'dollar-variables', 'declarations', 'rules'],
     // 指定2个空格
     indentation: 2,
-    // 多个选择器样式之间允许有空行
-    'rule-empty-line-before': null,
     // 样式块中不允许重复的属性
     'declaration-block-no-duplicate-properties': true,
     // 指定颜色函数使用传统符号隔开

+ 1 - 1
vue.config.js

@@ -16,7 +16,7 @@ let proxy = {};
 if (NODE_ENV === 'development') {
   proxy = {
     [process.env.VUE_APP_BASE_API]: {
-      target: 'http://gcls-learn.helxsoft.cn/',
+      target: 'http://gcls.helxsoft.cn/',
       changeOrigin: true,
       pathRewrite: {
         ['^' + process.env.VUE_APP_BASE_API]: ''