Browse Source

Merge branch 'develop' of http://52.130.252.100:10000/TEAMMODEL/TEAMModelOS into develop

jeff 1 year ago
parent
commit
65c43c4679

+ 1 - 0
TEAMModelOS.SDK/Models/Cosmos/CosmosEntity.cs

@@ -12,5 +12,6 @@ namespace TEAMModelOS.SDK.Models
         public string code { get; set; }
         public string pk { get; set; }
         public int? ttl { get; set; } = -1;
+        public long? _ts { get; set; } = -1;
     }
 }

+ 27 - 23
TEAMModelOS.SDK/Models/Cosmos/School/Debate.cs

@@ -53,10 +53,7 @@ namespace TEAMModelOS.SDK.Models
         /// 回复记录
         /// </summary>
         public List<DebateReply> replies { get; set; } = new List<DebateReply>();
-        /// <summary>
-        /// 点赞记录
-        /// </summary>
-        public List<LikeRcd> likes { get; set; } = new List<LikeRcd>();
+      
         /// <summary>
         /// 允许回复的字数
         /// </summary>
@@ -70,7 +67,7 @@ namespace TEAMModelOS.SDK.Models
         /// </summary>
         public long expire { get; set; } = 0;
         /// <summary>
-        /// 话题创建的来源  默认的normal, classvideo,ability,uploadscore
+        /// 话题创建的来源  默认的normal, classvideo,ability,uploadscore,homework,course
         /// </summary>
         public string source { get; set; } = "normal";
         /// <summary>
@@ -82,19 +79,7 @@ namespace TEAMModelOS.SDK.Models
         /// </summary>
         public List<string> tags = new List<string>();
     }
-    /// <summary>
-    /// 当前话题下的所有评语的点赞记录
-    /// </summary>
-    public class LikeRcd { 
-        /// <summary>
-        /// 评论或者话题本身的id
-        /// </summary>
-        public string id { get; set; }
-        /// <summary>
-        /// 点赞的tmdid
-        /// </summary>
-        public string tmdid { get; set; }
-    }
+    
     /// <summary>
     /// 话题回复
     /// </summary>
@@ -115,7 +100,7 @@ namespace TEAMModelOS.SDK.Models
         /// 回复者的昵称
         /// </summary>
         public string tmdname { get; set; }
-
+        public string picture { get; set; }
         /// <summary>
         /// 回复的评语
         /// </summary>
@@ -124,11 +109,30 @@ namespace TEAMModelOS.SDK.Models
         /// 回复的时间
         /// </summary>
         public long time { get; set; }
-        /// <summary>
-        /// 点赞数
-        /// </summary>
-        public int likeCount { get; set; }
         public string atTmdid { get; set; }
         public string atTmdname { get; set; }
+        public string atPicture { get; set; }
+        /// <summary>
+        /// 点赞记录
+        /// </summary>
+        public List<LikeRcd> likes { get; set; } = new List<LikeRcd>();
+
+    }
+    /// <summary>
+    /// 当前话题下的所有评语的点赞记录
+    /// </summary>
+    public class LikeRcd
+    {
+        /// <summary>
+        /// 评论或者话题本身的id
+        /// </summary>
+        public string replyId { get; set; }
+        /// <summary>
+        /// 点赞的tmdid
+        /// </summary>
+        public string tmdid { get; set; }
+        public string tmdname { get; set; }
+        public string userType { get; set; }
+        public string picture { get; set; }
     }
 }

+ 1 - 0
TEAMModelOS/ClientApp/public/lang/en-US.js

@@ -4463,6 +4463,7 @@ const LANG_EN_US = {
             wrongTopic1: "SMART Practice",
             classInteraction: "Web IRS",
             achievement: "My Grade",
+            discussionBoard: 'Discussion Board',
         },
         testType: [{
             label: "Single Answer",

+ 1 - 0
TEAMModelOS/ClientApp/public/lang/zh-CN.js

@@ -4462,6 +4462,7 @@ const LANG_ZH_CN = {
             wrongTopic1: "精准练习",
             classInteraction: "课堂互动",
             achievement: "我的成绩",
+            discussionBoard: '在线讨论区',
         },
         testType: [{
             label: "单选题",

+ 1 - 0
TEAMModelOS/ClientApp/public/lang/zh-TW.js

@@ -4463,6 +4463,7 @@ const LANG_ZH_TW = {
             wrongTopic1: "精準練習",
             classInteraction: "課堂互動",
             achievement: "我的成績",
+            discussionBoard: '線上討論區',
         },
         testType: [{
             label: "單選題",

+ 20 - 0
TEAMModelOS/ClientApp/src/api/studentWeb.js

@@ -296,4 +296,24 @@ export default {
     getErrorItemCnt: function(data) {
         return post("/common/exam/get-error-item-cnt", data)
     },
+    // 课程——新增话题
+    addTopic: function(data) {
+        return post("/school/debate/upsert", data)
+    },
+    // 话题——回复话题/删除回复
+    replyTopic: function(data) {
+        return post("/school/debate/reply", data)
+    },
+    // 话题——删除话题(教师端一样)
+    deleteTopic: function(data) {
+        return post("/school/debate/delete", data)
+    },
+    // 话题——话题列表(教师端一样)
+    findTopic: function(data) {
+        return post("/school/debate/find", data)
+    },
+    // 话题——回复信息(教师端一样)
+    findReply: function(data) {
+        return post("/school/debate/find-id", data)
+    },
 }

+ 141 - 3
TEAMModelOS/ClientApp/src/assets/iconfont/demo_index.html

@@ -54,6 +54,42 @@
       <div class="content unicode" style="display: block;">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+              <span class="icon iconfont">&#xe69f;</span>
+                <div class="name">编辑</div>
+                <div class="code-name">&amp;#xe69f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69a;</span>
+                <div class="name">点赞</div>
+                <div class="code-name">&amp;#xe69a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe717;</span>
+                <div class="name">点赞</div>
+                <div class="code-name">&amp;#xe717;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe698;</span>
+                <div class="name">评论</div>
+                <div class="code-name">&amp;#xe698;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe699;</span>
+                <div class="name">删除</div>
+                <div class="code-name">&amp;#xe699;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a0;</span>
+                <div class="name">故障&投诉</div>
+                <div class="code-name">&amp;#xe6a0;</div>
+              </li>
+          
             <li class="dib">
               <span class="icon iconfont">&#xe697;</span>
                 <div class="name">物联网</div>
@@ -1428,9 +1464,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1689156682901') format('woff2'),
-       url('iconfont.woff?t=1689156682901') format('woff'),
-       url('iconfont.ttf?t=1689156682901') format('truetype');
+  src: url('iconfont.woff2?t=1689734550184') format('woff2'),
+       url('iconfont.woff?t=1689734550184') format('woff'),
+       url('iconfont.ttf?t=1689734550184') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -1456,6 +1492,60 @@
       <div class="content font-class">
         <ul class="icon_lists dib-box">
           
+          <li class="dib">
+            <span class="icon iconfont icon-bianji"></span>
+            <div class="name">
+              编辑
+            </div>
+            <div class="code-name">.icon-bianji
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-dianzan"></span>
+            <div class="name">
+              点赞
+            </div>
+            <div class="code-name">.icon-dianzan
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-dianzan1"></span>
+            <div class="name">
+              点赞
+            </div>
+            <div class="code-name">.icon-dianzan1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-pinglun"></span>
+            <div class="name">
+              评论
+            </div>
+            <div class="code-name">.icon-pinglun
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-shanchu"></span>
+            <div class="name">
+              删除
+            </div>
+            <div class="code-name">.icon-shanchu
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-guzhangtousu"></span>
+            <div class="name">
+              故障&投诉
+            </div>
+            <div class="code-name">.icon-guzhangtousu
+            </div>
+          </li>
+          
           <li class="dib">
             <span class="icon iconfont icon-wulianwang"></span>
             <div class="name">
@@ -3517,6 +3607,54 @@
       <div class="content symbol">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-bianji"></use>
+                </svg>
+                <div class="name">编辑</div>
+                <div class="code-name">#icon-bianji</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-dianzan"></use>
+                </svg>
+                <div class="name">点赞</div>
+                <div class="code-name">#icon-dianzan</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-dianzan1"></use>
+                </svg>
+                <div class="name">点赞</div>
+                <div class="code-name">#icon-dianzan1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-pinglun"></use>
+                </svg>
+                <div class="name">评论</div>
+                <div class="code-name">#icon-pinglun</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-shanchu"></use>
+                </svg>
+                <div class="name">删除</div>
+                <div class="code-name">#icon-shanchu</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-guzhangtousu"></use>
+                </svg>
+                <div class="name">故障&投诉</div>
+                <div class="code-name">#icon-guzhangtousu</div>
+            </li>
+          
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#icon-wulianwang"></use>

+ 27 - 3
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 2000444 */
-  src: url('iconfont.woff2?t=1689156682901') format('woff2'),
-       url('iconfont.woff?t=1689156682901') format('woff'),
-       url('iconfont.ttf?t=1689156682901') format('truetype');
+  src: url('iconfont.woff2?t=1689734550184') format('woff2'),
+       url('iconfont.woff?t=1689734550184') format('woff'),
+       url('iconfont.ttf?t=1689734550184') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,30 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-bianji:before {
+  content: "\e69f";
+}
+
+.icon-dianzan:before {
+  content: "\e69a";
+}
+
+.icon-dianzan1:before {
+  content: "\e717";
+}
+
+.icon-pinglun:before {
+  content: "\e698";
+}
+
+.icon-shanchu:before {
+  content: "\e699";
+}
+
+.icon-guzhangtousu:before {
+  content: "\e6a0";
+}
+
 .icon-wulianwang:before {
   content: "\e697";
 }

File diff suppressed because it is too large
+ 1 - 1
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js


+ 42 - 0
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.json

@@ -5,6 +5,48 @@
   "css_prefix_text": "icon-",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "2077714",
+      "name": "编辑",
+      "font_class": "bianji",
+      "unicode": "e69f",
+      "unicode_decimal": 59039
+    },
+    {
+      "icon_id": "2742629",
+      "name": "点赞",
+      "font_class": "dianzan",
+      "unicode": "e69a",
+      "unicode_decimal": 59034
+    },
+    {
+      "icon_id": "4933298",
+      "name": "点赞",
+      "font_class": "dianzan1",
+      "unicode": "e717",
+      "unicode_decimal": 59159
+    },
+    {
+      "icon_id": "5532895",
+      "name": "评论",
+      "font_class": "pinglun",
+      "unicode": "e698",
+      "unicode_decimal": 59032
+    },
+    {
+      "icon_id": "10156386",
+      "name": "删除",
+      "font_class": "shanchu",
+      "unicode": "e699",
+      "unicode_decimal": 59033
+    },
+    {
+      "icon_id": "10268972",
+      "name": "故障&投诉",
+      "font_class": "guzhangtousu",
+      "unicode": "e6a0",
+      "unicode_decimal": 59040
+    },
     {
       "icon_id": "11761256",
       "name": "物联网",

BIN
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.ttf


BIN
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff


BIN
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff2


+ 31 - 3
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/RecordView.vue

@@ -345,7 +345,7 @@ export default {
                 groupIds: [this.courseNow.list],
             }
             param.scope === "school" ? param.school = this.courseNow.school : param.tmdid = this.courseNow.roster.teacherId
-            this.$api.studentWeb.getClassRecord(param).then(res => {
+            this.$api.studentWeb.getClassRecord(param).then(async res => {
                 if(res.lessonRecords && res.lessonRecords.length) {
                     let newList = res.lessonRecords.map(item => {
                         item.startTime = this.dateFormat(item.startTime)
@@ -355,17 +355,45 @@ export default {
                         let hour = parseInt(min / 60)
                         item.time = `${hour < 10 ? ('0' + hour) : hour}:${mins < 10 ? ('0' + mins) : mins}:${sec < 10 ? ('0' + sec) : sec}`
                         item.eNote = `${this.sasInfo.url}/${this.sasInfo.name}/records/${item.id}/Note.pdf?${this.sasInfo.sas}`
-                        item.CoverImage = `${this.sasInfo.url}/${this.sasInfo.name}/records/${item.id}/Record/CoverImage.jpg?${this.sasInfo.sas}`
+                        // item.CoverImage = `${this.sasInfo.url}/${this.sasInfo.name}/records/${item.id}/Record/CoverImage.jpg?${this.sasInfo.sas}`
                         item.myNote = []
                         return item
                     })
-                    this.recordList.push.apply(this.recordList, newList)
+                    // 改为:從 PgldList 中獲取MEMO的第一頁當封面
+                    let lists = await this.getCoverImg(newList)
+                    if(lists) {
+                        this.recordList.push.apply(this.recordList, lists)
+                    }
                     this.continuationToken = res.continuationToken
                     this.getTrainFiles()
                 }
             }).finally(() => {
             })
         },
+        getCoverImg(lists) {
+            return new Promise(async (resolve, reject) => {
+                let promiseArr = []
+                lists.forEach((item, index) => {
+                    promiseArr.push(new Promise(async (r, j) => {
+                        let imageCover = ''
+                        try {
+                            let url = `${this.sasInfo.url}/${this.sasInfo.name}/records/${item.id}/IES/TimeLine.json?${this.sasInfo.sas}`
+                            let res = JSON.parse(await this.$tools.getFile(url))
+                            let pgids = res.PgIdList || []
+                            imageCover = `${this.sasInfo.url}/${this.sasInfo.name}/records/${item.id}/Memo/${pgids[0]}.jpg?${this.sasInfo.sas}`
+                        } catch (e) {
+                        }
+                        item.CoverImage = imageCover
+                        r(item)
+                    }))
+                })
+                Promise.all(promiseArr).then(res => {
+                    resolve(res)
+                }).catch(err => {
+                    reject(err)
+                })
+            })
+        },
         // 查找活动的视频和文件
         getTrainFiles() {
             let blobTool = new BlobTool(this.sasInfo.url, this.sasInfo.name, "?" + this.sasInfo.sas, this.courseNow.scope)

+ 604 - 0
TEAMModelOS/ClientApp/src/components/student-web/DiscussionBoard.vue

@@ -0,0 +1,604 @@
+<template>
+    <div class="discussion">
+        <Loading v-show="isLoad" bgColor="rgba(0, 0, 0, 0.3)"></Loading>
+        <div style="width: 100%; height: 100%; background-color: #ffffff;">
+            <div style="padding: 15px 30px; margin-bottom: 5px;">
+                <div class="header-box">
+                    <div class="searchbox">
+                        <Input v-special-char search enter-button :placeholder="$t('jyzx.discuss.placeholder1')" v-model="keyword" @on-search="findTopic(true)" />
+                    </div>
+                    <div style="display: flex; align-items: center;">
+                        <Dropdown @on-click="metopic">
+                            <Button>
+                                {{ showType.name }}
+                                <Icon type="ios-arrow-down"></Icon>
+                            </Button>
+                            <DropdownMenu slot="list">
+                                <DropdownItem name="allTopic">{{ $t("jyzx.discuss.allTopic") }}</DropdownItem>
+                                <DropdownItem name="metopic">{{ $t("jyzx.discuss.myTopic") }}</DropdownItem>
+                                <!-- <DropdownItem name="mereply">我的回复</DropdownItem> -->
+                            </DropdownMenu>
+                        </Dropdown>
+                        <div style="padding-left: 10px; cursor: pointer;">
+                            <Icon type="md-add-circle" size="25" color="#02b35a" @click="topicShow = true" :title="$t('jyzx.discuss.addTopic')" />
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div style="height: 90%;">
+                <vuescroll>
+                    <div>
+                        <div v-for="(item, index) in discuss" :key="index" class="discuss">
+                            <div>
+                                <div class="disContent">
+                                    <span class="disName" :style="{color: item.tmdid == $store.state.userInfo.sub ? '#02B35A' : '',}">
+                                        {{ item.tmdname }}
+                                    </span>
+                                    <span class="tag-style" style="border-color: #6b6ba9;color: #6b6ba9;" v-show="item.identity">老师</span>
+                                </div>
+                                <p class="title-box">
+                                    <!-- <span style="color: #02B35A">{{ $t("jyzx.common.theme") }}:</span> -->
+                                    {{ item.title }}
+                                </p>
+                                <div>
+                                    <!-- <span style="color: #2d8cf0">{{ $t("jyzx.common.content") }}:</span> -->
+                                    <span v-html="item.comment"></span>
+                                </div>
+                                <div class="disAction">
+                                    <div>
+                                        <span v-if="item.tmdid == $store.state.userInfo.sub" @click="editTopic(item)">
+                                            <Icon custom="iconfont icon-bianji" size="17" />
+                                            编辑
+                                        </span>
+                                        <span style="margin-left: 20px" :style="{color: activeIn === index ? '#02B35A' : ''}" @click="openDisc(index, item)">
+                                            <Icon custom="iconfont icon-pinglun" size="17" />
+                                            回复
+                                            {{ item.replyCount ? item.replyCount : '' }}
+                                        </span>
+                                        <span style="margin-left: 20px" class="delete-icon" v-if="item.tmdid == $store.state.userInfo.sub" @click="deleteTopic(item, index)">
+                                            <Icon custom="iconfont icon-shanchu" size="17" />
+                                            {{ $t("jyzx.common.delete") }}
+                                        </span>
+                                        <!-- 先隐藏,课程改版完成后完善 -->
+                                        <!-- <span style="margin-left: 20px" v-show="item.tmdid != $store.state.userInfo.sub">
+                                            <Icon custom="iconfont icon-guzhangtousu" size="17" />
+                                            {{ $t("jyzx.common.report") }}
+                                        </span> -->
+                                    </div>
+                                    <p>{{ item.time | formatDate }}</p>
+                                </div>
+                            </div>
+                            <!-- 回复框 -->
+                            <div class="replyDisc" v-if="activeIn === index">
+                                <Input v-special-char v-model="replyDis" type="textarea" :maxlength="item.wordCount" :autosize="{ minRows: 2, maxRows: 5 }" placeholder="回复该话题"></Input>
+                                <div>
+                                    <Button type="primary" @click="replyTopic(item)">回复</Button>
+                                </div>
+                            </div>
+                            <!-- 别人的留言 -->
+                            <div v-if="replies.length && activeIn === index" class="disChild">
+                                <div v-for="(child, no) in replies" :key="no">
+                                    <div class="disContent">
+                                        <span class="disName" :style="{color:child.tmdid === $store.state.userInfo.sub ? '#02B35A' : '',}">
+                                            {{ child.tmdname }}
+                                        </span>
+                                        <span class="tag-style" style="border-color: #6b6ba9;color: #6b6ba9;" v-show="child.identity">老师</span>
+                                    </div>
+                                    <p v-html="child.comment"></p>
+                                    <div class="disAction">
+                                        <div>
+                                            <span @click="likeTopic(child, no)">
+                                                <Icon custom="iconfont icon-dianzan" size="17" color="#ff621a" v-show="child.likeit" />
+                                                <Icon custom="iconfont icon-dianzan1" size="17" v-show="!child.likeit" />
+                                                点赞
+                                                {{ child.likes.length ? child.likes.length : '' }}
+                                            </span>
+                                            <span style="margin-left: 20px" class="delete-icon" v-if="child.tmdid === $store.state.userInfo.sub" @click="deleteReply(child, no)">
+                                                <Icon custom="iconfont icon-shanchu" size="17" />
+                                                {{ $t("jyzx.common.delete") }}
+                                            </span>
+                                            <!-- 先隐藏,课程改版完成后完善 -->
+                                            <!-- <span style="margin-left: 20px" v-show="child.tmdid != $store.state.userInfo.sub">
+                                                <Icon custom="iconfont icon-guzhangtousu" size="17" />
+                                                {{ $t("jyzx.common.report") }}
+                                            </span> -->
+                                        </div>
+                                        <p>{{ child.time | formatDate }}</p>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        <div v-if="!discuss.length" style="font-size: 20px; text-align: center; padding-top: 20px;">
+                            暂无讨论话题
+                        </div>
+                    </div>
+                </vuescroll>
+            </div>
+        </div>
+        <Modal v-model="topicShow" width="50%" class="add-topic" :title="editId ? '编辑话题' : '发布话题'" @on-ok="addTopic" @on-cancel="cancelTopic">
+            <Input v-special-char v-model="topicTitle" :placeholder="$t('jyzx.discuss.placeholder2')"></Input>
+            <div id="releaseBox" style="height: 340px;"></div>
+        </Modal>
+        <Modal v-model="isReport" :title="$t('jyzx.common.report')">
+            <p>{{ $t("jyzx.common.message") }}</p>
+            <CheckboxGroup v-model="checkReport">
+                <Checkbox :label="index" v-for="(item, index) in reportList" :key="index">
+                    <span>{{ item }}</span>
+                </Checkbox>
+            </CheckboxGroup>
+        </Modal>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import { formatDate } from "../../utils/time.js"
+import E from "wangeditor"
+export default {
+    data () {
+        return {
+            MyNo: "14", //接收NavBar 選定的那一頁icon標示
+            MyName: "",
+            isLoad: false,
+            showType: {
+                name: this.$t("jyzx.discuss.allTopic"),
+                value: 0
+            },
+            keyword: '',
+            discuss: [],
+            activeIn: -1, //-1:没有点击回复
+            replyDis: "", //回复框内容
+            replies: [],
+            isReport: false,
+            checkReport: [],
+            reportList: this.$t("jyzx.discuss.report"),
+            // 发布框
+            topicShow: false,
+            topicTitle: '',
+            stemContent: "",
+            stemEditor: null,
+            editId: '', //编辑id
+        }
+    },
+    mounted () {
+        this.MyName = this.$t("studentWeb.type.discussionBoard")
+        this.$emit("onNavNo", this.MyNo)
+        this.$emit("onNavName", this.MyName)
+        this.stemEditor = new E("#releaseBox")
+        this.stemEditor.config.onchange = (html) => {
+            this.stemContent = html
+        }
+        this.stemEditor.config.uploadImgShowBase64 = true
+        this.stemEditor.config.zIndex = 500
+        this.$editorTools.initMyEditor(this.stemEditor, this)
+        this.stemEditor.create()
+        document.getElementsByClassName("w-e-text-container")[0].style.height = '300px'
+        this.findTopic()
+    },
+    computed: {
+        ...mapGetters(['getNowCourse']),
+        courseNow() {
+            return this.getNowCourse || JSON.parse(decodeURIComponent(localStorage.course, "utf-8"))
+        },
+    },
+    filters: {
+        //时间处理
+        formatDate (time) {
+            time = time
+            let date = new Date(time)
+            return formatDate(date, "yyyy-MM-dd hh:mm")
+        },
+    },
+    methods: {
+        // 话题列表
+        findTopic(search) {
+            this.isLoad = true
+            this.discuss = []
+            this.activeIn = -1
+            let param = {
+                source: 'course',
+                comid: this.getNowCourse.id,
+                code: this.getNowCourse.scope === 'school' ? this.getNowCourse.school : this.getNowCourse.creatorId
+            }
+            if(search) {
+                if(this.keyword) {
+                    param.keyWord = this.keyword
+                }
+                console.log(this.showType.value);
+                if(this.showType.value) {
+                    param.userType = this.$store.state.userInfo.scope === 'student' ? 'student' : 'tmdid'
+                    param.tmdid = this.$store.state.userInfo.sub
+                }
+            }
+            this.$api.studentWeb.findTopic(param).then(res => {
+                if(res.length) {
+                    res.forEach(item => {
+                        // 返回的课程的所有话题(未区分名单)
+                        // 先前端根据tmdid筛选出当前课程的老师,其他老师不显示
+                        if(this.getNowCourse.scope === 'school') {
+                            if(item.tmdid === this.getNowCourse.roster.teacherId || item.userType === 'student') {
+                                if(item.userType === 'tmdid') item.identity = 'teacher'
+                                this.discuss.push(item)
+                            }
+                        } else {
+                            if(item.tmdid === this.getNowCourse.creatorId) item.identity = 'teacher'
+                            this.discuss.push(item)
+                        }
+                    })
+                    this.isLoad = false
+                }
+            })
+        },
+        // 新增/编辑话题
+        addTopic() {
+            let param = {
+                comid: this.getNowCourse.id, // 课程id
+                source: 'course',
+                debate: {
+                    // 个人课程传课程创建者
+                    code: this.getNowCourse.scope === 'school' ? this.getNowCourse.school : this.getNowCourse.creatorId,
+                    id: this.editId, //编辑传id
+                    title: this.topicTitle,
+                    userType: this.$store.state.userInfo.scope === 'student' ? 'student' : 'tmdid',
+                    tmdid: this.$store.state.userInfo.sub,
+                    tmdname: this.$store.state.userInfo.name,
+                    comment: this.stemContent,
+                    wordCount: 200,// 允许回复的字数
+                    time: (new Date()).getTime(),
+                    comid: this.getNowCourse.id,
+                    school: this.$store.state.userInfo.azp,
+                    tags: [], //话题标签,先不管
+                },
+            }
+            this.$api.studentWeb.addTopic(param).then(res => {
+                if(res.debate) {
+                    this.$Message.success(this.editId ? '编辑成功' : "发布成功")
+                    this.topicShow = false
+                    if(this.editId) {
+                        let editContent = this.discuss.findIndex(item => {
+                            return item.id === this.editId
+                        })
+                        res.debate.replyCount = this.discuss[editContent].replyCount
+                        this.discuss.splice(editContent, 1)
+                    }
+                    this.discuss.unshift(res.debate)
+                }
+            })
+        },
+        // 删除话题
+        deleteTopic(content, index) {
+            this.$Modal.confirm({
+                title: "删除话题",
+                content: `确认删除“${content.title}”?`,
+                onOk: () => {
+                    let param = {
+                        debateId: content.id,
+                        debateCode: this.getNowCourse.scope === 'school' ? this.getNowCourse.school : this.getNowCourse.creatorId
+                    }
+                    this.$api.studentWeb.deleteTopic(param).then(res => {
+                        if(res.debate) {
+                            this.$Message.success("删除成功")
+                            this.discuss.splice(index, 1)
+                        }
+                    })
+                }
+            });
+        },
+        openDisc(index, content) {
+            this.replies = []
+            if (this.activeIn == -1 || this.activeIn != index) {
+                this.activeIn = index
+                this.replyDis = ""
+                let param = {
+                    debateId: content.id,
+                    debateCode: this.getNowCourse.scope === 'school' ? this.getNowCourse.school : this.getNowCourse.creatorId
+                }
+                this.$api.studentWeb.findReply(param).then(res => {
+                    if(res.debate) {
+                        res.debate.replies.forEach(item => {
+                            let isLike = item.likes.find(likes => {
+                                return likes.tmdid === this.$store.state.userInfo.sub
+                            })
+                            
+                            if(this.getNowCourse.scope === 'school' && item.userType === 'tmdid' || item.tmdid === this.getNowCourse.creatorId) {
+                                item.identity = 'teacher'
+                            }
+                            item.likeit = isLike ? true : false
+                            this.replies.push(item)
+                        })
+                    }
+                })
+            } else if(this.activeIn === index) {
+                this.activeIn = -1
+            }
+        },
+        // 回复话题
+        replyTopic(content, index) {
+            let param = {
+                debateId: content.id,
+                debateCode: this.getNowCourse.scope === 'school' ? this.getNowCourse.school : this.getNowCourse.creatorId,
+                opt: 'add', // 课程id
+                reply: {
+                    id: '', //编辑传id
+                    pid: content.id, //话题的id/或者回复的id
+                    userType: this.$store.state.userInfo.scope === 'student' ? 'student' : 'tmdid',
+                    tmdid: this.$store.state.userInfo.sub,
+                    tmdname: this.$store.state.userInfo.name,
+                    picture: '',
+                    school: this.$store.state.userInfo.azp,
+                    atTmdid: content.tmdid, //被回复者id,醍摩豆id/学生id
+                    atPicture: '', //被回复者头像,先不传
+                    atTmdname: content.tmdname, //被回复者名称
+                    atUserType: '', //被回复者类型
+                    comment: this.replyDis,
+                    time: (new Date()).getTime(),
+                },
+            }
+            this.$api.studentWeb.replyTopic(param).then(res => {
+                if(res.reply) {
+                    this.$Message.success("回复成功")
+                    this.replyDis = ''
+                    this.replies.unshift(res.reply)
+                    this.discuss[index].replyCount++
+                }
+            })
+        },
+        // 点赞
+        likeTopic(content, index) {
+            console.log(content, index);
+            let param = {
+                debateId: content.pid,
+                debateCode: this.getNowCourse.scope === 'school' ? this.getNowCourse.school : this.getNowCourse.creatorId,
+                opt: content.likeit ? 'unlike' : 'like',
+                likeData: {
+                    replyId: content.id,
+                    userType: this.$store.state.userInfo.scope === 'student' ? 'student' : 'tmdid',
+                    tmdid: this.$store.state.userInfo.sub,
+                    tmdname: this.$store.state.userInfo.name,
+                    picture: '',
+                }
+            }
+            this.$api.studentWeb.replyTopic(param).then(res => {
+                if(res) {
+                    if(param.opt === 'like') {
+                        this.replies[index].likes.push(param.likeData)
+                    } else {
+                        this.replies[index].likes.splice(index, 1)
+                    }
+                    this.replies[index].likeit = !content.likeit
+                }
+            })
+        },
+        // 删除回复
+        deleteReply(content, childindex, index) {
+            this.$Modal.confirm({
+                title: "删除回复",
+                content: `确认删除这条评论吗?`,
+                onOk: () => {
+                    let param = {
+                        debateId: content.pid,
+                        debateCode: this.getNowCourse.scope === 'school' ? this.getNowCourse.school : this.getNowCourse.creatorId,
+                        opt: 'del', // 课程id
+                        replyId: content.id,
+                    }
+                    this.$api.studentWeb.replyTopic(param).then(res => {
+                        if(!res.status) {
+                            this.$Message.success("删除成功")
+                            this.replies.splice(childindex, 1)
+                            this.discuss[index].replyCount === 1 ? this.discuss[index].replyCount = 0 : this.discuss[index].replyCount--
+                        }
+                    })
+                }
+            });
+        },
+        metopic(name) {
+            // 我的话题
+            if (name === "metopic") {
+                this.showType = {
+                    name: this.$t("jyzx.discuss.myTopic"),
+                    value: 1
+                }
+            } else if (name === "allTopic") {
+                this.showType = {
+                    name: this.$t("jyzx.discuss.allTopic"),
+                    value: 0
+                }
+            }
+            this.findTopic(true)
+        },
+        editTopic(content) {
+            this.topicTitle = content.title
+            this.stemEditor.txt.html(content.comment)
+            // this.stemContent = content.comment
+            this.topicShow = true
+            this.editId = content.id
+        },
+        cancelTopic() {
+            if(this.editId) {
+                this.topicTitle = ''
+                this.stemEditor.txt.html('')
+                this.editId = ''
+            }
+        },
+        //时间格式化处理
+        dateFormat(timestamp) {
+            var date = new Date(timestamp)
+            var Y = date.getFullYear() + '-'
+            var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
+            var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' '
+            var H = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ":"
+            var Min = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes())
+            var S = (date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()) + " "
+            return Y + M + D + H + Min;
+        },
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.discussion {
+    width: 100%;
+    height: 100%;
+    background-color: #f2f2f2;
+    padding: 25px;
+
+    .header-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .searchbox {
+            width: 25%;
+        }
+    }
+
+    .discuss {
+        border-bottom: 1px solid var(--border-color);
+        padding: 15px;
+        margin: 0 15px;
+
+        &:first-child {
+            margin-top: 0;
+        }
+
+        &:last-child {
+            margin-bottom: 30px;
+            border-bottom: none;
+        }
+
+        .disContent {
+            .disName {
+                font-size: 20px;
+                // color: #2d8cf0;
+            }
+
+            & > p:nth-of-type(2) {
+                margin: 5px 0;
+            }
+        }
+
+        .title-box {
+            font-weight: bold;
+            margin-top: 10px;
+            margin-bottom: 5px;
+            font-size: 16px;
+        }
+
+        .disAction {
+            margin-top: 5px;
+
+            .delete-icon:hover {
+                color: rgb(226, 17, 17);
+            }
+
+            & > p:last-child {
+                color: #878787;
+            }
+
+            div {
+                float: right;
+
+                span {
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .inClass {
+            background: #02B35A;
+            color: white;
+            border-radius: 10px;
+            padding: 2px 10px;
+            margin: 2px 0;
+            float: right;
+        }
+
+        .disChild {
+            padding: 10px 0 10px 5px;
+
+            & > div {
+                padding: 3px 0 7px 10px;
+
+                &:not(:last-child) {
+                    border-bottom: 1px solid #ffffff;
+                    margin-bottom: 10px;
+                }
+            }
+
+            .disName {
+                font-size: 16px;
+                // color: #515a6e;
+            }
+        }
+
+        .replyDisc {
+            border-top: 1px dashed var(--border-color);
+            margin-top: 10px;
+            padding-top: 10px;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+
+            .ivu-input-wrapper {
+                width: 95%;
+            }
+
+            .ivu-btn {
+                background-color: #02b35a;
+                border-color: #02b35a;
+            }
+        }
+    }
+}
+
+.add-topic {
+    margin-top: 20px;
+    .ivu-input-wrapper {
+        margin-bottom: 10px;
+    }
+    &>p {
+        text-align: right;
+    }
+}
+.w-e-text-container {
+    height: 300px !important;
+}
+</style>
+
+<style lang="less">
+.discussion {
+    .header-box {
+        .ivu-input-with-search:hover,
+        .ivu-input-search,
+        .ivu-input-search:hover{
+            background: #02B35A !important;
+            border-color: #02B35A !important;
+        }
+        .ivu-input:hover,
+        .ivu-input:focus {
+            border-color: #02B35A !important;
+        }
+        .ivu-btn:hover {
+            color: #02B35A;
+            border-color: #02B35A;
+        }
+    }
+    .add-topic{
+        .ivu-btn-primary{
+            background-color: #02B35A;
+            border-color: #02B35A;
+        }
+        .ivu-btn-text:hover{
+            color: #02B35A;
+        }
+    }
+
+    .header-box,
+    .add-topic,
+    .replyDisc {
+        .ivu-input:hover,
+        .ivu-input:focus {
+            border-color: #02B35A !important;
+        }
+    }
+}
+</style>

+ 6 - 0
TEAMModelOS/ClientApp/src/router/routes.js

@@ -1562,6 +1562,12 @@ export const routes = [{
             name: "classRecord",
             path: "classRecord",
             component: () => import('@/components/student-web/ClassRecord/RecordView'),
+        },
+        {
+            // 在线讨论区
+            name: "discussionBoard",
+            path: "discussionBoard",
+            component: () => import('@/components/student-web/DiscussionBoard'),
         }
     ]
 },

+ 567 - 0
TEAMModelOS/ClientApp/src/view/mycourse/discussion/Discussion.vue

@@ -0,0 +1,567 @@
+<template>
+    <div class="discussion-teacher">
+        <div v-if="!classInfo || (!classInfo.classId && !classInfo.stulist)" class="not-class">
+            <EmptyData :top="100" textContent="暂无名单,无法进行讨论"></EmptyData>
+        </div>
+        <div style="width: 100%; height: 100%;" v-else>
+            <div style="background-color: #ffffff; padding: 10px 30px; margin-bottom: 15px;">
+                <div class="header-box">
+                    <div class="searchbox">
+                        <Input v-special-char search enter-button :placeholder="$t('jyzx.discuss.placeholder1')" v-model="keyword" @on-search="findTopic(true)" />
+                    </div>
+                    <div style="display: flex; align-items: center;">
+                        <Dropdown @on-click="metopic">
+                            <Button>
+                                {{ showType.name }}
+                                <Icon type="ios-arrow-down"></Icon>
+                            </Button>
+                            <DropdownMenu slot="list">
+                                <DropdownItem name="allTopic">{{ $t("jyzx.discuss.allTopic") }}</DropdownItem>
+                                <DropdownItem name="metopic">{{ $t("jyzx.discuss.myTopic") }}</DropdownItem>
+                                <!-- <DropdownItem name="mereply">我的回复</DropdownItem> -->
+                            </DropdownMenu>
+                        </Dropdown>
+                        <div style="padding-left: 10px; cursor: pointer;">
+                            <Icon type="md-add-circle" size="25" color="#2b85e4" @click="topicShow = true" :title="$t('jyzx.discuss.addTopic')" />
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div style="height: 90%;">
+                <vuescroll>
+                    <div style="margin-bottom: 30px;">
+                        <div v-for="(item, index) in discuss" :key="index" class="discuss">
+                            <div>
+                                <div class="disContent">
+                                    <p class="disName" :style="{color: item.tmdid === $store.state.userInfo.TEAMModelId ? '#2b85e4' : '',}">
+                                        {{ item.tmdname }}
+                                    </p>
+                                </div>
+                                <p class="title-box">
+                                    <!-- <span style="color: #02B35A">{{ $t("jyzx.common.theme") }}:</span> -->
+                                    {{ item.title }}
+                                </p>
+                                <div>
+                                    <!-- <span style="color: #2d8cf0">{{ $t("jyzx.common.content") }}:</span> -->
+                                    <span v-html="item.comment"></span>
+                                </div>
+                                <div class="disAction">
+                                    <div>
+                                        <span v-if="item.tmdid == $store.state.userInfo.sub" @click="editTopic(item)">
+                                            <Icon custom="iconfont icon-bianji" size="17" />
+                                            编辑
+                                        </span>
+                                        <span style="margin-left: 20px" :style="{color: activeIn === index ? '#2b85e4' : ''}" @click="openDisc(index, item)">
+                                            <Icon custom="iconfont icon-pinglun" size="17" />
+                                            回复
+                                            {{ item.replyCount ? item.replyCount : '' }}
+                                        </span>
+                                        <span style="margin-left: 20px" class="delete-icon" @click="deleteTopic(item, index)">
+                                            <Icon custom="iconfont icon-shanchu" size="17" />
+                                            {{ $t("jyzx.common.delete") }}
+                                        </span>
+                                    </div>
+                                    <p>{{ item.time | formatDate }}</p>
+                                </div>
+                            </div>
+                            <!-- 回复框 -->
+                            <div class="replyDisc" v-if="activeIn === index">
+                                <Input v-special-char v-model="replyDis" type="textarea" :maxlength="item.wordCount" :autosize="{ minRows: 2, maxRows: 5 }" placeholder="回复该话题"></Input>
+                                <div>
+                                    <Button type="primary" @click="replyTopic(item, index)">回复</Button>
+                                </div>
+                            </div>
+                            <!-- 别人的留言 -->
+                            <div v-if="replies.length && activeIn === index" class="disChild">
+                                <div v-for="(child, no) in replies" :key="no">
+                                    <div class="disContent">
+                                        <p class="disName" :style="{color:child.tmdid === $store.state.userInfo.TEAMModelId ? '#2b85e4' : '',}">
+                                            {{ child.tmdname }}
+                                        </p>
+                                    </div>
+                                    <p v-html="child.comment"></p>
+                                    <div class="disAction">
+                                        <div>
+                                            <span @click="likeTopic(child, no)">
+                                                <Icon custom="iconfont icon-dianzan" size="17" color="#ff621a" v-show="child.likeit" />
+                                                <Icon custom="iconfont icon-dianzan1" size="17" v-show="!child.likeit" />
+                                                点赞
+                                                {{ child.likes.length ? child.likes.length : '' }}
+                                            </span>
+                                            <span style="margin-left: 20px" class="delete-icon" @click="deleteReply(child, no, index)">
+                                                <Icon custom="iconfont icon-shanchu" size="17" />
+                                                {{ $t("jyzx.common.delete") }}
+                                            </span>
+                                        </div>
+                                        <p>{{ child.time | formatDate }}</p>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        <EmptyData v-show="!discuss.length" :top="100" textContent="暂无讨论话题"></EmptyData>
+                    </div>
+                </vuescroll>
+            </div>
+        </div>
+        <Modal v-model="topicShow" width="50%" class="add-topic" :title="editId ? '编辑话题' : '发布话题'" @on-ok="addTopic" @on-cancel="cancelTopic">
+            <Input v-special-char v-model="topicTitle" :placeholder="$t('jyzx.discuss.placeholder2')"></Input>
+            <div id="releaseBox" style="height: 340px"></div>
+        </Modal>
+        <Modal v-model="isReport" :title="$t('jyzx.common.report')">
+            <p>{{ $t("jyzx.common.message") }}</p>
+            <CheckboxGroup v-model="checkReport">
+                <Checkbox :label="index" v-for="(item, index) in reportList" :key="index">
+                    <span>{{ item }}</span>
+                </Checkbox>
+            </CheckboxGroup>
+        </Modal>
+    </div>
+</template>
+
+<script>
+import { formatDate } from "@/utils/time.js"
+import E from "wangeditor"
+export default {
+    props: {
+        classList: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        classInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        courseInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        }
+    },
+    data () {
+        return {
+            MyNo: "14", //接收NavBar 選定的那一頁icon標示
+            MyName: "",
+            showType: {
+                name: this.$t("jyzx.discuss.allTopic"),
+                value: 0
+            },
+            keyword: '',
+            discuss: [],
+            activeIn: -1, //-1:没有点击回复
+            replyDis: "", //回复框内容
+            replies: [],
+            isReport: false,
+            checkReport: [],
+            reportList: this.$t("jyzx.discuss.report"),
+            // 发布框
+            topicShow: false,
+            topicTitle: '',
+            stemContent: "",
+            stemEditor: null,
+            editId: '', //编辑状态
+        }
+    },
+    mounted () {
+        this.MyName = this.$t("studentWeb.type.discussionBoard")
+        this.$emit("onNavNo", this.MyNo)
+        this.$emit("onNavName", this.MyName)
+        this.stemEditor = new E("#releaseBox")
+        this.stemEditor.config.onchange = (html) => {
+            this.stemContent = html
+        }
+        this.stemEditor.config.uploadImgShowBase64 = true
+        this.stemEditor.config.zIndex = 500
+        this.$editorTools.initMyEditor(this.stemEditor, this)
+        this.stemEditor.create()
+        document.getElementsByClassName("w-e-text-container")[0].style.height = '300px'
+        this.findTopic()
+    },
+    filters: {
+        //时间处理
+        formatDate (time) {
+            time = time
+            let date = new Date(time)
+            return formatDate(date, "yyyy-MM-dd hh:mm")
+        },
+    },
+    watch: {
+        classInfo: {
+            deep: true,
+            immediate: true,
+            handler(n, o) {
+                if (n && (n.classId || n.stulist)) {
+                    this.findTopic()
+                }
+            }
+        },
+    },
+    methods: {
+        // 话题列表
+        findTopic(search) {
+            this.discuss = []
+            this.activeIn = -1
+            let param = {
+                source: 'course',
+                comid: this.courseInfo.id,
+                code: this.courseInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId
+            }
+            if(search) {
+                if(this.keyword) {
+                    param.keyWord = this.keyword
+                }
+                if(this.showType.value) {
+                    param.userType = 'tmdid'
+                    param.tmdid = this.$store.state.userInfo.TEAMModelId
+                }
+            }
+            this.$api.studentWeb.findTopic(param).then(res => {
+                if(res.length) {
+                    res.forEach(item => {
+                        if(this.courseInfo.scope === 'school') {
+                            if(item.tmdid === this.classInfo.teacherId || item.userType === 'student') {
+                                if(item.userType === 'tmdid') item.identity = 'teacher'
+                                this.discuss.push(item)
+                            }
+                        } else {
+                            if(item.tmdid === this.classInfo.teacherId) item.identity = 'teacher'
+                            this.discuss.push(item)
+                        }
+                    })
+                }
+            })
+        },
+        // 新增/编辑话题
+        addTopic() {
+            let param = {
+                comid: this.courseInfo.id, // 课程id
+                source: 'course',
+                debate: {
+                    // 个人课程传课程创建者
+                    code: this.courseInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                    id: this.editId, //编辑传id
+                    title: this.topicTitle,
+                    userType: 'tmdid',
+                    tmdid: this.$store.state.userInfo.TEAMModelId,
+                    tmdname: this.$store.state.userInfo.name,
+                    comment: this.stemContent,
+                    wordCount: 200,// 允许回复的字数
+                    time: (new Date()).getTime(),
+                    comid: this.courseInfo.id,
+                    school: this.courseInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : '',
+                    tags: [], //话题标签,先不管
+                },
+            }
+            this.$api.studentWeb.addTopic(param).then(res => {
+                if(res.debate) {
+                    this.$Message.success(this.editId ? '编辑成功' : "发布成功")
+                    this.topicShow = false
+                    if(this.editId) {
+                        let editContent = this.discuss.findIndex(item => {
+                            return item.id === this.editId
+                        })
+                        res.debate.replyCount = this.discuss[editContent].replyCount
+                        this.discuss.splice(editContent, 1)
+                    }
+                    this.discuss.unshift(res.debate)
+                }
+            })
+        },
+        // 删除话题
+        deleteTopic(content, index) {
+            this.$Modal.confirm({
+                title: "删除话题",
+                content: `确认删除“${content.title}”?`,
+                onOk: () => {
+                    let param = {
+                        debateId: content.id,
+                        debateCode: this.courseInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId
+                    }
+                    this.$api.studentWeb.deleteTopic(param).then(res => {
+                        if(res.debate) {
+                            this.$Message.success("删除成功")
+                            this.discuss.splice(index, 1)
+                        }
+                    })
+                }
+            });
+        },
+        // 查询回复
+        openDisc(index, content) {
+            this.replies = []
+            if (this.activeIn == -1 || this.activeIn != index) {
+                this.activeIn = index
+                this.replyDis = ""
+                let param = {
+                    debateId: content.id,
+                    debateCode: this.courseInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId
+                }
+                this.$api.studentWeb.findReply(param).then(res => {
+                    if(res.debate) {
+                        res.debate.replies.forEach(item => {
+                            let isLike = item.likes.find(likes => {
+                                return likes.tmdid === this.$store.state.userInfo.TEAMModelId
+                            })
+                            item.likeit = isLike ? true : false
+                            this.replies.push(item)
+                        })
+                    }
+                })
+            } else if(this.activeIn === index) {
+                this.activeIn = -1
+            }
+        },
+        // 回复话题
+        replyTopic(content, index) {
+            let param = {
+                debateId: content.id,
+                debateCode: this.courseInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                opt: 'add', // 课程id
+                reply: {
+                    id: '', //编辑传id
+                    pid: content.id, //话题的id/或者回复的id
+                    userType: 'tmdid',
+                    tmdid: this.$store.state.userInfo.TEAMModelId,
+                    tmdname: this.$store.state.userInfo.name,
+                    picture: '',
+                    school: this.courseInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : '',
+                    atTmdid: content.tmdid, //被回复者id,醍摩豆id/学生id
+                    atPicture: '', //被回复者头像,先不传
+                    atTmdname: content.tmdname, //被回复者名称
+                    atUserType: '', //被回复者类型
+                    comment: this.replyDis,
+                    time: (new Date()).getTime(),
+                },
+            }
+            this.$api.studentWeb.replyTopic(param).then(res => {
+                if(res.reply) {
+                    this.$Message.success("回复成功")
+                    this.replyDis = ''
+                    this.replies.unshift(res.reply)
+                    this.discuss[index].replyCount++
+                }
+            })
+        },
+        // 点赞
+        likeTopic(content, index) {
+            console.log(content, index);
+            let param = {
+                debateId: content.pid,
+                debateCode: this.courseInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                opt: content.likeit ? 'unlike' : 'like',
+                likeData: {
+                    replyId: content.id,
+                    userType: 'tmdid',
+                    tmdid: this.$store.state.userInfo.TEAMModelId,
+                    tmdname: this.$store.state.userInfo.name,
+                    picture: '',
+                }
+            }
+            this.$api.studentWeb.replyTopic(param).then(res => {
+                if(res) {
+                    if(param.opt === 'like') {
+                        this.replies[index].likes.push(param.likeData)
+                    } else {
+                        this.replies[index].likes.splice(index, 1)
+                    }
+                    this.replies[index].likeit = !content.likeit
+                }
+            })
+        },
+        // 删除回复
+        deleteReply(content, childindex, index) {
+            this.$Modal.confirm({
+                title: "删除回复",
+                content: `确认删除这条评论吗?`,
+                onOk: () => {
+                    let param = {
+                        debateId: content.pid,
+                        debateCode: this.courseInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                        opt: 'del', // 课程id
+                        replyId: content.id,
+                    }
+                    this.$api.studentWeb.replyTopic(param).then(res => {
+                        if(!res.status) {
+                            this.$Message.success("删除成功")
+                            this.replies.splice(childindex, 1)
+                            this.discuss[index].replyCount === 1 ? this.discuss[index].replyCount = 0 : this.discuss[index].replyCount--
+                        }
+                    })
+                }
+            });
+        },
+        metopic(name) {
+            // 我的话题
+            if (name === "metopic") {
+                this.showType = {
+                    name: this.$t("jyzx.discuss.myTopic"),
+                    value: 1
+                }
+            } else if (name === "allTopic") {
+                this.showType = {
+                    name: this.$t("jyzx.discuss.allTopic"),
+                    value: 0
+                }
+            }
+            this.findTopic(true)
+        },
+        editTopic(content) {
+            this.topicTitle = content.title
+            this.stemEditor.txt.html(content.comment)
+            this.topicShow = true
+            this.editId = content.id
+        },
+        cancelTopic() {
+            if(this.isEdit) {
+                this.topicTitle = ''
+                this.stemEditor.txt.html('')
+                this.editId = ''
+            }
+        },
+        //时间格式化处理
+        dateFormat(timestamp) {
+            var date = new Date(timestamp)
+            var Y = date.getFullYear() + '-'
+            var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
+            var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' '
+            var H = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ":"
+            var Min = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes())
+            var S = (date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()) + " "
+            return Y + M + D + H + Min;
+        },
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.discussion-teacher {
+    width: 100%;
+    height: 100%;
+    // background-color: #f2f2f2;
+    // padding: 25px;
+
+    .not-class {
+        width: 100%;
+        height: 100%;
+    }
+
+    .header-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .searchbox {
+            width: 25%;
+            // padding: 0% 2%;
+            // padding-top: 2px;
+            // display: inline-block;
+        }
+    }
+
+    .discuss {
+        border-bottom: 1px solid var(--border-color);
+        // background-color: #fff;
+        padding: 15px;
+        margin: 0 15px;
+
+        &:first-child {
+            margin-top: 0;
+        }
+
+        &:last-child {
+            margin-bottom: 30px;
+            border-bottom: none;
+        }
+
+        .disContent {
+            .disName {
+                font-size: 20px;
+                // color: #2d8cf0;
+            }
+
+            & > p:nth-of-type(2) {
+                margin: 5px 0;
+            }
+        }
+
+        .title-box {
+            font-weight: bold;
+            margin-top: 10px;
+            margin-bottom: 5px;
+            font-size: 16px;
+        }
+
+        .disAction {
+            margin-top: 5px;
+            .delete-icon:hover {
+                color: rgb(226, 17, 17);
+            }
+
+            & > p:last-child {
+                color: #878787;
+            }
+
+            div {
+                float: right;
+
+                span {
+                    cursor: pointer;
+                }
+            }
+        }
+
+        .inClass {
+            background: #2b85e4;
+            color: white;
+            border-radius: 10px;
+            padding: 2px 10px;
+            margin: 2px 0;
+            float: right;
+        }
+
+        .disChild {
+            padding: 10px 0 10px 5px;
+
+            & > div {
+                padding: 2px 10px 5px;
+
+                &:not(:last-child) {
+                    border-bottom: 1px solid #ffffff;
+                    margin-bottom: 10px;
+                }
+            }
+
+            .disName {
+                font-size: 16px;
+                // color: #515a6e;
+            }
+        }
+
+        .replyDisc {
+            border-top: 1px dashed var(--border-color);
+            margin-top: 10px;
+            padding-top: 10px;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+
+            .ivu-input-wrapper {
+                width: 95%;
+            }
+        }
+    }
+}
+
+.add-topic {
+    margin-top: 20px;
+    .ivu-input-wrapper {
+        margin-bottom: 10px;
+    }
+    &>p {
+        text-align: right;
+    }
+}
+</style>

+ 5 - 1
TEAMModelOS/ClientApp/src/view/student-web/AppNew.vue

@@ -3,7 +3,7 @@
         <!--導航列-->
         <div class="myNav">
             <div>
-                <span @click="clickSidebarToggle()" v-if="MyNo != '1' && MyNo != '4' && MyNo != 'X' && MyNo != '7' && MyNo != '8' && MyNo != '12' && MyNo != '13'" class="sidebar-toggle">
+                <span @click="clickSidebarToggle()" v-if="MyNo != '1' && MyNo != '4' && MyNo != 'X' && MyNo != '7' && MyNo != '8' && MyNo != '12' && MyNo != '13' && MyNo != '14'" class="sidebar-toggle">
                     <Icon class="menu-icon" type="md-menu" :class="{'menu-icon-close': this.$store.getters.getSidebarisOpen == false}" v-if="MyNo != '11'" />
                 </span>
                 <span v-show="MyNo === '13'" class="sidebar-toggle">
@@ -98,6 +98,10 @@
                         <Icon custom="iconfont icon-cuotiji" size="17" class="tabIcon1" />
                         <span class="no-show" v-show="MyNo != 8">{{ $t('studentWeb.type.wrongTopic') }}</span>
                     </MenuItem>
+                    <MenuItem name="14" to="/studentWeb/discussionBoard" :title="$t('studentWeb.type.discussionBoard')" v-show="selectClass">
+                        <Icon type="ios-chatboxes-outline" size="18" style="font-weight: bold" />
+                        <span class="no-show" v-show="MyNo != 14">{{ $t('studentWeb.type.discussionBoard') }}</span>
+                    </MenuItem>
                     <!-- 精准练习 -->
                     <!-- <MenuItem name="12" to="/studentWeb/practice/preciseQues" :title="$t('studentWeb.type.wrongTopic1')" v-show="selectClass && !onlySystem && getNowCourse.subject">
                         <Icon custom="iconfont icon-cuotiji" size="17" class="tabIcon1" />

+ 79 - 19
TEAMModelOS/Controllers/School/DebateController.cs

@@ -36,7 +36,7 @@ namespace TEAMModelOS.Controllers
             _option = option?.Value; ;
         }
         /// <summary>
-        /// 查询所有话题
+        /// 查询话题
         /// </summary>
         /// <param name="request"></param>
         /// <returns></returns>
@@ -54,8 +54,9 @@ namespace TEAMModelOS.Controllers
             {
                 if (!request.TryGetProperty("debateId", out JsonElement _debateId)) { return BadRequest(); }
                 if (!request.TryGetProperty("debateCode", out JsonElement _debateCode)) { return BadRequest(); }
+                string debateCode = $"{_debateCode}".StartsWith("Debate-") ? $"{_debateCode}" : $"Debate-{_debateCode}";
                 List<dynamic> debates = new List<dynamic>();
-                Debate debate = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReadItemAsync<Debate>($"{_debateId}", new PartitionKey($"{_debateCode}"));
+                Debate debate = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReadItemAsync<Debate>($"{_debateId}", new PartitionKey($"{debateCode}"));
                 return Ok(new { debate = debate , replyCount = debate.replies!=null  ?debate.replies.Count:0});
             }
             catch (Exception ex)
@@ -72,9 +73,10 @@ namespace TEAMModelOS.Controllers
         [ProducesDefaultResponseType]
         //[AuthToken(Roles = "teacher")]
         [HttpPost("delete")]
+#if !DEBUG
         [AuthToken(Roles = "teacher,admin,student")]
 
-#if !DEBUG
+
         [Authorize(Roles = "IES")]
 #endif
         public async Task<IActionResult> Delete(JsonElement request)
@@ -83,13 +85,13 @@ namespace TEAMModelOS.Controllers
             {
                 if (!request.TryGetProperty("debateId", out JsonElement _debateId)) { return BadRequest(); }
                 if (!request.TryGetProperty("debateCode", out JsonElement _debateCode)) { return BadRequest(); }
-                List<dynamic> debates = new List<dynamic>();
-                await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").DeleteItemAsync<Debate>($"{_debateId}", new PartitionKey($"{_debateCode}"));
+                string debateCode = $"{_debateCode}".StartsWith("Debate-") ? $"{_debateCode}" : $"Debate-{_debateCode}";
+                await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync($"{_debateId}", new PartitionKey($"{debateCode}"));
                 return Ok(new { status=200});
             }
             catch (Exception ex)
             {
-                await _dingDing.SendBotMsg($"OS,{_option.Location},DebateController,school/debate/delete\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
+                await _dingDing.SendBotMsg($"OS,{_option.Location},DebateController,school/debate/delete\n{ex.Message}\n{ex.StackTrace}{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
                 return BadRequest();
             }
         }
@@ -113,21 +115,28 @@ namespace TEAMModelOS.Controllers
                 List<dynamic> debates = new List<dynamic>();
                 request.TryGetProperty("source", out JsonElement _source);
                 request.TryGetProperty("tmdid", out JsonElement _tmdid);
+                request.TryGetProperty("userType", out JsonElement _userType);
+                request.TryGetProperty("keyWord", out JsonElement _keyWord);
                 request.TryGetProperty("comid", out JsonElement _comid);
                 if (request.TryGetProperty("code", out JsonElement code))
                 {
-                    StringBuilder stringBuilder = new StringBuilder($"select c.id,c.code,c.tmdid,c.tmdname,c.title,c.comment,c.time,c.comid,c.likeCount,c.school,c.likes,c.wordCount,c.timeoutReply,c.expire,c.source,c.pk, ARRAY_LENGTH( c.replies ) as replyCount from c where c.pk='Debate' ");
+                    StringBuilder stringBuilder = new StringBuilder($"select c.id,c.code,c.tmdid,c.userType,c.tmdname,c.title,c.comment,c.time,c.comid,c.likeCount,c.school,c.wordCount,c.timeoutReply,c.expire,c.source,c.pk, ARRAY_LENGTH( c.replies ) as replyCount from c where c.pk='Debate' ");
                     if (!string.IsNullOrEmpty($"{_source}")) {
                         stringBuilder.Append($" and c.source='{_source}'");
                     }
-                    if (!string.IsNullOrEmpty($"{_tmdid}"))
+                    if (!string.IsNullOrEmpty($"{_tmdid}") && !string.IsNullOrWhiteSpace($"{_userType}"))
+                    {
+                        stringBuilder.Append($" and c.tmdid='{_tmdid}' and c.userType='{_userType}'");
+                    }
+                    if (!string.IsNullOrEmpty($"{_keyWord}"))
                     {
-                        stringBuilder.Append($" and c.tmdid='{_tmdid}'");
+                        stringBuilder.Append($" and contains(c.title,'{_keyWord}') ");
                     }
                     if (!string.IsNullOrEmpty($"{_comid}"))
                     {
                         stringBuilder.Append($" and c.comid='{_comid}'");
                     }
+                    stringBuilder.Append("order by c.time desc ");
                     await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School")
                             .GetItemQueryIterator<dynamic>(queryText: stringBuilder.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Debate-{code}") }))
                     {
@@ -176,14 +185,15 @@ namespace TEAMModelOS.Controllers
         [ProducesDefaultResponseType]
         [HttpPost("reply")]
         [Authorize(Roles = "IES")]
-        [AuthToken(Roles = "admin,teacher")]
+        [AuthToken(Roles = "admin,teacher,student")]
         public async Task<IActionResult> reply(JsonElement request) {
             var (userid, _, _, school) = HttpContext.GetAuthTokenInfo();
             if (!request.TryGetProperty("opt", out JsonElement _opt)) { return BadRequest(); }
             if (!request.TryGetProperty("debateId", out JsonElement _debateId)) { return BadRequest(); }
             if (!request.TryGetProperty("debateCode", out JsonElement _debateCode)) { return BadRequest(); }
-            if (!request.TryGetProperty("reply", out JsonElement _reply)) { return BadRequest(); }
+           
             DebateReply reply = null;
+            string debateCode = $"{_debateCode}".StartsWith("Debate-") ? $"{_debateCode}" : $"Debate-{_debateCode}";
             if (!string.IsNullOrEmpty($"{_debateId}") && !string.IsNullOrEmpty($"{_debateCode}") && !string.IsNullOrEmpty($"{_opt}"))
             {
                 switch ($"{_opt}")
@@ -191,25 +201,76 @@ namespace TEAMModelOS.Controllers
                     case "add":
                         try
                         {
-                            Debate debate = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReadItemAsync<Debate>($"{_debateId}", new PartitionKey($"{_debateCode}"));
+                            if (!request.TryGetProperty("reply", out JsonElement _reply)) { return BadRequest(); }
+                            Debate debate = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReadItemAsync<Debate>($"{_debateId}", new PartitionKey($"{debateCode}"));
                             reply = _reply.ToObject<DebateReply>();
                             reply.pid = string.IsNullOrEmpty(reply.pid) ? $"{_debateId}" : reply.pid;
                             reply.id = string.IsNullOrEmpty(reply.id) ? Guid.NewGuid().ToString() : reply.id;
                             reply.time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                             debate.replies.Add(reply);
-                            await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Debate>(debate, $"{_debateId}", new PartitionKey($"{_debateCode}"));
+                            debate.likeCount = debate.replies.SelectMany(z => z.likes).Count();
+                            await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Debate>(debate, $"{_debateId}", new PartitionKey($"{debateCode}"));
                             return Ok(new { reply , replyCount = debate.replies.Count });
                         }
                         catch (CosmosException ex)
                         {
                             return BadRequest();
                         }
+                    case "like":
+                        try
+                        {
+                            Debate debate = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReadItemAsync<Debate>($"{_debateId}", new PartitionKey($"{debateCode}"));
+                            if (!request.TryGetProperty("likeData", out JsonElement _likeData)) { return BadRequest(); }
+                            LikeRcd likeRcd = _likeData.ToObject<LikeRcd>();
+                            if (likeRcd != null && !string.IsNullOrWhiteSpace(likeRcd.replyId))
+                            {
+                                var replyData = debate.replies.Find(x => $"{likeRcd.replyId}".Equals(x.id));
+                                var like = replyData.likes.FindAll(c => !string.IsNullOrWhiteSpace(c.tmdid) && c.tmdid.Equals(likeRcd.tmdid) && !string.IsNullOrWhiteSpace(c.userType) && c.userType.Equals(likeRcd.userType));
+                                if (like.IsEmpty())
+                                {
+                                    replyData.likes.Add(likeRcd);
+                                }
+                                debate.likeCount = debate.replies.SelectMany(z => z.likes).Count();
+                                await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Debate>(debate, $"{_debateId}", new PartitionKey($"{debateCode}"));
+                                return Ok(new { reply, replyCount = debate.replies.Count });
+                            }
+                            else {
+                                return BadRequest();
+                            }
+                        }
+                        catch (CosmosException ex)
+                        {
+                            return BadRequest();
+                        }
+                    case "unlike":
+                        try
+                        {
+                            Debate debate = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReadItemAsync<Debate>($"{_debateId}", new PartitionKey($"{debateCode}"));
+                            if (!request.TryGetProperty("likeData", out JsonElement _likeData)) { return BadRequest(); }
+                            LikeRcd likeRcd = _likeData.ToObject<LikeRcd>();
+                            if (likeRcd != null && !string.IsNullOrWhiteSpace(likeRcd.replyId))
+                            {
+                                var replyData = debate.replies.Find(x => $"{likeRcd.replyId}".Equals(x.id) );
+                                replyData.likes.RemoveAll(c => !string.IsNullOrWhiteSpace(c.tmdid) && c.tmdid.Equals(likeRcd.tmdid) && !string.IsNullOrWhiteSpace(c.userType) && c.userType.Equals(likeRcd.userType));
+                                debate.likeCount = debate.replies.SelectMany(z => z.likes).Count();
+                                await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Debate>(debate, $"{_debateId}", new PartitionKey($"{debateCode}"));
+                                return Ok(new { reply, replyCount = debate.replies.Count });
+                            }
+                            else
+                            {
+                                return BadRequest();
+                            }
+                        }
+                        catch (CosmosException ex)
+                        {
+                            return BadRequest();
+                        }
                     case "del":
                         try
                         {
-                            Debate debate = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReadItemAsync<Debate>($"{_debateId}", new PartitionKey($"{_debateCode}"));
-                            reply = _reply.ToObject<DebateReply>();
-                            var findall = debate.replies.FindAll(x => reply.id .Equals(x.id));
+                            Debate debate = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReadItemAsync<Debate>($"{_debateId}", new PartitionKey($"{debateCode}"));
+                            if (!request.TryGetProperty("replyId", out JsonElement _replyId)) { return BadRequest(); }
+                            var findall = debate.replies.FindAll(x => $"{_replyId}" .Equals(x.id));
                             if (findall.IsNotEmpty())
                             {   
                                 foreach (var find in findall)
@@ -223,7 +284,8 @@ namespace TEAMModelOS.Controllers
                                         return Ok(new { status = 2, replyCount = debate.replies.Count });
                                     }
                                 }
-                                await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Debate>(debate, $"{_debateId}", new PartitionKey($"{_debateCode}"));
+                                debate.likeCount = debate.replies.SelectMany(z => z.likes).Count();
+                                await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Debate>(debate, $"{_debateId}", new PartitionKey($"{debateCode}"));
                                 //删除成功
                                 return Ok(new { status = 0 , replyCount = debate.replies.Count });
                             }
@@ -314,8 +376,6 @@ namespace TEAMModelOS.Controllers
                     ///保证记录回复记录和点赞记录正常
                     debate.replies = _DBdebate.replies;
                     debate.likeCount = _DBdebate.likeCount;
-                    debate.likes = _DBdebate.likes;
-                    debate.replies = _DBdebate.replies;
                     await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync(debate, debate.id, new PartitionKey(debate.code));
                 }
                 catch (CosmosException ex) {