Browse Source

Merge branch 'develop5.0-tmd' into develop6.0-tmd

CrazyIter_Bin 3 years ago
parent
commit
29a7eff492
70 changed files with 3357 additions and 493 deletions
  1. 4 0
      TEAMModeBI/ClientApp/src/api/index.js
  2. 616 0
      TEAMModeBI/ClientApp/src/components/Ability.vue
  3. 4 16
      TEAMModeBI/ClientApp/src/components/AbilityTree.vue
  4. 57 20
      TEAMModeBI/ClientApp/src/components/echarts/customPie.vue
  5. 2 0
      TEAMModeBI/ClientApp/src/main.js
  6. 29 0
      TEAMModeBI/ClientApp/src/until/common.js
  7. 11 4
      TEAMModeBI/ClientApp/src/until/http.js
  8. 26 0
      TEAMModeBI/ClientApp/src/until/inspect.js
  9. 67 4
      TEAMModeBI/ClientApp/src/view/index/dashboard.vue
  10. 43 16
      TEAMModeBI/ClientApp/src/view/teachermanage/areamanage.vue
  11. 0 1
      TEAMModeBI/ClientApp/src/view/teachermanage/traitmanage.vue
  12. 3 3
      TEAMModeBI/Controllers/LoginController.cs
  13. 19 1
      TEAMModelFunction/ScsApisHttpTrigger.cs
  14. 40 1
      TEAMModelOS.SDK/DI/AzureStorage/AzureStorageBlobExtensions.cs
  15. 3 2
      TEAMModelOS.SDK/Extension/JwtAuthExtension.cs
  16. 47 1
      TEAMModelOS.SDK/Models/Table/OperateLog.cs
  17. 1 1
      TEAMModelOS/ClientApp/public/index.html
  18. 6 0
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/app-nav.less
  19. 87 13
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/course-content.less
  20. 43 22
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/hiteachNote-content.less
  21. 419 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ClassRecord.less
  22. 509 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ClassRecord.vue
  23. 67 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/CorrectRate.vue
  24. 106 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/OptionCount.vue
  25. 1 1
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventView.vue
  26. 83 78
      TEAMModelOS/ClientApp/src/components/student-web/HiteachView/HiteachNoteList.vue
  27. 4 4
      TEAMModelOS/ClientApp/src/components/student-web/HiteachView/HiteachView.vue
  28. 252 37
      TEAMModelOS/ClientApp/src/components/student-web/HomeView/CourseListView.vue
  29. 2 1
      TEAMModelOS/ClientApp/src/locale/lang/en-US/learnActivity.js
  30. 14 1
      TEAMModelOS/ClientApp/src/locale/lang/en-US/studentWeb.js
  31. 25 19
      TEAMModelOS/ClientApp/src/locale/lang/en-US/unit.js
  32. 2 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/learnActivity.js
  33. 14 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/studentWeb.js
  34. 7 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/unit.js
  35. 2 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/learnActivity.js
  36. 14 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/studentWeb.js
  37. 25 19
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/unit.js
  38. 9 3
      TEAMModelOS/ClientApp/src/router/routes.js
  39. 0 3
      TEAMModelOS/ClientApp/src/utils/evTools.js
  40. 1 0
      TEAMModelOS/ClientApp/src/utils/js-fn.js
  41. 6 6
      TEAMModelOS/ClientApp/src/utils/public.js
  42. 0 5
      TEAMModelOS/ClientApp/src/view/auth/Classroom.less
  43. 0 17
      TEAMModelOS/ClientApp/src/view/auth/Classroom.vue
  44. 2 4
      TEAMModelOS/ClientApp/src/view/auth/Index.vue
  45. 6 1
      TEAMModelOS/ClientApp/src/view/auth/Product.less
  46. 6 3
      TEAMModelOS/ClientApp/src/view/auth/Product.vue
  47. 172 0
      TEAMModelOS/ClientApp/src/view/auth/Serial.less
  48. 269 0
      TEAMModelOS/ClientApp/src/view/auth/Serial.vue
  49. 10 5
      TEAMModelOS/ClientApp/src/view/auth/SpaceInfo.vue
  50. 51 16
      TEAMModelOS/ClientApp/src/view/classmgt/ClassStudent.vue
  51. 1 1
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseImport.vue
  52. 1 1
      TEAMModelOS/ClientApp/src/view/forgotPw/Index.vue
  53. 9 3
      TEAMModelOS/ClientApp/src/view/learnactivity/markpaper/MarkSetting.vue
  54. 11 14
      TEAMModelOS/ClientApp/src/view/newcourse/MyCourse.vue
  55. 16 8
      TEAMModelOS/ClientApp/src/view/newcourse/NewCusMgt.vue
  56. 6 6
      TEAMModelOS/ClientApp/src/view/regist/Index.vue
  57. 31 14
      TEAMModelOS/ClientApp/src/view/student-account/stuMgt/StuMgt.vue
  58. 9 5
      TEAMModelOS/ClientApp/src/view/student-web/App.vue
  59. 25 14
      TEAMModelOS/ClientApp/src/view/task/index.vue
  60. 7 6
      TEAMModelOS/ClientApp/src/view/user/BandPhone.vue
  61. 13 73
      TEAMModelOS/Controllers/Analysis/AchievementController.cs
  62. 7 1
      TEAMModelOS/Controllers/Knowledge/KnowledgesController.cs
  63. 8 4
      TEAMModelOS/Controllers/School/ClassController.cs
  64. 2 2
      TEAMModelOS/Controllers/School/StudentController.cs
  65. 1 1
      TEAMModelOS/Controllers/School/TmdUserController.cs
  66. 2 1
      TEAMModelOS/Controllers/Teacher/InitController.cs
  67. 15 0
      TEAMModelOS/Controllers/XTest/TestController.cs
  68. 3 1
      TEAMModelOS/Filter/AuthTokenAttribute.cs
  69. 1 2
      TEAMModelOS/Services/Common/TeacherService.cs
  70. 3 3
      TEAMModelOS/TEAMModelOS.csproj

+ 4 - 0
TEAMModeBI/ClientApp/src/api/index.js

@@ -138,6 +138,10 @@ export default {
     getEachProvince(data) {
         return post('/homestatis/get-provincestics', data)
     },
+    //统计空间类型占比
+    getSpacetype(data) {
+        return post('/homestatis/get-datatypestics', data)
+    },
 
     //强制刷新人员名单(从DD服务器拉取)
     getStaffList(data) {

+ 616 - 0
TEAMModeBI/ClientApp/src/components/Ability.vue

@@ -0,0 +1,616 @@
+<template>
+    <div class="ability-box">
+        <div class="ability-d">
+            <p class="abilitys-one">
+                <i class="dian"></i>
+                <span class="tabname">微能力点</span>
+            </p>
+            <div class="abilitybox">
+                <div class="pointbox" v-for="(items,index) in abilityData" :key="index" :class="{'active':position===index}" @click="addClass(index,items)">
+                    <!-- <div class="pointbox"> -->
+                    <p>
+                        <span class="ability-num">{{items.no}}</span>
+                        <span class="ability-name">{{items.name}}</span>
+                        <span class="ability-tag" v-if="items.currency ===3">选修</span>
+                        <span class="ability-must" v-if="items.currency ===1">必修</span>
+                        <span v-if="items.currency ===0"></span>
+                    </p>
+                    <p>
+                    <div class="period">总学时:<span>{{items.hour}}</span></div>
+                    <div class="grow">成长值:<span>{{items.abilityCount}}</span></div>
+                    <div class="dimensionality">维度:<span>{{items.dimensionName}}</span></div>
+                    </p>
+                </div>
+            </div>
+        </div>
+        <div class="ability-tree">
+            <div class="chapter">
+                <div style="display:inline-block">章节</div>
+            </div>
+            <div class="ztreebox">
+                <el-tree ref='treeRef' :data="treeDatas" :props="defaultProps" node-key="id" :highlight-current="true" :current-node-key="pitchNow" default-expand-all :expand-on-click-node="false" :render-content="renderContent" @node-click="handleNodeClick" />
+            </div>
+        </div>
+        <div class="ability-recoue">
+            <div class="resource">
+                <div class="resource-title">关联资源</div>
+            </div>
+            <div class="resourcebox" v-if="datais">
+                <div class="resource-item" v-for="(item,index) in curNode" :key="item.id">
+                    <img src="@/assets/icon/image.png" v-if="item.type === 'image'" />
+                    <img src="@/assets/icon/word.png" v-else-if="item.type === 'doc' && docType.includes(getSuffix(item.title))" />
+                    <img src="@/assets/icon/excel.png" v-else-if="item.type === 'doc' && excelType.includes(getSuffix(item.title))" />
+                    <img src="@/assets/icon/ppt.png" v-else-if="item.type === 'doc' && pptType.includes(getSuffix(item.title))" />
+                    <img src="@/assets/icon/pdf.png" v-else-if="item.type === 'doc' && getSuffix(item.title) === 'pdf'" />
+                    <img src="@/assets/icon/video.png" v-else-if="item.type === 'video'" />
+                    <img src="@/assets/icon/audio.png" v-else-if="item.type === 'audio'" />
+                    <img src="@/assets/icon/item.png" v-else-if="item.type === 'item'" />
+                    <img src="@/assets/icon/folder.png" v-else-if="item.type === 'paper'" />
+                    <img src="@/assets/icon/link.png" v-else-if="item.type === 'link'" />
+                    <img src="@/assets/icon/zip.png" v-else-if="item.type === 'res'" />
+                    <img src="@/assets/icon/image.png" v-else-if="item.type === 'thum'" />
+                    <img src="@/assets/icon/unknow.png" v-else="item.type === 'other'" />
+                    <span class="title-name" @click=details(item)>{{index+1}}、{{item.title}}</span>
+                    <div class="resource-tools">
+                    </div>
+                </div>
+            </div>
+            <div class="none-data" v-else-if="datais ===false">
+                暂无数据
+            </div>
+        </div>
+    </div>
+    <!--资源预览-->
+    <div class="previewState">
+        <el-dialog v-model="previewStates" width="60%" center :before-close="closebtn">
+            <p class="resource-title">{{detailsInfo.title}}</p>
+            <!-- <img :src="detailsInfo.url" v-if="detailsInfo.type==='image'"> -->
+            <el-image class="imagepreview" :src="detailsInfo.url" :preview-src-list="srcList" :initial-index="1" v-if="detailsInfo.type==='image'">
+            </el-image>
+            <video v-else-if="detailsInfo.type == 'video'" id="previewVideo" ref="videobox" :src="detailsInfo.url" width="870" controls="controls" autoplay style="max-height: 800px;">
+                {{detailsInfo.title}}
+            </video>
+            <audio v-else-if="detailsInfo.type == 'audio'" controls>
+                <source :src="detailsInfo.url">
+                {{detailsInfo.title}}
+            </audio>
+            <template #footer>
+                <span class="dialog-footer">
+                    <el-button @click="closebtn">关闭</el-button>
+                </span>
+            </template>
+        </el-dialog>
+    </div>
+    <div class="cut-ability">
+        <el-button type="primary" plain>切换微能力方案</el-button>
+    </div>
+</template>
+<script>
+import { reactive, getCurrentInstance, ref, watch, nextTick } from 'vue'
+import dimension from '@/static/dimension.js'
+export default {
+    props: {
+        selectObject: {
+            type: String,
+            default: null,
+        },
+    },
+    setup(props) {
+        let { proxy } = getCurrentInstance()
+        let direction = dimension.DIMENSIONS()
+        let abilityData = ref([])
+        let position = ref(0)
+        //目前选中的能力点
+        let nowPitchAbility = reactive({
+            name: '',
+            no: '',
+            id: '',
+            standard: '',
+        })
+        let treeDatas = ref([])
+        //为配合tree新增而声明的变量,完整数据
+        let treeDataInfo = ref([])
+        let defaultPitch = ref('')
+        let curNode = ref(null)
+        let datais = ref(false)
+        let previewStates = ref(false)
+        let detailsInfo = ref({
+            id: '',
+            title: '',
+            url: '',
+            type: '',
+        })
+        let videobox = ref()
+        const docType = ['doc', 'docx']
+        const excelType = ['xls', 'csv', 'xlsx']
+        const pptType = ['ppt', 'pptx']
+        let srcList = ref([])
+        let treeRef = ref()
+        let pitchNow = ref('')
+        //根据区级信息获取所有的册别
+        function getAllability(datas) {
+            let data = { standard: datas }
+            let num = ''
+            proxy.$api.getProjectAbility(data).then(async (res) => {
+                console.log(res, '区级的返回册别')
+                for (let i in direction) {
+                    let Ids = direction[i].code
+                    let names = direction[i].val
+                    res.abilities.forEach((element) => {
+                        element.dimension === Ids ? (element.dimensionName = names) : ''
+                        element.name === '信息化能力工程2.0通识性课程' ? (element.no = '通用') : ''
+                    })
+                }
+                res.abilities.sort(function (s, t) {
+                    var a = s.no.toLowerCase()
+                    var b = t.no.toLowerCase()
+                    if (a === '通用' || b === '通用') {
+                        return -1
+                    }
+                    if (a.length === 2) {
+                        a = a.slice(0, a.length - 1) + '0' + a.slice(-1)
+                    }
+                    if (b.length === 2) {
+                        b = b.slice(0, b.length - 1) + '0' + b.slice(-1)
+                    }
+                    if (a < b) return -1
+                    if (a > b) return 1
+                    return 0
+                })
+                abilityData.value = []
+                abilityData.value.push(...res.abilities)
+                for (let x in res.abilities) {
+                    let result = await getsectiontrre(res.abilities[x].standard, res.abilities[x].id)
+                    if (result === 0) {
+                        continue
+                    } else {
+                        num = x
+                        break
+                    }
+                }
+                nowPitchAbility.name = res.abilities[num].name
+                nowPitchAbility.no = res.abilities[num].no
+                nowPitchAbility.id = res.abilities[num].id
+                nowPitchAbility.standard = res.abilities[num].standard
+                addClass(num, res.abilities[num], 'init')
+            })
+        }
+        //给选中的添加样式
+        function addClass(val, item, state) {
+            nowPitchAbility.name = item.name
+            nowPitchAbility.no = item.no
+            nowPitchAbility.id = item.id
+            nowPitchAbility.standard = item.standard
+            position.value = Number(val)
+            state === 'init' ? '' : getsectiontrre(item.standard, item.id)
+            handleNodeClick
+        }
+        //查询章节tree
+        async function getsectiontrre(standardName, ids) {
+            let resultData = ''
+            let datas = { standard: standardName, abilityId: ids }
+            await proxy.$api.getAbilityDeatils(datas).then(async (res) => {
+                resultData = res.abilityTaskTreeNodes.length
+                console.log(res, '查询章节返回')
+                ;(res.state === 200) !== 0 ? await conductTree(res.abilityTaskTreeNodes) : ElMessage.error('章节数据API异常')
+            })
+            return resultData
+        }
+        //处理章节的内容数据格式(tree)
+        function conductTree(data) {
+            console.log(data, '进来的值')
+            if (data.length === 0) {
+                treeDatas.value = []
+                return
+            }
+            let datas = data
+            treeDatas.value = []
+            for (let i in datas) {
+                let resultD = datas[i]
+                let outermostData = datas[i].trees
+                console.log(outermostData, '循环的内容')
+                //获取章节
+                for (let y in outermostData) {
+                    treeDatas.value.push(outermostData[y])
+                }
+                treeDataInfo.value.push(resultD)
+            }
+
+            handleNodeClick(treeDatas.value[0])
+            defaultPitch.value = treeDatas.value[0].id
+        }
+        //渲染tree
+        function renderContent(h, { node, data }) {
+            return h(
+                'span',
+                {
+                    class: 'custom-tree-nodes',
+                    title: data.title,
+                },
+                h('span', null, data.title),
+                h(
+                    'i',
+                    data.rnodes && data.rnodes.length ? { class: 'tree-icon' } : { class: 'tree-none' },
+                    <svg class="treeInfoicon" aria-hidden="true">
+                        <use xlink:href="#icon-ziyuan"></use>
+                    </svg>
+                )
+            )
+        }
+        //当前点击的tree上的点
+        function handleNodeClick(data) {
+            console.log(data, '当前点击的元素')
+            curNode.value = data.rnodes
+            console.log(curNode.value, '资源数据')
+            data.rnodes.length ? (datais.value = true) : (datais.value = false)
+        }
+        //查看资源详情
+        function details(val) {
+            // detailsInfo = val
+            console.log(val, '查看详情传进来的值')
+            console.log(detailsInfo, '赋值过后')
+            detailsInfo.value = {}
+            let bolbs = JSON.parse(localStorage.getItem('blobInfo'))
+            let host = getBlobHost(bolbs.osblob_uri)
+            let space = bolbs.osblob_uri.slice(bolbs.osblob_uri.indexOf('cn') + 3)
+            let urls = host + '/' + space + val.link + '?' + bolbs.osblob_sas
+            if (getSuffix(val.title.toLowerCase()) == 'pdf') {
+                window.open('https://www.teammodel.cn/web/viewer.html?file=' + encodeURIComponent(urls))
+            } else if (val.type === 'doc') {
+                window.open('https://view.officeapps.live.com/op/view.aspx?src=' + encodeURIComponent(urls))
+            } else if (val.type === 'image') {
+                detailsInfo.value.title = val.title
+                detailsInfo.value.type = val.type
+                detailsInfo.value.id = val.id
+                detailsInfo.value.url = urls
+                detailsInfo.value.url ? (previewStates.value = true) : ''
+                srcList.value.push(urls)
+            } else {
+                detailsInfo.value.title = val.title
+                detailsInfo.value.type = val.type
+                detailsInfo.value.id = val.id
+                detailsInfo.value.url = urls
+                detailsInfo.value.url ? (previewStates.value = true) : ''
+            }
+        }
+        //查看资源 关闭视频
+        function closebtn() {
+            previewStates.value = false
+            console.log(videobox, '视频')
+            videobox.value ? videobox.value.pause() : ''
+        }
+        //处理HOST返回截取
+        function getBlobHost(url) {
+            let s = url || store.state.user.userProfile.blob_uri || store.state.user.studentProfile.blob_uri
+            let pattern = /[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/
+            return s.split('//')[0] + '//' + s.match(pattern)[0]
+        }
+        function getSuffix(name) {
+            return name.substr(name.lastIndexOf('.') + 1)
+        }
+        watch(
+            props,
+            (newProps) => {
+                nextTick(() => {
+                    getAllability(props.selectObject)
+                })
+            },
+            { immediate: true, deep: true }
+        )
+        watch(defaultPitch, (newdata, olddata) => {
+            nextTick(() => {
+                treeRef.value.setCurrentKey(newdata)
+            }),
+                { immediate: true, deep: true }
+        })
+        return {
+            getAllability,
+            direction,
+            abilityData,
+            position,
+            nowPitchAbility,
+            addClass,
+            getsectiontrre,
+            conductTree,
+            defaultPitch,
+            treeDataInfo,
+            treeDatas,
+            renderContent,
+            datais,
+            curNode,
+            handleNodeClick,
+            previewStates,
+            detailsInfo,
+            details,
+            closebtn,
+            getBlobHost,
+            getSuffix,
+            videobox,
+            docType,
+            excelType,
+            pptType,
+            srcList,
+            treeRef,
+            pitchNow,
+        }
+    },
+}
+</script>
+<style scoped>
+.ability-box {
+    display: flex;
+    width: 100%;
+    height: 100%;
+    line-height: 20px;
+}
+
+.ability-d {
+    width: 25%;
+    height: 65vh;
+    overflow: hidden;
+}
+.ability-tree {
+    width: 40%;
+    height: 65vh;
+    border-right: 1px dashed rgb(180, 178, 178);
+    margin-top: 0px;
+    overflow: hidden;
+}
+/* .ability-tree:hover {
+    overflow-y: auto;
+} */
+.ability-recoue {
+    width: 35%;
+    height: 65vh;
+}
+.abilitys-one {
+    margin-bottom: 0%;
+    padding-left: 1%;
+    line-height: 40px;
+    padding-right: 4%;
+    background-color: #e9eef3;
+    border-bottom: 1px solid #e9eef3;
+}
+.dian {
+    content: '';
+    display: inline-block;
+    border: 4px solid #16c18e;
+    border-radius: 50%;
+    margin-right: 10px;
+    margin-bottom: 2px;
+}
+.pointbox {
+    width: 100%;
+    padding: 3% 4%;
+}
+.pointbox:hover {
+    background-color: #bde0f2;
+    cursor: pointer;
+}
+.ability-num {
+    font-size: 12px;
+    padding: 0 8px;
+    background-color: #70b1e7;
+    color: #fff;
+    margin-right: 5px;
+    margin-bottom: 1px;
+    vertical-align: text-bottom;
+    border-radius: 4px;
+}
+.ability-name {
+    font-size: 16px;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+.ability-tag {
+    background-color: initial;
+    color: #00a4e8;
+    margin-left: 5px;
+    border: 1px solid #00a4e8;
+    padding: 0 8px;
+    font-size: 12px;
+    margin-right: 5px;
+    margin-bottom: 1px;
+    vertical-align: text-bottom;
+    border-radius: 4px;
+}
+.ability-must {
+    background-color: initial;
+    color: #32c270;
+    margin-left: 5px;
+    border: 1px solid #32c270;
+    padding: 0 8px;
+    font-size: 12px;
+    margin-right: 5px;
+    margin-bottom: 1px;
+    vertical-align: text-bottom;
+    border-radius: 4px;
+}
+.period {
+    display: inline-block;
+    width: 30%;
+    height: 20px;
+    font-size: 12px;
+    border-right: 1px solid #ccc;
+    margin-right: 2%;
+    color: #909399;
+}
+.grow {
+    display: inline-block;
+    width: 30%;
+    font-size: 12px;
+    border-right: 1px solid #ccc;
+    margin-right: 2%;
+    color: #909399;
+}
+.dimensionality {
+    display: inline-block;
+    width: 30%;
+    font-size: 12px;
+    color: #909399;
+}
+.abilitybox {
+    height: 63vh;
+    width: 100%;
+    border-right: 1px dashed rgb(180, 178, 178);
+    overflow: auto;
+}
+/* .abilitybox:hover {
+    overflow-y: auto;
+} */
+.active {
+    background-color: #bde0f2;
+}
+.ztreebox {
+    height: 63vh;
+    width: 100%;
+    overflow: auto;
+}
+/*章节样式*/
+.chapter {
+    /* width: 600px !important; */
+    position: sticky !important;
+    top: 0px;
+    background-color: #e9eef3;
+    z-index: 999;
+    height: 40px;
+    line-height: 40px;
+}
+.chapter::before {
+    content: '';
+    display: inline-block;
+    border: 4px solid #16c18e;
+    border-radius: 50%;
+    margin-right: 10px;
+    margin-bottom: 2px;
+}
+.resourcebox {
+    display: flex;
+    flex-direction: column;
+    padding: 10px 20px;
+    overflow: auto;
+}
+.resource {
+    height: 40px;
+    width: 100%;
+    display: flex;
+    align-items: center;
+    padding-left: 15px;
+    font-size: 14px;
+    position: sticky;
+    top: 0px;
+    background-color: #e9eef3;
+    z-index: 999;
+    border-bottom: 1px solid #e9eef3;
+}
+.resource-title {
+    width: 80%;
+    display: inline-block;
+}
+.resource::before {
+    content: '';
+    display: inline-block;
+    border: 4px solid #16c18e;
+    border-radius: 50%;
+    margin-right: 10px;
+    margin-bottom: 2px;
+}
+.resource-item {
+    position: relative;
+    display: flex;
+    align-items: center;
+    border-bottom: 1px solid rgb(229, 229, 229);
+    padding: 10px 20px;
+}
+.resource-item img {
+    width: 25px;
+    margin-right: 20px;
+}
+.resource-item:hover .resource-tools {
+    display: block;
+}
+.resource-item:hover {
+    background-color: #d6d6d6;
+    cursor: pointer;
+}
+.none-data {
+    width: 100%;
+    height: 63vh;
+    font-size: 28px;
+    font-weight: 800;
+    margin: 0 auto;
+    display: flex;
+    justify-content: center;
+    flex-direction: row;
+    align-items: center;
+    color: #ccc;
+}
+.previewState .el-dialog__body {
+    text-align: center;
+}
+.previewState .el-dialog__body .resource-title {
+    font-size: 16px;
+    font-weight: 700;
+}
+.previewState .el-dialog--center {
+    background-color: #e9eef3;
+    line-height: 0px !important;
+}
+.previewState .el-dialog__body img {
+    max-width: 1100px;
+    max-height: 600px;
+}
+.cut-ability {
+    position: absolute;
+    top: -12%;
+    right: 3%;
+}
+</style>
+<style>
+.treeInfoicon {
+    width: 1.5em;
+    height: 1.5em;
+    vertical-align: -0.5em;
+    fill: currentColor;
+    overflow: hidden;
+    margin-bottom: 1px;
+    margin-left: 1%;
+}
+.custom-tree-nodes {
+    width: 80%;
+    font-size: 14px;
+    height: 40px;
+    line-height: 40px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+.tree-icon {
+    opacity: 1;
+}
+.tree-none {
+    opacity: 0;
+}
+.el-tree-node__content {
+    height: 40px;
+    position: relative;
+}
+.tree-node-tools {
+    position: absolute;
+    right: 0;
+    color: rgb(77, 130, 245);
+}
+.tree-node-tools a {
+    font-size: 12px;
+    margin-right: 10px;
+}
+.ability-tree .tree-node-tools {
+    display: none;
+}
+.ability-tree .el-tree-node__content:hover .tree-node-tools {
+    display: inline-block;
+}
+.previewState .el-dialog--center .el-dialog__body {
+    text-align: center;
+}
+</style>

+ 4 - 16
TEAMModeBI/ClientApp/src/components/AbilityTree.vue

@@ -68,7 +68,7 @@
     </div>
     <!--资源预览-->
     <div class="previewState">
-        <el-dialog v-model="previewStates" width="60%" center>
+        <el-dialog v-model="previewStates" width="60%" center :before-close="closebtn">
             <p class="resource-title">{{detailsInfo.title}}</p>
             <!-- <img :src="detailsInfo.url" v-if="detailsInfo.type==='image'"> -->
             <el-image class="imagepreview" :src="detailsInfo.url" :preview-src-list="srcList" :initial-index="1" v-if="detailsInfo.type==='image'">
@@ -206,7 +206,7 @@ export default {
                 })
                 .catch(() => {})
         }
-        //删除子节点
+        //删除子节点defaultinfo
         function remove(node, data) {
             console.log(props, '获取现在的树状')
             console.log(nowPitchCb.value, '当前的数据')
@@ -525,7 +525,7 @@ export default {
             props,
             (nweProps) => {
                 nextTick(() => {
-                    console.log(watchModel.value, '值')
+                    console.log(nweProps.defaultinfo, '值')
                     if (watchModel.value) {
                         treeRef.value.setCurrentKey(nweProps.defaultinfo)
                         fileList.value.id = nweProps.defaultinfo
@@ -610,7 +610,6 @@ export default {
     height: 40px;
     position: relative;
 }
-
 .custom-tree-node {
     width: 80%;
     font-size: 14px;
@@ -633,10 +632,9 @@ export default {
 }
 /*章节样式*/
 .chapter {
-    width: 600px !important;
+    /* width: 600px !important; */
     position: sticky !important;
     top: 0px;
-    background-color: #e9eef3;
     z-index: 999;
 }
 .chapter,
@@ -824,14 +822,4 @@ export default {
 #previewVideo {
     width: 100%;
 }
-@media screen and (max-width: 1366px) {
-    .mainBox .column .panel .chart {
-        height: 11vh;
-    }
-}
-@media screen and (max-width: 1920px) {
-    .mainBox .column .panel .chart {
-        height: 16vh;
-    }
-}
 </style>

+ 57 - 20
TEAMModeBI/ClientApp/src/components/echarts/customPie.vue

@@ -3,7 +3,7 @@
     <div ref="myEcharts" :style="{ height, width }"></div>
 </template>
 <script>
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, watch, nextTick } from 'vue'
 import * as echarts from 'echarts'
 export default {
     name: 'baseBar',
@@ -17,7 +17,7 @@ export default {
             default: '100%',
         },
         mapData: {
-            type: Array,
+            type: Object,
             default: () => [],
         },
         title: {
@@ -26,12 +26,22 @@ export default {
         },
     },
     setup(props) {
-        console.log(props)
+        console.log(props, 'pie图页面接受')
         const myEcharts = ref(null)
         const chart = new InitChart(props, myEcharts)
         onMounted(() => {
             chart.init()
         })
+        watch(
+            props,
+            (newProps) => {
+                console.log(newProps, '监听')
+                nextTick(() => {
+                    chart.init(props.mapData)
+                })
+            },
+            { immediate: true, deep: true }
+        )
         return {
             myEcharts,
         }
@@ -46,41 +56,43 @@ class InitChart {
         }
     }
 
-    init() {
+    init(data) {
+        console.log(data, '接收')
+        if (data === undefined) {
+            return
+        }
+        var totalSize = 0
+        data.various.forEach((element) => {
+            totalSize = Number(totalSize) + Number(element.value)
+        })
+        console.log(totalSize, '查看总和')
         this.state.chart && this.destory()
         this.state.chart = echarts.init(this.myEcharts.value)
-
         this.state.chart.setOption({
             legend: {
-                top: '90%',
+                top: '80%',
                 itemWidth: 10,
                 itemHeight: 10,
                 textStyle: {
                     color: 'rgba(255,255,255,.5)',
-                    fontSize: '12',
+                    fontSize: '10',
                 },
             },
             tooltip: {
                 trigger: 'item',
-                formatter: '{a} <br/>{b} : {c} ({d}%)',
+                formatter: '{a} <br/>{b} :{c}GB     ({d}%)',
             },
             // 注意颜色写的位置
-            color: ['#006cff', '#60cda0', '#ed8884', '#ff9f7f', '#0096ff', '#9fe6b8', '#32c5e9', '#1d9dff'],
+            color: ['#006cff', '#60cda0', '#ed8884', '#ff9f7f', '#0096ff', '#9fe6b8', '#32c5e9', '#1d9dff', '#8e3e1f', '#f36c21', '#3c3645', '#b7ba6b', '#f391a9', '#b2d235'],
             series: [
                 {
-                    name: '点位统计',
+                    name: '数据占比',
                     type: 'pie',
                     // 如果radius是百分比则必须加引号
-                    radius: ['10%', '70%'],
+                    radius: ['10%', '65%'],
                     center: ['50%', '42%'],
                     roseType: 'radius',
-                    data: [
-                        { value: 20, name: '音频' },
-                        { value: 26, name: '图片' },
-                        { value: 24, name: 'PDF文档' },
-                        { value: 25, name: 'word文档' },
-                        { value: 42, name: '视频' },
-                    ],
+                    data: data.various,
                     // 修饰饼形图文字相关的样式 label对象
                     label: {
                         fontSize: 12,
@@ -88,9 +100,9 @@ class InitChart {
                     // 修饰引导线样式
                     labelLine: {
                         // 连接到图形的线长度
-                        length: 10,
+                        length: 8,
                         // 连接到文字的线长度
-                        length2: 10,
+                        length2: 8,
                     },
                 },
             ],
@@ -99,7 +111,32 @@ class InitChart {
             this.state.chart.resize()
         })
     }
+    convertSizes(limit) {
+        console.log(limit, '收到的大小')
+        var size = ''
+        if (limit < 0.1 * 1024) {
+            //小于0.1KB,则转化成B
+            size = limit.toFixed(2) + 'B'
+        } else if (limit < 0.1 * 1024 * 1024) {
+            //小于0.1MB,则转化成KB
+            size = (limit / 1024).toFixed(2) + 'KB'
+        } else if (limit < 0.1 * 1024 * 1024 * 1024) {
+            //小于0.1GB,则转化成MB
+            size = (limit / (1024 * 1024)).toFixed(2) + 'MB'
+        } else {
+            //其他转化成GB
+            size = (limit / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
+        }
 
+        var sizeStr = size + '' //转成字符串
+        var index = sizeStr.indexOf('.') //获取小数点处的索引
+        var dou = sizeStr.substr(index + 1, 2) //获取小数点后两位的值
+        if (dou == '00') {
+            //判断后两位是否为00,如果是则删除00
+            return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
+        }
+        return size
+    }
     destory() {
         this.state.chart.dispose()
         window.removeEventListener('resize', () => {

+ 2 - 0
TEAMModeBI/ClientApp/src/main.js

@@ -7,11 +7,13 @@ import 'element-plus/dist/index.css'
 import axios from '@/api/index.js'
 import store from '@/store'
 import inspect from '@/until/inspect.js'
+import common from '@/until/common.js'
 import powerfulTable from "el-plus-powerful-table-ts";
 import { ElIcon } from 'element-plus'
 const app = createApp(App)
 app.config.globalProperties.$api = axios
 app.config.globalProperties.$access = inspect
+app.config.globalProperties.$common = common
 app.use(router)
 app.use(ElementPlus)
 app.use(store)

+ 29 - 0
TEAMModeBI/ClientApp/src/until/common.js

@@ -0,0 +1,29 @@
+export default {
+    //字节大小转换
+    convertSize(limit) {
+        console.log(limit, '收到的大小')
+        var size = ''
+            // if (limit < 0.1 * 1024) {
+            //     //小于0.1KB,则转化成B
+            //     size = limit.toFixed(2) + 'B'
+            // } else if (limit < 0.1 * 1024 * 1024) {
+            //     //小于0.1MB,则转化成KB
+            //     size = (limit / 1024).toFixed(2) + 'KB'
+            // } else if (limit < 0.1 * 1024 * 1024 * 1024) {
+            //     //小于0.1GB,则转化成MB
+            //     size = (limit / (1024 * 1024)).toFixed(2) + 'MB'
+            // } else {
+            //     //其他转化成GB
+            //     size = (limit / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
+            // }
+        size = (limit / (1024 * 1024 * 1024)).toFixed(2)
+        var sizeStr = size + '' //转成字符串
+        var index = sizeStr.indexOf('.') //获取小数点处的索引
+        var dou = sizeStr.substr(index + 1, 2) //获取小数点后两位的值
+        if (dou == '00') {
+            //判断后两位是否为00,如果是则删除00
+            return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
+        }
+        return size
+    }
+}

+ 11 - 4
TEAMModeBI/ClientApp/src/until/http.js

@@ -1,16 +1,23 @@
 import axios from 'axios';
 axios.defaults.timeout = 1000000; //设置超时时长
 axios.defaults.baseURL = '';
-
 //http request 拦截器
 axios.interceptors.request.use(
     config => {
         // const token = getCookie('名称');
         config.data = JSON.stringify(config.data);
-        config.headers = {
-            'Content-Type': 'application/json',
-            'Authorization': ""
+        if (config.url.indexOf('upd-abilitytask') != -1) {
+            config.headers = {
+                'Content-Type': 'application/json',
+                'x-auth-authtoken': JSON.parse(localStorage.id_token)
+            }
+        } else {
+            config.headers = {
+                'Content-Type': 'application/json',
+                'Authorization': ""
+            }
         }
+
         return config;
     },
     error => {

+ 26 - 0
TEAMModeBI/ClientApp/src/until/inspect.js

@@ -16,4 +16,30 @@ export default {
     guid() {
         return this.randomId() + this.randomId() + '-' + this.randomId() + '-' + this.randomId() + '-' + this.randomId() + '-' + this.randomId() + this.randomId() + this.randomId()
     },
+    //字节大小转换
+    convertSize(limit) {
+        var size = ''
+        if (limit < 0.1 * 1024) {
+            //小于0.1KB,则转化成B
+            size = limit.toFixed(2) + 'B'
+        } else if (limit < 0.1 * 1024 * 1024) {
+            //小于0.1MB,则转化成KB
+            size = (limit / 1024).toFixed(2) + 'KB'
+        } else if (limit < 0.1 * 1024 * 1024 * 1024) {
+            //小于0.1GB,则转化成MB
+            size = (limit / (1024 * 1024)).toFixed(2) + 'MB'
+        } else {
+            //其他转化成GB
+            size = (limit / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
+        }
+
+        var sizeStr = size + '' //转成字符串
+        var index = sizeStr.indexOf('.') //获取小数点处的索引
+        var dou = sizeStr.substr(index + 1, 2) //获取小数点后两位的值
+        if (dou == '00') {
+            //判断后两位是否为00,如果是则删除00
+            return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
+        }
+        return size
+    }
 }

+ 67 - 4
TEAMModeBI/ClientApp/src/view/index/dashboard.vue

@@ -67,8 +67,8 @@
                     <div class="panel-footer"></div>
                 </div>
                 <div class="panel pie1">
-                    <div class="chart">
-                        <customPie></customPie>
+                    <div class="chart" v-loading="loading.customPie" element-loading-background="rgba(62,182,250, 0.3)">
+                        <CustomPie :mapData="spaceTypsdata"></CustomPie>
                     </div>
                     <div class="panel-footer"></div>
                 </div>
@@ -130,6 +130,7 @@ export default {
             BaseBar: true,
             BasicPie: true,
             map: true,
+            customPie: true,
         })
         //数据综合
         let totalData = ref([
@@ -138,6 +139,8 @@ export default {
             { name: '学生总人数', value: '' },
             { name: '空间总大小', value: '' },
         ])
+        //空间各类型数据(pie)
+        let spaceTypsdata = ref({})
         //获取各市学校数量数据
         function handleBarData() {
             //基础数据
@@ -365,6 +368,47 @@ export default {
                 res.state === 200 ? ((mapData.value = res.provinceStics), (loading.value.map = false)) : ''
             })
         }
+        //获取空间类型占比
+        async function getSpeacetypes() {
+            await proxy.$api.getSpacetype({}).then((res) => {
+                console.log(res, '空间类型占比')
+                let dataArr = {
+                    totalSize: '',
+                    various: [
+                        { value: '', size: 0, name: '教材' },
+                        { value: '', size: 0, name: '图片' },
+                        { value: '', size: 0, name: '视频' },
+                        { value: '', size: 0, name: '音频' },
+                        { value: '', size: 0, name: '文档' },
+                        { value: '', size: 0, name: 'HiTeach上传的数据' },
+                        { value: '', size: 0, name: '应用数据' },
+                        { value: '', size: 0, name: '平台数据' },
+                        { value: '', size: 0, name: '其他' },
+                    ],
+                }
+                res.state === 200 ? (dataArr.totalSize = res.totalSize) : ''
+                res.stics.forEach((item) => {
+                    item.key === 'vote' || item.key === 'survey' || item.key === 'train' || item.key === 'item' || item.key === 'paper' || item.key === 'exam'
+                        ? (dataArr.various[6].size = dataArr.various[6].size + item.value)
+                        : ''
+                    item.key === 'homework' || item.key === 'res' || item.key === 'syllabus' ? (dataArr.various[0].size = dataArr.various[0].size + item.value) : ''
+                    item.key === 'image' ? (dataArr.various[1].size = dataArr.various[1].size + item.value) : ''
+                    item.key === 'video' ? (dataArr.various[2].size = dataArr.various[2].size + item.value) : ''
+                    item.key === 'audio' ? (dataArr.various[3].size = dataArr.various[3].size + item.value) : ''
+                    item.key === 'doc' ? (dataArr.various[4].size = dataArr.various[4].size + item.value) : ''
+                    item.key === 'records' ? (dataArr.various[5].size = dataArr.various[5].size + item.value) : ''
+                    item.key === 'yxpt' || item.key === 'jyzx' ? (dataArr.various[7].size = dataArr.various[7].size + item.value) : ''
+                    item.key === 'thum' || item.key === 'avatar' || item.key === 'other' || item.key === 'temp' ? (dataArr.various[8].size = dataArr.various[8].size + item.value) : ''
+                })
+                dataArr.various.forEach((item) => {
+                    item.value = Number(proxy.$common.convertSize(item.size))
+                })
+                spaceTypsdata.value = dataArr
+                // let ss = proxy.$common.convertSize(res.typeStics.video)
+                // console.log(ss, '大小')
+                loading.value.customPie = false
+            })
+        }
         //跳转到管理界面
         function skipbtn() {
             router.push({ path: '/home/teach' })
@@ -372,7 +416,26 @@ export default {
         handleBarData()
         getEachTotal()
         getMapdata()
-        return { proxy, store, barData, handleBarData, no1Data, handleAreaBar, AcrossBar, handelPie, schoolPie, loading, getEachTotal, totalData, getMapdata, mapData, skipbtn }
+        getSpeacetypes()
+        return {
+            proxy,
+            store,
+            barData,
+            handleBarData,
+            no1Data,
+            handleAreaBar,
+            AcrossBar,
+            handelPie,
+            schoolPie,
+            loading,
+            getEachTotal,
+            totalData,
+            getMapdata,
+            mapData,
+            skipbtn,
+            getSpeacetypes,
+            spaceTypsdata,
+        }
     },
 }
 </script>
@@ -459,7 +522,7 @@ ul > li {
     font-weight: 400;
 }
 .mainBox .column .panel .chart {
-    height: 25vh;
+    height: 27vh;
 }
 .mainBox .column .panel .panel-footer {
     position: absolute;

+ 43 - 16
TEAMModeBI/ClientApp/src/view/teachermanage/areamanage.vue

@@ -44,7 +44,7 @@
                                 <ul>
                                     <li class="details-list-school" v-for="(item,index) in notjoinSchool" :key="item.id" :class="{'active':position===index}">
                                         <div class="list-school-logo">
-                                            <el-image style="width: 60px; height: 60px" :src="item.picture" :fit="fit"></el-image>
+                                            <el-image style="width: 60px; height: 60px" :src="item.picture" :fit="fill"></el-image>
                                         </div>
                                         <div class="list-school-name">
                                             <div>名称:<span>{{item.name}}</span></div>
@@ -92,15 +92,11 @@
                         </div>
                     </el-tab-pane>
                     <el-tab-pane label="微能力方案" name="adjust">
-                        <div class="cuttable">
+                        <div class="areaAbilitys" v-if="abilityModel ===true">
+                            <Ability :selectObject="currentlySelect.standard"></Ability>
+                        </div>
+                        <div class="cuttable" v-else-if="abilityModel ===false">
                             <el-table :data="tableData" style="width: 100%" :highlight-current-row="true" @row-click="cutpitch">
-                                <!-- <el-table-column label="" width="70" align="center">
-                                    <template #default="scope">
-                                        <svg class="cuticon" aria-hidden="true" v-show="scope.row.pitchState">
-                                            <use xlink:href="#icon-003-xuanze"></use>
-                                        </svg>
-                                    </template>
-                                </el-table-column> -->
                                 <el-table-column prop="index" label="编号" width="180" type=index align="center" />
                                 <el-table-column prop="standardName" label="名称" width="180" align="center" />
                                 <el-table-column prop="name" label="来源" align="center" />
@@ -113,24 +109,31 @@
                                     </template>
                                 </el-table-column>
                             </el-table>
-                        </div>
-                        <div class="cuttable-btn">
-                            <el-button type="primary" @click="confirm">确定</el-button>
-                            <el-button @click="adjustmentbox = false">取消</el-button>
+                            <div class="cuttable-btn">
+                                <el-button type="primary" @click="confirm">确定</el-button>
+                                <el-button @click="adjustmentbox = false">取消</el-button>
+                            </div>
                         </div>
                     </el-tab-pane>
                 </el-tabs>
+                <div class="cut-ability" v-if="activeName ==='adjust'" @click="abilityModel=!abilityModel">
+                    <el-button type="primary" plain>{{cutbtnTitle}}</el-button>
+                </div>
             </el-dialog>
         </div>
     </div>
 </template>
 <script>
-import { ref, onMounted, getCurrentInstance, reactive, h } from 'vue'
+import { ref, onMounted, getCurrentInstance, reactive, h, watch } from 'vue'
 import { useStore } from 'vuex'
 import { ElMessageBox, ElMessage } from 'element-plus'
 import router from '@/router/index.js'
 import { useRouter } from 'vue-router'
+import Ability from '@/components/Ability.vue'
 export default {
+    components: {
+        Ability,
+    },
     setup() {
         let { proxy } = getCurrentInstance()
         let PowerShow = proxy.$access.inspectPower('batcharea-upd')
@@ -147,6 +150,8 @@ export default {
         let activeName = ref('add')
         let notjoinSchool = ref([])
         let position = ref()
+        let cutbtnTitle = ref('切换微能力方案')
+        let abilityModel = ref(true)
         const routerInfo = useRouter()
         const tableDatas = ref([])
         onMounted(() => {
@@ -162,6 +167,7 @@ export default {
         }
         //table按钮
         async function operation(index, row, state) {
+            console.log(row)
             currentlySelect.value = row
             let dialogData = store.state.point
             dialogData.forEach((items, index) => {
@@ -294,6 +300,10 @@ export default {
         function changeStyle(index) {
             position.value = index
         }
+        watch(abilityModel, (newdata) => {
+            console.log(newdata)
+            newdata ? (cutbtnTitle.value = '切换微能力方案') : (cutbtnTitle.value = '查看能力点数据')
+        })
         return {
             nowPitch,
             PowerShow,
@@ -318,6 +328,9 @@ export default {
             PowerShow,
             deleteRow,
             loading,
+            currentlySelect,
+            cutbtnTitle,
+            abilityModel,
         }
     },
 }
@@ -543,6 +556,11 @@ export default {
     min-height: 55vh;
     padding: 1% 2%;
 }
+.cut-ability {
+    position: absolute;
+    top: 7%;
+    right: 1.5%;
+}
 </style>
 <style>
 .traitfrom .refer {
@@ -572,8 +590,9 @@ export default {
 .cuttable {
     width: 98%;
     padding: 1%;
-    height: 60vh;
-    overflow-y: auto;
+    height: 67vh;
+    overflow-y: hidden;
+    position: relative;
 }
 .cuttable-btn {
     width: 16%;
@@ -599,6 +618,14 @@ export default {
 .areamanabox .el-table__header-wrapper {
     line-height: 60px;
 }
+.cuttable .el-table--fit {
+    width: 100%;
+    height: 60vh;
+    overflow: auto;
+}
+.adjustmentDialog .el-dialog {
+    --el-dialog-margin-top: 11vh;
+}
 </style>
 
 

+ 0 - 1
TEAMModeBI/ClientApp/src/view/teachermanage/traitmanage.vue

@@ -500,7 +500,6 @@ export default {
                     if (b.length === 2) {
                         b = b.slice(0, b.length - 1) + '0' + b.slice(-1)
                     }
-
                     if (a < b) return -1
                     if (a > b) return 1
                     return 0

+ 3 - 3
TEAMModeBI/Controllers/LoginController.cs

@@ -242,7 +242,7 @@ namespace TEAMModeBI.Controllers
                                 depts.Add(temp.ToString());
                             }
 
-                            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, teacher.id,teacher.name?.ToString(),teacher.picture?.ToString(),_option.JwtSecretKey, scope: Constant.ScopeTeacher, schoolID: school_code?.ToString(), standard: school_base.standard, roles:roles.ToArray(),permissions:permissions.ToArray(),ddDepts: depts.ToArray(),ddsub:ddbind.userid);
+                            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, teacher.id,teacher.name?.ToString(),teacher.picture?.ToString(),_option.JwtSecretKey, scope: Constant.ScopeTeacher, Website: "BI", schoolID: school_code?.ToString(), standard: school_base.standard, roles:roles.ToArray(),permissions:permissions.ToArray(),ddDepts: depts.ToArray(),ddsub:ddbind.userid);
 
                             return Ok(new { state = 200, auth_token = auth_token, teacher = teacher, id_token = implicit_token.id_token, access_token = implicit_token.access_token, expires_in = implicit_token.expires_in, token_type = implicit_token.token_type });
                         }
@@ -379,7 +379,7 @@ namespace TEAMModeBI.Controllers
                     }
                     else return Ok(new { state = 1, message = "该账户未绑定钉钉信息!请扫码绑定信息!" });
 
-                    auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, teacher.id, teacher.name?.ToString(), teacher.picture?.ToString(), _option.JwtSecretKey, scope: Constant.ScopeTeacher, schoolID: school_code.ToString(), standard: school_base.standard, roles: roles.ToArray(), permissions: permissions.ToArray(), ddDepts: depts.ToArray(), ddsub: ddbind.userid);
+                    auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, teacher.id, teacher.name?.ToString(), teacher.picture?.ToString(), _option.JwtSecretKey, scope: Constant.ScopeTeacher, Website: "BI", schoolID: school_code.ToString(), standard: school_base.standard, roles: roles.ToArray(), permissions: permissions.ToArray(), ddDepts: depts.ToArray(), ddsub: ddbind.userid);
                 }
 
                 var (osblob_uri, osblob_sas) = roles.Contains("area") ? _azureStorage.GetBlobContainerSAS("teammodelos", BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Delete) : _azureStorage.GetBlobContainerSAS("teammodelos", BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List);
@@ -638,7 +638,7 @@ namespace TEAMModeBI.Controllers
                         }
                         else return Ok(new { state = responseMessage.StatusCode });
                     }
-                    id_token = JwtAuthExtension.CreateAuthToken(_option.HostName, itemUser.tmdId?.ToString(), itemUser.tmdName?.ToString(), itemUser.picture?.ToString(), _option.JwtSecretKey, scope: $"assist", roles: roles?.ToArray(), permissions: permissions?.ToArray(), ddsub: itemUser.RowKey?.ToString());
+                    id_token = JwtAuthExtension.CreateAuthToken(_option.HostName, itemUser.tmdId?.ToString(), itemUser.tmdName?.ToString(), itemUser.picture?.ToString(), _option.JwtSecretKey,Website: "BI", scope: $"assist", roles: roles?.ToArray(), permissions: permissions?.ToArray(), ddsub: itemUser.RowKey?.ToString());
                 }
 
                 var (osblob_uri, osblob_sas) = roles.Contains("assist") ? _azureStorage.GetBlobContainerSAS("teammodelos", BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Delete) : _azureStorage.GetBlobContainerSAS("teammodelos", BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List);

+ 19 - 1
TEAMModelFunction/ScsApisHttpTrigger.cs

@@ -21,6 +21,8 @@ using TEAMModelOS.SDK.Models.Service;
 using HTEXLib.COMM.Helpers;
 using System.Text;
 using static TEAMModelOS.SDK.Models.Teacher;
+using TEAMModelOS.SDK.Models.Cosmos.Common.Inner;
+using Azure.Storage.Blobs.Models;
 
 namespace TEAMModelFunction
 {
@@ -52,7 +54,7 @@ namespace TEAMModelFunction
         [FunctionName("knowledge-change")]
         public async Task<IActionResult> KnowledgeChange([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req, ILogger log) {
             string data = await new StreamReader(req.Body).ReadToEndAsync();
-           var  json = JsonDocument.Parse(data).RootElement;
+            var  json = JsonDocument.Parse(data).RootElement;
             List<OldNew> old_new = null;
             string school = null;
             if (json.TryGetProperty("school", out JsonElement _school))
@@ -95,6 +97,22 @@ namespace TEAMModelFunction
                     });
                     foreach (var item in items)
                     {
+                        ItemBlob itemBlob = null;
+                        try
+                        {
+                            BlobDownloadInfo blobDownloadResult = await _azureStorage.GetBlobContainerClient($"hbcn").GetBlobClient($"/item/{item.id}/{item.id}.json").DownloadAsync();
+                            if (blobDownloadResult != null)
+                            {
+                                var blob = JsonDocument.Parse(blobDownloadResult.Content);
+                                itemBlob = blob.RootElement.ToObject<ItemBlob>();
+                                itemBlob.exercise.knowledge = item.knowledge;
+                                await _azureStorage.UploadFileByContainer("hbcn", itemBlob.ToJsonString(), "item", $"{item.id}/{item.id}.json", true);
+                            }
+                        }
+                        catch (Exception ex)
+                        {
+                            itemBlob = null;
+                        }
                         await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync(item, item.id, new PartitionKey(item.code));
                     }
                 }

+ 40 - 1
TEAMModelOS.SDK/DI/AzureStorage/AzureStorageBlobExtensions.cs

@@ -17,6 +17,9 @@ using TEAMModelOS.SDK;
 using TEAMModelOS.SDK.Extension;
 using HTEXLib.COMM.Helpers;
 using System.Text.Encodings.Web;
+using TEAMModelOS.SDK.Models.Table;
+using Microsoft.AspNetCore.Http;
+using TEAMModelOS.Models;
 
 namespace TEAMModelOS.SDK.DI
 {
@@ -261,7 +264,43 @@ namespace TEAMModelOS.SDK.DI
                 return false;
             }
         }
-
+        public static async Task SaveLog(this AzureStorageFactory azureStorage, string type, string msg,DingDing dingDing, string scope = null, string bizId = null, Option option = null, HttpContext httpContext = null)
+        {
+            OptLog log = new OptLog() { RowKey = Guid.NewGuid().ToString() };
+            try
+            {
+                object id = null, school = null, website = null ;
+                httpContext?.Items.TryGetValue("ID", out id);
+                httpContext?.Items.TryGetValue("School", out school);
+                httpContext?.Items.TryGetValue("Website", out website);
+                log.tmdId = id != null ? $"{id}" : log.tmdId;
+                string host = httpContext?.Request?.Host.Value;
+                log.school = school != null ? $"{school}" : log.school;
+                log.PartitionKey = type != null ? $"Log-{type}" : "Log-Default";
+                log.RowKey = bizId != null ? bizId : Guid.NewGuid().ToString();
+                log.platform = website!=null? $"{website}" : "Default";
+                log.msg = msg;
+                log.type = type;
+                log.scope = scope;
+                host = !string.IsNullOrWhiteSpace($"{host}") ? $"{host}" : option?.Location != null ? $"{host}" : "Default";
+                log.url =$"{host}{httpContext?.Request.Path}" ;
+                if (!string.IsNullOrWhiteSpace(msg) && msg.Length > 100)
+                {
+                    log.saveMod = 1;
+                   
+                    _ = azureStorage.UploadFileByContainer("0-public", log.ToJsonString(), "optlog", $"{log.RowKey}-{log.PartitionKey}.json");
+                    log.msg = null;
+                    await azureStorage.SaveOrUpdate<OptLog>(log);
+                }
+                else {
+                    await azureStorage.SaveOrUpdate<OptLog>(log);
+                }
+            }
+            catch (Exception ex)
+            {
+                _ = dingDing.SendBotMsg($"日志保存失败:{ex.Message},{ex.StackTrace},{log.ToJsonString()}", GroupNames.成都开发測試群組);
+            }
+        }
         /// <summary>
         /// 系统管理员 资源,题目关联,htex关联,学习活动学生上传文件关联,基本信息关联,教室平面图关联,评测冷数据关联
         /// "system": [ "res", "item", "htex", "task", "info", "room", "exam" ],

+ 3 - 2
TEAMModelOS.SDK/Extension/JwtAuthExtension.cs

@@ -14,7 +14,7 @@ namespace TEAMModelOS.SDK.Extension
 {
     public static class JwtAuthExtension
     {
-        public static string CreateAuthToken(string issuer, string id, string name, string picture, string salt, string scope, string schoolID = "", string standard = "", string[] roles = null, string[] permissions = null, int expire = 1, string[] ddDepts = null, string ddsub = null)
+        public static string CreateAuthToken(string issuer, string id, string name, string picture, string salt, string scope,string Website, string schoolID = "", string standard = "", string[] roles = null, string[] permissions = null, int expire = 1, string[] ddDepts = null, string ddsub = null)
         {
             // 設定要加入到 JWT Token 中的聲明資訊(Claims)  
             var payload = new JwtPayload {
@@ -29,7 +29,8 @@ namespace TEAMModelOS.SDK.Extension
                 { "standard",standard} ,//登入者的能力点标准
                 { "scope",scope},  //登入者的入口类型。 (teacher 教师端登录的醍摩豆ID、tmduser学生端登录的醍摩豆ID、student学生端登录校内账号的学生ID) 
                 { "dddepts",ddDepts},  //登陆者的钉钉部门id
-                {"ddsub",ddsub }   //登陆者的钉钉用户id
+                { "ddsub",ddsub } ,  //登陆者的钉钉用户id
+                { JwtRegisteredClaimNames.Website,Website}, // 學校簡碼,如果有的話
             };
 
             // 建立一組對稱式加密的金鑰,主要用於 JWT 簽章之用

+ 47 - 1
TEAMModelOS.SDK/Models/Table/OperateLog.cs

@@ -6,7 +6,7 @@ using Microsoft.Azure.Cosmos.Table;
 
 namespace TEAMModelOS.SDK.Models.Table
 {
-    [TableName(Name = "OperateLogs")]
+    [TableName(Name = "OperateLog")]
     public class OperateLog : TableEntity
     {
         /// <summary>
@@ -59,6 +59,52 @@ namespace TEAMModelOS.SDK.Models.Table
         /// </summary>
         public string owner { get; set; }
 
+        /// <summary>
+        /// 学校编码
+        /// </summary>
+        public string school { get; set; }
+    }
+    [TableName(Name = "OptLog")]
+    public class OptLog : TableEntity
+    {
+        /// <summary>
+        /// 日志平台:BI 、 IES5
+        /// </summary>
+        public string platform { get; set; }
+
+        /// <summary>
+        /// 醍摩豆ID
+        /// </summary>
+        public string tmdId { get; set; }
+
+        /// <summary>
+        /// 操作描述
+        /// </summary>
+        public string msg { get; set; }
 
+        /// <summary>
+        /// 日志类型: school-update school-del    名词-动词组合方式
+        /// </summary>
+        public string type { get; set; }
+
+        /// <summary>
+        /// 访问接口
+        /// </summary>
+        public string url { get; set; }
+
+        /// <summary>
+        /// 使用范围  private school
+        /// </summary>
+        public string scope { get; set; }
+
+        /// <summary>
+        /// 学校编码
+        /// </summary>
+        public string school { get; set; }
+        /// <summary>
+        /// 保存模式。0 Table ,1 增加保存在Blob 
+        /// </summary>
+        public int saveMod { get; set; } = 0;
+        public long time { get; set; }= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
     }
 }

+ 1 - 1
TEAMModelOS/ClientApp/public/index.html

@@ -25,7 +25,7 @@
 		};
 		</script>
 		<script type="text/javascript" id="MathJax-script" async
-		  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-svg.js">
+		  src="https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/js/tex-mml-svg.js">
 		</script>
 		<script>
 			let cloudSetting = localStorage.getItem('cloudSetting')

+ 6 - 0
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/app-nav.less

@@ -273,6 +273,12 @@
   padding-top: 4px;
 }
 .tabIcon4 {
+  position: relative;
+  font-size: 24px;
+  bottom: 1px;
+  padding-top: 4px;
+}
+.tabIcon5 {
   position: relative;
   font-size: 24px;
   top: 0px;

+ 87 - 13
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/course-content.less

@@ -4,7 +4,7 @@
     position: relative;
     float: right;
     width: 50%;
-    padding: 2% 3%;
+    padding: 2%;
     height: auto;
 
     @media screen and (max-width: 991px){
@@ -31,18 +31,6 @@
         font-weight: bolder;
         font-size: 16px;
     }
-    .group-btn {
-        cursor: pointer;
-        z-index: 1;
-        float: right;
-        position: relative;
-        top: 25px;
-        right: 5.5%;
-        font-size: 30px;
-    }
-    .group-on {
-        color: @primary;
-    }
 
     .group-title {
         background-color: #ececec;
@@ -84,7 +72,93 @@
     .my-name {
         color: #24b880;
     }
+    .list-block-box{
+        // border-right: 1px solid @border;
+        height: 100%;
+        overflow:auto;
+        margin-top:0px;
+        padding-bottom: 100px;
+        z-index: 5;
+        // width: 25%;
+        background-color: #fff;
+        box-shadow: 4px 1px 10px rgb(0 0 0 / 10%);
+
+        .list-item {
+            list-style-type: none;
+            width: 100%;
+            border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+            padding: 15px 10px 15px 15px;
+
+            &:hover {
+                background: linear-gradient(-0.75turn, #fafafa, #d4ede1);
+                color: #03966a;
+                cursor: pointer;
+            }
+
+            .list-item-info {
+                padding-right: 20px;
+                position: relative;
+
+                .list-item-title {
+                    font-weight: bolder;
+                    font-size: 14px;
+                    line-height: 22px;
+                }
+                .list-item-time {
+                    span {
+                        display: block;
+                    }
+                }
+            }
+        }
+
+        .list-item-selected {
+            background: linear-gradient(-0.75turn, #fafafa, #d4ede1);
+            color: #03966a;
+            cursor: pointer;
+            // width: 100%;
+        }
+    }
+    .info-icon{
+        text-align: right;
+        overflow: hidden;
+
+        & > span:first-child{
+            float: left;
+            font-size: 16px;
+        }
+    }
 }
 .courseContentEn {
     width: 50% !important ;
 }
+
+.group-btn {
+    cursor: pointer;
+    z-index: 1;
+    float: right;
+    position: relative;
+    top: 12px;
+    right: 6%;
+    font-size: 22px;
+}
+
+.group-on {
+    color: @primary;
+}
+
+.course-content-span {
+    /* .list-block-box{
+        width: 20%;
+
+        .list-item-time {
+            span {
+                display: inline-block !important;
+
+                &:nth-child(2) {
+                    margin-left: 20px;
+                }
+            }
+        }
+    } */
+}

+ 43 - 22
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/hiteachNote-content.less

@@ -11,18 +11,20 @@
     opacity: 0;
 }
 
-.video-player-box {
+.hiteachNote-content .video-player-box {
     width: 50%;
     justify-content: center;
     height: 450px;
+    display: flex;
+    margin-left: 5px;
 }
 
 .courseware-wrap {
     width: 50%;
     position: relative;
     height: 450px;
-    margin-top: 10px;
     background-color: #808080;
+    margin-right: 5px;
 }
 .full-screen-icon {
     float: right;
@@ -32,7 +34,7 @@
 }
 .cur-page-tag {
     position: absolute;
-    left: 0px;
+    left: 5px;
     bottom: 5px;
     width: 30px;
     height: 30px;
@@ -106,9 +108,9 @@
 .hiteachNote-content {
     float: right;
     width: 75%;
-    padding: 1% 3%;
+    // padding: 2%;
     height: 95.59vh;
-    padding-bottom: 50px;
+    // padding-bottom: 50px;
     overflow: auto;
 /*
     img {
@@ -522,8 +524,14 @@
 .hiteachNote-content .message-area {
     overflow: scroll;
     height: 815px;
+    // padding: 50px 20px;
+    // padding-left: 10.8%;
+}
+
+.hiteachNote-content .message-area .message-box{
+    position: relative;
     padding: 50px 20px;
-    padding-left: 10.8%;
+    padding-left: 11%;
 }
 
 .hiteachNote-content .message-area .message-item {
@@ -547,15 +555,27 @@
     padding: 10px;
     display: inline-block;
     border-radius: 4px;
+
+    &::before{
+        content: "";
+        position: absolute;
+        top: 12px;
+        margin-left: -16px;
+        width: 0;
+        height: 0;
+        border-style: solid;
+        border-width: 5px 7.7px 5px 0;
+        border-color: transparent #d4ede1 transparent transparent;
+    }
 }
 
-.hiteachNote-content .message-area .message-item .message-content .messagetoPPT-tag {
+.hiteachNote-content .message-area .message-box .messagetoPPT-tag {
     background-color: rgba(0, 0, 0, 0.1);
     white-space: initial;
     display: block;
     position: absolute;
-    left: -10%;
-    top: 2px;
+    left: 15px;
+    top: 50px;
     font-size: 14px;
     width: 10%;
     height: auto;
@@ -563,7 +583,7 @@
     border-radius: 4px;
 }
 
-.hiteachNote-content .message-area .message-item .message-content .messagetoPPT-tag:hover {
+.hiteachNote-content .message-area .message-box .messagetoPPT-tag:hover {
     cursor: pointer;
     color: #ffffff;
     background-color: #24b880;
@@ -613,6 +633,7 @@
 }
 
 .hiteachNote-content .message-area .message-item .message-text img {
+    display: inline-block;
     cursor: pointer;
 }
 
@@ -668,7 +689,7 @@
     cursor: pointer;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-content .link-item {
+.hiteachNote-content .message-area .message-content .link-item {
     background-color: #fff;
     margin-top: 10px;
     text-align: left;
@@ -679,23 +700,23 @@
     border-radius: 4px;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-content .link-item:hover {
+.hiteachNote-content .message-area .message-content .link-item:hover {
     color: #03966a;
     cursor: pointer;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-content .link-item .link-content {
+.hiteachNote-content .message-area .message-content .link-item .link-content {
     position: relative;
     white-space: nowrap;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-content .link-item .link-content li {
+.hiteachNote-content .message-area .message-content .link-item .link-content li {
     display: inline-block;
     border: none;
     padding: 0px !important;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-content .link-item .link-content .link-img {
+.hiteachNote-content .message-area .message-content .link-item .link-content .link-img {
     border-radius: 2px;
     position: relative;
     top: 1px;
@@ -706,25 +727,25 @@
     margin-right: 15px;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-content .link-item .link-content .link-text {
+.hiteachNote-content .message-area .message-content .link-item .link-content .link-text {
     font-weight: lighter;
     position: relative;
     top: 1px;
     color: #777777;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-content .link-item .link-content .link-text a {
+.hiteachNote-content .message-area .message-content .link-item .link-content .link-text a {
     text-decoration: none !important;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-content .link-item .link-content .link-text a p {
+.hiteachNote-content .message-area .message-content .link-item .link-content .link-text a p {
     overflow: hidden;
     width: 90%;
     text-overflow: ellipsis;
     color: #777777;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-content .link-item .link-content .link-text a .title {
+.hiteachNote-content .message-area .message-content .link-item .link-content .link-text a .title {
     color: #03966a;
     font-weight: bolder;
 }
@@ -766,18 +787,18 @@
     white-space: nowrap;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-text .message-link {
+.hiteachNote-content .message-area .message-text .message-link {
     cursor: pointer;
     font-size: 14px;
     font-weight: bolder;
     color: #24b880;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-text .message-link:hover {
+.hiteachNote-content .message-area .message-text .message-link:hover {
     color: #1c8d62;
 }
 
-.hiteachNote-content .message-area .teacher-item .message-text .message-link .link-icon {
+.hiteachNote-content .message-area .message-text .message-link .link-icon {
     font-weight: bolder;
     font-size: 20px;
     position: relative;

+ 419 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ClassRecord.less

@@ -0,0 +1,419 @@
+.class-content {
+    width: 100%;
+    display: flex;
+
+    .courseware-wrap {
+        width: 50%;
+        position: relative;
+        height: 450px;
+        background-color: #808080;
+        margin-right: 5px;
+
+        .page-wrap {
+            opacity: 1;
+        }
+
+        .cur-page-tag {
+            opacity: 0;
+        }
+        .course-cur-img{
+            height: 100%;
+        }
+    }
+}
+.cur-page-tag {
+    position: absolute;
+    left: 5px;
+    bottom: 5px;
+    width: 30px;
+    height: 30px;
+    font-size: 18px;
+    line-height: 30px;
+    display: block;
+    background: rgba(255,153,0,0.9);
+    border-radius: 50%;
+    text-align: center;
+    opacity: 1;
+    transition: opacity 0.2s;
+    color: white;
+    box-shadow: 0px 0px 5px rgb(255, 153, 0);
+}
+
+.page-wrap {
+    left: 0px;
+    bottom: 0px;
+    position: absolute;
+    padding: 6px;
+    color: white;
+    width: 100%;
+    height: 36px;
+    background: rgba(43, 51, 63, 0.7);
+    text-align: center;
+    opacity: 0;
+    transition: opacity 1.2s;
+    border-right: 2px solid black;
+}
+.record-info {
+    background-color: #f9f9f9;
+    width: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 10;
+    
+    .testTitle {
+        background-color: rgb(255, 255, 255);
+        // color: #24b880;
+        font-size: 20px;
+        padding: 5px 15px;
+        position: fixed;
+        width: 100%;
+        height: 44px;
+        border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+        z-index: 10;
+
+        .logoutIcon{
+            cursor: pointer;
+            margin-right: 10px;
+        }
+    }
+
+    img {
+        border-radius: 4px;
+        margin-right: 20px;
+        border: 1px solid rgba(173, 173, 173, 0.233);
+        width: 100%;
+    }
+
+    .video-player-box{
+        width: 50%;
+        justify-content: center;
+        height: 450px;
+        display: flex;
+        margin-left: 5px;
+    }
+
+    .message-area{
+        position: relative;
+        height: 815px;
+        padding: 10px;
+        margin-bottom: 20px;
+
+        .filter-type{
+            position: absolute;
+            right: 0;
+            top: 10px;
+            padding-right: 10px;
+            padding-left: 10px;
+            text-align: center;
+            z-index: 9;
+            display: flex;
+            flex-direction: column;
+            background-color: #F1F1F1;
+
+            span{
+                border: none;
+                padding: 0px;
+                font-size: 32px;
+                
+                &:hover {
+                    cursor: pointer;
+                    color: #24b880;
+                }
+
+                .select-filter {
+                    color: #24b880;
+                }
+            }
+        }
+
+        .message-box{
+            display: flex;
+            border-bottom: 1px #ccc solid;
+
+            .message-page{
+                width: 10%;
+                padding: 10px 15px;
+                padding-bottom: 0;
+                // border-right: 1px #ccc dashed;
+
+                img{
+                    margin-top: 5px;
+                }
+
+                .messagetoPPT-tag {
+                    background-color: rgba(0, 0, 0, 0.1);
+                    white-space: initial;
+                    display: block;
+                    font-size: 14px;
+                    height: auto;
+                    padding: 3px 10px;
+                    border-radius: 4px;
+
+                    &:hover {
+                        cursor: pointer;
+                        color: #ffffff;
+                        background-color: #24b880;
+                    }
+                }
+            }
+
+            .message-record{
+                padding: 10px 15px;
+                padding-bottom: 0;
+                width: 87%;
+
+                .message-item {
+                    white-space: nowrap;
+                    display: block;
+                    position: relative;
+                    padding-top: 20px;
+                    margin-bottom: 50px;
+
+                    .message-avatar {
+                        border-radius: 50%;
+                        float: left;
+                        width: 32px !important;
+                        height: 32px !important;
+                        margin: 0px 15px;
+                        margin-top: -10px;
+                    }
+
+                    .user-name {
+                        white-space: nowrap;
+                        font-weight: bolder;
+                        position: absolute;
+                        top: 0;
+                        left: 55px;
+                        // position: absolute;
+                        // top: -12px;
+                        // margin-left: -8px;
+                    }
+
+                    .message-content {
+                        background: #d4ede1;
+                        padding: 10px;
+                        display: inline-block;
+                        border-radius: 4px;
+                        position: relative;
+                        // width: 85%;
+                    
+                        &::before{
+                            content: "";
+                            position: absolute;
+                            top: 3px;
+                            // margin-left: -16px;
+                            left: -7px;
+                            width: 0;
+                            height: 0;
+                            border-style: solid;
+                            border-width: 5px 7.7px 5px 0;
+                            border-color: transparent #d4ede1 transparent transparent;
+                        }
+
+                        .message-text{
+                            img{
+                                max-width: 80%;
+                            }
+
+                            .pick-item {
+                                width: 100px;
+                                text-align: center;
+                                background: rgba(0, 0, 0, 0.3);
+                                height: 100px;
+                                border: 2px solid #ff9900;
+                                border-radius: 8px;
+                                display: flex;
+                                flex-direction: column;
+                                justify-content: center;
+                                .student-no {
+                                    font-size: 40px;
+                                    color: black;
+                                    font-weight: 600;
+                                }
+                                .student-name {
+                                    color: #007cff;
+                                    font-weight: 800;
+                                    font-size: 16px;
+                                }
+                            }
+                        }
+
+                    }
+
+                    .message-time {
+                        white-space: nowrap;
+                        color: gray;
+                        font-size: 10px;
+                        font-weight: 400;
+                        // position: absolute;
+                        // bottom: -20px;
+                        // margin-left: -8px;
+                        position: absolute;
+                        // bottom: 0;
+                        left: 55px;
+                    }
+                }
+                
+                .teacher-item {
+                    display: block;
+                    // padding-right: 5%;
+                    text-align: right !important;
+                    position: relative;
+                    margin-bottom: 50px;
+                    
+                    .message-avatar {
+                        border-radius: 50%;
+                        float: left;
+                        width: 32px !important;
+                        height: 32px !important;
+                        margin: 0px 15px;
+                        margin-top: -10px;
+                    }
+
+                    .user-name {
+                        white-space: nowrap;
+                        font-weight: bolder;
+                        position: absolute;
+                        top: 0;
+                        left: 55px;
+                        // position: absolute;
+                        // top: -12px;
+                        // margin-left: -8px;
+                    }
+
+                    .message-content {
+                        background: #ffecc2;
+                        padding: 10px;
+                        display: inline-block;
+                        border-radius: 4px;
+                        position: relative;
+                        // width: 85%;
+                    
+                        &::before{
+                            content: "";
+                            position: absolute;
+                            top: 3px;
+                            // margin-left: -16px;
+                            right: -9px;
+                            width: 0;
+                            height: 0;
+                            border-style: solid;
+                            border-width: 5px 0 5px 8.7px;
+                            border-color: transparent transparent transparent #ffecc2;
+                        }
+
+                        .message-text{
+                            img{
+                                max-width: 80%;
+                            }
+
+                            .pick-item {
+                                width: 100px;
+                                text-align: center;
+                                background: rgba(0, 0, 0, 0.3);
+                                height: 100px;
+                                border: 2px solid #ff9900;
+                                border-radius: 8px;
+                                display: flex;
+                                flex-direction: column;
+                                justify-content: center;
+                                .student-no {
+                                    font-size: 40px;
+                                    color: black;
+                                    font-weight: 600;
+                                }
+                                .student-name {
+                                    color: #007cff;
+                                    font-weight: 800;
+                                    font-size: 16px;
+                                }
+                            }
+                        }
+
+                    }
+
+                    .message-time {
+                        white-space: nowrap;
+                        color: gray;
+                        font-size: 10px;
+                        font-weight: 400;
+                        // position: absolute;
+                        // bottom: -20px;
+                        // margin-left: -8px;
+                        position: absolute;
+                        // bottom: 0;
+                        right: 55px;
+                    }
+                }
+
+                .teacher-client-icon{
+                    font-size: 30px;
+                    padding: 5px;
+                    color: #ffffff;
+                    border-radius: 50%;
+                    background: #19be6b;
+                    margin-right: 15px;
+                    float: left;
+                }
+
+                .student-client-icon{
+                    font-size: 30px;
+                    padding: 5px;
+                    color: #ffffff;
+                    border-radius: 50%;
+                    background: #efbb49;
+                    float: right;
+                    margin-left: 15px;
+                }
+            }
+        }
+    }
+
+    .dec {
+        padding: 0px;
+        height: 800px;
+        overflow: hidden;
+        position: relative;
+    }
+    
+    .title-rect-name > span{
+        background-color: #24B880;
+        font-size: 12px;
+        padding: 3px;
+        border: 1px solid #ccc;
+        border-radius: 4px;
+        color: #fff;
+        cursor: pointer;
+    }
+}
+.image-viewer {
+    background-color: rgba(0, 0, 0, 0.8);
+    z-index: 9999;
+    width: 100%;
+    height: 100%;
+    position: fixed;
+    top: 0;
+    left: 0;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    text-align: center;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    img {
+        max-width: 50%;
+        margin: 5% auto;
+        border: none;
+    }
+    
+    .close-icon {
+        position: absolute;
+        right: 15px;
+        top: 10px;
+        font-size: 20px;
+        color: white;
+        cursor: pointer;
+    }
+}

+ 509 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ClassRecord.vue

@@ -0,0 +1,509 @@
+<template>
+    <div class="record-info">
+        <div class="testTitle">
+            <span class="logoutIcon" @click="quitRec">
+                <svg-icon icon-class="logout" />
+            </span>
+            <span class="testTitleText">{{$t("studentWeb.courseContent.classRecord")}}</span>
+        </div>
+        <vuescroll ref="pagewrap">
+            <div style="padding: 1% 3% 3%; margin-top: 44px;">
+                <h2 class="event-title">{{ recordInfo.name }}</h2>
+                <p style="text-align: right;">
+                    <span>
+                        <Icon type="md-timer" class="base-info-icon" />{{ $t('studentWeb.baseInfo.duration') }}:
+                        <span class="base-info-text">{{ recordInfo.time }}</span>
+                    </span>
+                    <span style="margin-left: 20px;">
+                        <svg-icon icon-class="time" class="base-info-icon" />{{$t('studentWeb.baseInfo.classTime')}}:
+                        <span class="base-info-text">{{ recordInfo.startTime }}</span>
+                    </span>
+                </p>
+                <div class="title-rect-group">
+                    <h2 class="title-rect-name">
+                        {{ $t("studentWeb.hiteachNote.material") }}
+                    </h2>
+                </div>
+                <div>
+                    <div class="class-content" id="videoNow">
+                        <div class="courseware-wrap">
+                            <!-- <DrawHTEX :mapJson="mapJson"></DrawHTEX> -->
+                            <img :src="curImg" alt="" class="course-cur-img">
+                            <div class="page-wrap">
+                                <Page :total="pageList.length" :current="curPage" :page-size="1" size="small" @on-change="getCurHTEX" />
+                                <!-- <Icon v-if="pageList.length" type="md-qr-scanner" class="full-screen-icon" @click="viewHtex" /> -->
+                            </div>
+                            <!-- <span class="cur-page-tag">{{ curPage }}</span> -->
+                        </div>
+                        <video id="recordVideo" class="video-player-box video-js vjs-default-skin"></video>
+                    </div>
+                    <!-- 课堂互动记录 -->
+                    <div class="title-rect-group">
+                        <h2 class="title-rect-name">
+                            {{ $t("studentWeb.hiteachNote.classInteractionRecord") }}
+                            <!-- <span class="feedbackNum">05</span> -->
+                            <span @click="loadNote">
+                                {{ $t('studentWeb.courseContent.notes') }}
+                                <Icon type="md-download" />
+                            </span>
+                        </h2>
+                    </div>
+                    <div class="dec">
+                        <div class="message-area">
+                            <div class="filter-type">
+                                <span @click="showFile('all')">
+                                    <Icon :class="{'select-filter': currentfilterType === '' }" custom="iconfont icon-all" :title="$t('studentWeb.courseContent.type.all')" size="30" />
+                                </span>
+                                <span @click="showFile('msg')">
+                                    <Icon :class="{'select-filter': currentfilterType === 'msg' }" custom="iconfont icon-megaphone" :title="$t('studentWeb.courseContent.type.msg')" size="30" />
+                                </span>
+                                <span @click="showFile('doc')">
+                                    <Icon :class="{'select-filter': currentfilterType === 'doc' }" custom="iconfont icon-file1" :title="$t('studentWeb.courseContent.type.file')" size="30" />
+                                </span>
+                                <span @click="showFile('FastPgPush')">
+                                    <Icon :class="{'select-filter': currentfilterType === 'FastPgPush' }" custom="iconfont icon-img" :title="$t('studentWeb.courseContent.type.img')" size="30" />
+                                </span>
+                                <span @click="showFile('link')">
+                                    <Icon :class="{'select-filter': currentfilterType === 'link' }" type="ios-link" :title="$t('studentWeb.courseContent.type.link')" />
+                                </span>
+                                <span @click="showFile('ShowAnsLoad')">
+                                    <Icon :class="{'select-filter': currentfilterType === 'ShowAnsLoad' || currentfilterType === 'QaRight' || currentfilterType === 'QaWrong'}" custom="iconfont icon-zimuQ" :title="$t('studentWeb.courseContent.type.answer')" size="30" />
+                                    <!-- <span :class="{'select-filter': currentfilterType === 'ShowAnsLoad' || currentfilterType === 'QaRight' || currentfilterType === 'QaWrong' }" style="font-size: 30px; line-height: 30px" title="即问即答">
+                                        Q
+                                    </span> -->
+                                </span>
+                                <span @click="showFile('QaRight')">
+                                    <Icon :class="{'select-filter': currentfilterType === 'ShowAnsLoad' || currentfilterType === 'QaRight' }" custom="iconfont icon-duihao" :title="$t('studentWeb.courseContent.type.right')" size="30" />
+                                </span>
+                                <span @click="showFile('QaWrong')">
+                                    <Icon :class="{'select-filter': currentfilterType === 'ShowAnsLoad' || currentfilterType === 'QaWrong' }" custom="iconfont icon-cuowu" :title="$t('studentWeb.courseContent.type.wrong')" size="30" />
+                                </span>
+                            </div>
+                            <vuescroll ref="datawrap">
+                                <div style="margin-bottom: 50px;">
+                                    <div v-for="(items, index) in pageList" :key="index" class="message-box" :id="'page' + (items.page)">
+                                        <div class="message-page">
+                                            <!-- <div class="messagetoPPT-tag" @click="toVideo(index+1, $event)">
+                                                课件第{{ items.page }}页
+                                            </div> -->
+                                            <div @click="toVideo(index + 1, $event)">
+                                                <img :src="items.img">
+                                                <p style="text-align: center;">
+                                                    {{ items.page }}
+                                                    <Icon type="ios-search" @click="openViewer(items.img)" />
+                                                </p>
+                                            </div>
+                                            
+                                            <!-- <div v-for="(rtItem, rtIndex) in items.pageData" :key="rtIndex + '' + index" class="record-data-item">
+                                                <span class="event-tag">{{ rtItem.Event }}</span>
+                                            </div> -->
+                                        </div>
+                                        <div class="message-record">
+                                            <template v-if="items.pageRecorde.length">
+                                                <div v-for="(recorde, rI) in items.pageRecorde" :key="rI">
+                                                    <!-- 展示答案 -->
+                                                    <div v-if="recorde.Event === 'ShowAnsLoad' && (currentfilterType === '' || currentfilterType === 'ShowAnsLoad' || currentfilterType === 'PopQuesLoad')" class="message-item">
+                                                        <Icon type="ios-person" class="teacher-client-icon"/>
+                                                        <!-- <p class="user-name">王大锤</p> -->
+                                                        <div class="message-content">
+                                                            <div class="message-text">
+                                                                这里有一个答案要展示
+                                                            </div>
+                                                        </div>
+                                                        <p class="message-time">{{ getTime(recorde.Time) }}</p>
+                                                    </div>
+                                                    <!-- 柱形图 -->
+                                                    <div v-if="recorde.Event === 'TakePic' && (currentfilterType === '' || currentfilterType === 'irs')" class="message-item">
+                                                        <Icon type="ios-person" class="teacher-client-icon"/>
+                                                        <!-- <p class="user-name">王大锤</p> -->
+                                                        <div class="message-content">
+                                                            <div class="message-text">
+                                                                <OptionCount />
+                                                                <CorrectRate />
+                                                            </div>
+                                                        </div>
+                                                        <p class="message-time">{{ getTime(recorde.Time) }}</p>
+                                                    </div>
+                                                    <!-- 推送 -->
+                                                    <div v-if="recorde.Event === 'FastPgPush' && (currentfilterType === '' || currentfilterType === 'FastPgPush')" class="message-item">
+                                                        <Icon type="ios-person" class="teacher-client-icon"/>
+                                                        <!-- <p class="user-name">王大锤</p> -->
+                                                        <div class="message-content">
+                                                            <div class="message-text">
+                                                                <p>老师推送了一张图:{{ recorde.PushPgNo }}</p>
+                                                                <img @click="openViewer('https://teammodelostest.blob.core.chinacloudapi.cn/teammodelcontest/common/20200213/%E9%86%8D%E6%91%A9%E8%B1%86_%E8%B1%86%E5%AD%90%E5%BE%BD%E7%AB%A0_20200213154333.png')" src="https://teammodelostest.blob.core.chinacloudapi.cn/teammodelcontest/common/20200213/%E9%86%8D%E6%91%A9%E8%B1%86_%E8%B1%86%E5%AD%90%E5%BE%BD%E7%AB%A0_20200213154333.png" />
+                                                            </div>
+                                                        </div>
+                                                        <p class="message-time">{{ getTime(recorde.Time) }}</p>
+                                                    </div>
+                                                    <!-- 挑人 -->
+                                                    <div v-if="recorde.Event === 'PickupResult' && (currentfilterType === '' || currentfilterType === 'PickupResult')" class="message-item">
+                                                        <Icon type="ios-person" class="teacher-client-icon"/>
+                                                        <!-- <p class="user-name">王大锤</p> -->
+                                                        <div class="message-content">
+                                                            <div class="message-text">
+                                                                <div class="pick-item">
+                                                                    <span class="student-no">{{ recorde.PickupMemberId }}</span>
+                                                                    <span class="student-name">秋香</span>
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                        <p class="message-time">{{ getTime(recorde.Time) }}</p>
+                                                    </div>
+                                                    <!-- 即问即答 -->
+                                                    <div v-if="recorde.Event === 'PopQuesLoad' && (currentfilterType === '' || currentfilterType === 'PopQuesLoad' || currentfilterType === 'ShowAnsLoad')" class="teacher-item">
+                                                        <Icon type="ios-people" class="student-client-icon" />
+                                                        <!-- <p class="user-name">王大锤</p> -->
+                                                        <div class="message-content">
+                                                            <div class="message-text">
+                                                                即问即答
+                                                            </div>
+                                                        </div>
+                                                        <p class="message-time">{{ getTime(recorde.Time) }}</p>
+                                                    </div>
+                                                </div>
+                                            </template>
+                                        </div>
+                                    </div>
+                                </div>
+                            </vuescroll>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </vuescroll>
+        <div v-if="openImageViewer" class="image-viewer">
+            <Icon type="md-close" class="close-icon" @click="closeViewer()" />
+            <img :src="nowImgSrc" />
+        </div>
+    </div>
+</template>
+
+<script>
+import OptionCount from './OptionCount.vue';
+import CorrectRate from './CorrectRate.vue';
+
+import videojs from "video.js";
+import 'video.js/dist/video-js.css';
+
+export default {
+    name: "ClassRecord",
+    components: {
+        OptionCount,
+        CorrectRate,
+    },
+    data () {
+        return {
+            player: undefined,
+            pageList: [], //课件
+            markers: [], //打点
+            sokratesRecords: {}, //原始数据
+            curPage: 1, //当前课件页码
+            currentfilterType: "",
+            openImageViewer: false,
+            nowImgSrc: "",
+            openHtexViewer: false,
+            playerOptions: {
+                height: "450px",
+                controlBar: {
+                    durationDisplay: true, // 总时间
+                    currentTimeDisplay: true,
+                },
+                html5: {
+                    nativeControlsForTouch: true,
+                },
+                inactivityTimeout: 1,
+                nativeVideoTracks: false,
+                playbackRates: [0.5, 1.0, 1.25, 2.0], //播放速度
+                autoplay: false, //如果true,浏览器准备好时开始回放。
+                controls: true, //控制条
+                preload: 'auto', //视频预加载
+                muted: false, //默认情况下将会消除任何音频。
+                loop: false, //导致视频一结束就重新开始。
+                language: 'zh-CN',
+                //aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
+                //fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
+                sources: [{
+                    src: ""
+                }],
+                notSupportedMessage: '此视频暂无法播放,请稍后再试' //允许覆盖Video.js无法播放媒体源时显示的默认信息。
+            },
+            recordInfo: {},
+        }
+    },
+    created () {
+        console.log(this.$route.query.id);
+        this.recordInfo = 
+        {
+            "id": "256246355239899136",
+            "tmdid": "教师醍摩豆id",
+            "scope": "private/school",
+            "school": "学校id",
+            "name": "语文第一节",
+            "poster": "视频封面地址",
+            "startTime": 1607435663434, //开始时间(时间戳),
+            "duration": 5612, //"上课时长",
+            "tScore": 95, //"t分,科技应用",
+            "pScore": 95, //"p分,教法应用",
+            "courseId": "关联的课程id,同时可以拿到,学段id,科目id",
+            "groupIds": ["选用了IES5固定名单的id,通过名单id也可以拿到学段,年级"],
+            "periodId": "学段id",
+            "subject": "科目id",
+            "grade": ["1,年级"],
+            "like": 10, //"点赞数",
+            "share": 10, //"分享转发数",
+            "mCount": 50, //"参加本次课堂学生人数"
+            "discuss": 100, //"议课次数,大于1则是优课",
+        }
+        this.recordInfo.startTime = this.dateFormat(this.recordInfo.startTime)
+        let sec = this.recordInfo.duration % 60
+        let min = parseInt(this.recordInfo.duration / 60)
+        let mins = min >= 60 ? min % 60 : min
+        let hour = parseInt(min / 60)
+        this.recordInfo.time = `${hour < 10 ? ('0' + hour) : hour}:${mins < 10 ? ('0' + mins) : mins}:${sec < 10 ? ('0' + sec) : sec}`
+    },
+    mounted () {
+        // this.getVideo()
+        this.getPageList()
+    },
+    methods: {
+        getVideo() {
+            this.player = videojs(document.getElementById("recordVideo"), this.playerOptions)
+            var that = this
+            //时间切片
+            this.player.markers({
+                markerStyle: {
+                    width: "16px",
+                    height: "16px",
+                    top: "-22px",
+                    "display": "inline-block",
+                    "border-radius": "50%",
+                    "font-size": "12px",
+                    "line-height": "16px",
+                    "background-color": "orange"
+                },
+                breakOverlay: {
+                    display: true,
+                    displayTime: 4,
+                    style: {
+                        "z-index": "6",
+                        width: "100%",
+                        height: "10%",
+                        "background-color": "rgba(200,250,10,0.6)",
+                        color: "white",
+                        "font-size": "16px",
+                    },
+                    text: function (marker) {
+                        return that.$t('system.compt.cusWare') + marker.text;
+                    },
+                },
+                markerTip: {
+                    display: false,
+                    text: function (marker) {
+                        return marker.text;
+                    },
+                },
+                markers: that.markers,
+
+                //标记点击事件
+                onMarkerClick: function (marker) {
+                    
+                },
+                //视频播放到标记点触发的时间
+                onMarkerReached: function (marker) {
+                    let mkDoms = document.getElementsByClassName('vjs-marker ')
+                    for (let index in mkDoms) {
+                        if (mkDoms[index].dataset) {
+                            if (parseInt(mkDoms[index].dataset.markerTime) <= marker.time) {
+                                mkDoms[index].style.backgroundColor = '#1CC0F3'
+                                mkDoms[index].classList.add('vjs-marker-active')
+                            } else {
+                                mkDoms[index].style.backgroundColor = 'orange'
+                                mkDoms[index].classList.remove('vjs-marker-active')
+                                
+                            }
+                            mkDoms[index].innerHTML = this.markers[index].page
+                        }
+                    }
+                    that.getCurPage(marker.page)
+                },
+            });
+        },
+        // 根据SokratesRecords.json处理page数据
+        async getPageList() {
+            this.pageList = []
+            this.markers = []
+            let sas = await this.$tools.getBlobSas("1528783259")
+            this.recordInfo.sokrateImg = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Sokrates/SokratesResults/event.png?${sas.sas}`
+            this.recordInfo.eNote = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Note.pdf?${sas.sas}`
+            // 如果只会存在一个视频,文件名是否可以固定?
+            this.playerOptions.sources[0].src = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Record/Record_Core_2021-12-08_103133.mp4?${sas.sas}`
+            this.getVideo()
+            let url = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Sokrates/SokratesRecords.json?${sas.sas}`
+            this.$tools.getFile(url).then(
+                res => {
+                    this.sokratesRecords = JSON.parse(res)
+                    let r = this.sokratesRecords.find(item => item.Event === 'PgidList')
+                    let pgids = r ? r.PgIdList : []
+                    pgids.forEach((item, index) => {
+                        let page = {}
+                        page.id = item
+                        page.img = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Memo/${item}.jpg?${sas.sas}`
+                        page.page = index + 1
+                        //当前页面对应的sokrates
+                        page.pageData = this.sokratesRecords.filter(record => record.Pgid === item && record.Event != 'PgJump' && record.Event != 'PgAdd' && record.Event != 'DiscussStart')
+                        // 先筛选部分以作展示
+                        page.pageRecorde = this.sokratesRecords.filter(record => record.Pgid === item && (record.Event === 'PickupResult' || record.Event === 'TakePic' || record.Event === 'ShowAnsLoad' || record.Event === 'StatChartLoad' || record.Event === 'FastPgPush' || record.Event === 'PickupResult' || record.Event === 'PopQuesLoad'))
+                        this.pageList.push(page)
+                    })
+                    let pageEvent = this.sokratesRecords.filter(item => item.Event === 'PgJump' || item.Event === 'PgAdd' || item.Event === 'DiscussStart')
+                    this.markers = pageEvent.map((item, index) => {
+                        return {
+                            time: item.Time,
+                            text: `第${index + 1}页`,
+                            page: index + 1
+                        }
+                    })
+                },
+                err => {
+                    this.$Message.error('获取数据失败')
+                }
+            )
+        },
+        // 点击互动记录页面tag
+        toVideo(page, e) {
+            this.curPage = page
+            //页面滚动
+            let dataLoacation = this.$refs["datawrap"].getPosition()
+            let pageLocaltion = this.$refs["pagewrap"].getPosition()
+            let y = e.pageY - 770 + pageLocaltion.scrollTop + dataLoacation.scrollTop
+            this.$nextTick(() => {
+                this.$refs["pagewrap"].scrollTo(
+                    {
+                        x: 0,
+                        y: 0
+                    }
+                )
+                this.$refs["datawrap"].scrollTo(
+                    {
+                        x: 0,
+                        y: y
+                    }
+                )
+            })
+            //视频时间定位
+            let pageInfo = this.markers.find(item => {
+                return item.page === page
+            })
+            if(pageInfo) {
+                this.player.currentTime(pageInfo.time)
+            }
+        },
+        // 点击课件page
+        getCurHTEX(page) {
+            this.curPage = page
+            // this.mapJson = require('./data/' + page + '.json')
+            //视频时间定位
+            let pageInfo = this.markers.find(item => {
+                return item.page === page
+            })
+            if(pageInfo) {
+                this.player.currentTime(pageInfo.time)
+                if (!this.openHtexViewer) {
+                    this.player.play()
+                } else {
+                    this.player.pause()
+                }
+                //互动记录滚动
+                this.$refs["datawrap"].scrollIntoView('#page' + page, 500)
+            } else {
+            }
+        },
+        // 点击视频切片
+        getCurPage(page) {
+            this.curPage = page
+            // this.mapJson = require('./data/' + page + '.json')
+            this.$refs["datawrap"].scrollIntoView('#page' + page, 500)
+            this.player.play()
+        },
+        //下载电子笔记
+        async loadNote() {
+            if(this.recordInfo.eNote) {
+                // 已经有授权,在查看文件时不需要再次获取授权
+                let blobData = this.recordInfo.eNote
+                const downloadRes = async () => {
+                    let response = await fetch(blobData); // 内容转变成blob地址
+                    let blob = await response.blob();  // 创建隐藏的可下载链接
+                    let objectUrl = window.URL.createObjectURL(blob);
+                    let a = document.createElement('a');
+                    a.href = objectUrl;
+                    a.download = "电子笔记.pdf";
+                    a.click()
+                    a.remove();
+                }
+                downloadRes();
+            } else {
+                this.$Message.warning("暂无电子笔记")
+            }
+        },
+        // 筛选
+        showFile(type) {
+            this.currentfilterType = (type === "all" ? "" : type)
+        },
+        // 打开图片
+        openViewer(item) {
+            this.openImageViewer = true;
+            this.nowImgSrc = item;
+        },
+        // 取消
+        closeViewer() {
+            this.openImageViewer = false
+        },
+        getTime(time) {
+            let sec = parseInt(time % 60)
+            let min = parseInt(time / 60)
+            let mins = min >= 60 ? min % 60 : min
+            let hour = parseInt(min / 60)
+            return `${hour < 10 ? ('0' + hour) : hour}:${mins < 10 ? ('0' + mins) : mins}:${sec < 10 ? ('0' + sec) : sec}`
+        },
+        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;
+        },
+        quitRec() {
+            this.$router.go(-1)
+        },
+    },
+    computed: {
+        curImg() {
+            if (this.pageList[this.curPage]) {
+                return this.pageList[this.curPage - 1].img
+            } else {
+                return ""
+            }
+        }
+    },
+}
+</script>
+
+<style lang="less" scoped>
+@import "./ClassRecord.less";
+</style>
+<style lang="less">
+.class-content {
+
+    .video-js .vjs-big-play-button{
+        top: 50%;
+        left: 50%;
+        margin-left: -20px;
+        margin-top: -20px;
+        display: none;
+    }
+}
+</style>

+ 67 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/CorrectRate.vue

@@ -0,0 +1,67 @@
+<template>
+    <div :id="'options-count'+id" style="width: 240px;height: 180px;display: inline-block;margin-left:30px;"></div>
+</template>
+<script>
+    export default {
+        data() {
+            return {
+                id:'',
+                optionBar: undefined,
+                option: {
+                    title: {
+                        text: '正确率统计',
+                        // textStyle: {
+                        //     color: 'white'
+                        // },
+                        right: '70',
+                        top:'10'
+                    },
+                    tooltip: {
+                        trigger: 'item',
+                        formatter: '{b} : {c} ({d}%)'
+                    },
+                    series: [
+                        {
+                            type: 'pie',
+                            radius: '65%',
+                            center: ['50%', '60%'],
+                            selectedMode: 'single',
+                            label: {
+                                show: false
+                            },
+                            data: [
+                                {
+                                    value: 535,
+                                    itemStyle: {
+                                        color: '#1cc0f3'
+                                    },
+                                    name:'正确'
+                                },
+                                {
+                                    value: 510,
+                                    name: '错误',
+                                    itemStyle: {
+                                        color: '#ed4014'
+                                    },
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        created() {
+            this.id = this.$jsFn.getBtwRandom(0,100000000)
+        },
+        mounted() {
+            this.typeCountPie = this.$echarts.init(document.getElementById('options-count' + this.id))
+            this.typeCountPie.setOption(this.option)
+        }
+    }
+</script>
+<style scoped lang="less">
+    #options-count {
+    }
+</style>
+<style>
+</style>

+ 106 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/OptionCount.vue

@@ -0,0 +1,106 @@
+<template>
+    <div :id="'options-count'+id" style="width: 240px;height: 180px;display: inline-block;"></div>
+</template>
+<script>
+    export default {
+        data() {
+            return {
+                id:'',
+                optionBar: undefined,
+                option: {
+                    title: {
+                        text: '选项分布',
+                        // textStyle: {
+                        //     color: 'white'
+                        // },
+                        right: '85',
+                        top:'10'
+                    },
+                    tooltip: {
+                        trigger: 'axis',
+                        axisPointer: {
+                            type: 'shadow'
+                        }
+                    },
+                    //legend: {
+                    //    data: ['2011年', '2012年']
+                    //},
+                    grid: {
+                        left: '3%',
+                        right: '4%',
+                        bottom: '3%',
+                        containLabel: true
+                    },
+                    xAxis: {
+                        type: 'value',
+                        boundaryGap: [0, 0.01],
+                        // axisLabel: {
+                        //     color:'white'
+                        // },
+                        // axisLine: {
+                        //     lineStyle: {
+                        //         color:'white'
+                        //     }
+                        // }
+                    },
+                    yAxis: {
+                        type: 'category',
+                        data: ['A', 'B', 'C', 'D'],
+                        // axisLabel: {
+                        //     color:'white'
+                        // },
+                        // axisLine: {
+                        //     lineStyle: {
+                        //         color:'white'
+                        //     }
+                        // }
+                    },
+                    series: [
+                        {
+                            type: 'bar',
+                            data: [
+                                {
+                                    value: 20,
+                                    itemStyle: {
+                                        color: '#ed4014'
+                                    },
+                                },
+                                {
+                                    value: 21,
+                                    itemStyle: {
+                                        color: '#1cc0f3'
+                                    }
+                                },
+                                {
+                                    value: 5,
+                                    itemStyle: {
+                                        color: '#ed4014'
+                                    },
+                                },
+                                {
+                                    value: 1,
+                                    itemStyle: {
+                                        color: '#ed4014'
+                                    },
+                                }]
+                        }
+                    ]
+                }
+            }
+        },
+        created() {
+            this.id = this.$jsFn.getBtwRandom(0,100000000)
+        },
+        mounted() {
+            this.typeCountPie = this.$echarts.init(document.getElementById('options-count' + this.id))
+            this.typeCountPie.setOption(this.option)
+        }
+    }
+</script>
+<style scoped lang="less">
+    #options-count {
+        
+    }
+</style>
+<style>
+</style>

+ 1 - 1
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventView.vue

@@ -20,7 +20,7 @@
         },
         data() {
             return {
-                MyNo: "4", //接收NavBar 選定的那一頁icon標示
+                MyNo: "3", //接收NavBar 選定的那一頁icon標示
                 MyName: "",
                 mockdata: "",
                 eventPageType: ["preview", "exam", "homework", "vote", "survey"] //本頁出現的類型

+ 83 - 78
TEAMModelOS/ClientApp/src/components/student-web/HiteachView/HiteachNoteList.vue

@@ -1,92 +1,97 @@
 <template>
-  <div class="hiteachNote-list">
-    <div class="list">
-      
-      <ul class="list-block">
-        <li
-          class="list-item"
-          v-show="item.eventType == 'Hiteach電子筆記'"
-          v-for="(item, index) in mockdata"
-          @click="sentSelectedEventTitle(item)"
-          :class="{ 'list-item-selected': selectedCondition(item) }"
-          :key="index"
-        >
-          <ul>
-            <li class="list-item-icon">
-              <svg-icon icon-class="hiteach" />
-            </li>
-            <li class="list-item-info list-item-infonofeedback">
-              <p class="list-item-title" :class="{'list-item-titleEn':getCurrentLang()=='en'}">
-                <span class="list-item-typeMark">
-                  T{{item.eventID.substr(0, 5)}}
-                </span>
-                
-                <span>{{ item.eventName }}</span>
-              </p>
-              <p class="list-item-time">
-                {{ item.endTime }}
-              </p>
-            </li>
-          </ul>
-        </li>
-      </ul>
+    <div class="hiteachNote-list">
+        <div class="list">
+            <ul class="list-block">
+                <li
+                    class="list-item"
+                    v-show="item.eventType == 'Hiteach電子筆記'"
+                    v-for="(item, index) in mockdata"
+                    @click="sentSelectedEventTitle(item)"
+                    :class="{ 'list-item-selected': selectedCondition(item) }"
+                    :key="index"
+                >
+                    <ul>
+                        <li class="list-item-icon">
+                            <svg-icon icon-class="hiteach" />
+                        </li>
+                        <li class="list-item-info list-item-infonofeedback">
+                            <p
+                                class="list-item-title"
+                                :class="{
+                                    'list-item-titleEn':
+                                        getCurrentLang() == 'en',
+                                }"
+                            >
+                                <span class="list-item-typeMark">
+                                    T{{ item.eventID.substr(0, 5) }}
+                                </span>
+
+                                <span>{{ item.eventName }}</span>
+                            </p>
+                            <p class="list-item-time">
+                                {{ item.endTime }}
+                            </p>
+                        </li>
+                    </ul>
+                </li>
+            </ul>
+        </div>
     </div>
-  </div>
 </template>
 
 <script>
-import mockdata from "@/api/newData";
-import mockdataEn from "@/api/newDataEn";
+import mockdata from "@/api/newData"
+import mockdataEn from "@/api/newDataEn"
 export default {
-  name: "HiteachNoteList",
-  data() {
-    return {
-      mockdata: '',
-    };
-  },
-   created() {
-     this.createMockdata();
-    this.sentFirstItemActive();
-  },
-  methods: {
-     getCurrentLang() {
-      return localStorage.getItem("lang");
-    },
-    createMockdata: function () {
-      if (localStorage.getItem('lang') == "en") {
-        this.mockdata = mockdataEn.eventList;
-      } else this.mockdata = mockdata.eventList;
-    },
-       sentFirstItemActive() {
-      let tempArr = [];
-      for (let i = 0; i < this.mockdata.length; i++) {
-        if (this.mockdata[i].eventType == "Hiteach電子筆記") {
-          tempArr.push(this.mockdata[i]);
+    name: "HiteachNoteList",
+    data() {
+        return {
+            mockdata: "",
         }
-      }
-      this.sentSelectedEventTitle(tempArr[0]);
     },
-
-    sentSelectedEventTitle: function(item) {
-      //this.$router.push("/studentWeb/EventView/" + item.eventID);
-      //改變ItemName的狀態 vuex mutations
-      this.$store.commit("ChangeItemName", item);
-      //螢幕寬度<680px時,直接關掉sidebar
-      if(window.innerWidth<=680){
-        this.$store.commit("ToggleSidebar", false);
-      }
+    created() {
+        this.createMockdata()
+        this.sentFirstItemActive()
     },
+    methods: {
+        getCurrentLang() {
+            return localStorage.getItem("lang")
+        },
+        createMockdata: function () {
+            if (localStorage.getItem("lang") == "en") {
+                this.mockdata = mockdataEn.eventList
+            } else this.mockdata = mockdata.eventList
+        },
+        sentFirstItemActive() {
+            let tempArr = []
+            for (let i = 0; i < this.mockdata.length; i++) {
+                if (this.mockdata[i].eventType == "Hiteach電子筆記") {
+                    tempArr.push(this.mockdata[i])
+                }
+            }
+            this.sentSelectedEventTitle(tempArr[0])
+        },
+
+        sentSelectedEventTitle: function (item) {
+            //this.$router.push("/studentWeb/EventView/" + item.eventID);
+            //改變ItemName的狀態 vuex mutations
+            this.$store.commit("ChangeItemName", item)
+            //螢幕寬度<680px時,直接關掉sidebar
+            if (window.innerWidth <= 680) {
+                this.$store.commit("ToggleSidebar", false)
+            }
+        },
 
-    selectedCondition(item) {
-      if (
-        this.$store.getters.getIsSelectedNow == true &&
-        this.$store.getters.getItemTitle.eventName == item.eventName
-      ) {
-        return true;
-      } else return false;
+        selectedCondition(item) {
+            if (
+                this.$store.getters.getIsSelectedNow == true &&
+                this.$store.getters.getItemTitle.eventName == item.eventName
+            ) {
+                return true
+            } else return false
+        },
     },
-  },
-};
+}
 </script>
 
 <style scoped>

+ 4 - 4
TEAMModelOS/ClientApp/src/components/student-web/HiteachView/HiteachView.vue

@@ -769,7 +769,7 @@ export default {
                     "tmdid": "教师醍摩豆id",
                     "scope": "private/school",
                     "school": "学校id",
-                    "name": "小葵花小课堂",
+                    "name": "语文第一节",
                     "poster": "视频封面地址",
                     "startTime": 1607435663434, //开始时间(时间戳),
                     "duration": 5612, //"上课时长",
@@ -784,14 +784,14 @@ export default {
                     "share": 10, //"分享转发数",
                     "mCount": 50, //"参加本次课堂学生人数"
                     "discuss": 100, //"议课次数,大于1则是优课",
-                    teacher: "王大锤",
+                    teacher: "王老师",
                 },
                 {
                     "id": "256246355239899136",
                     "tmdid": "教师醍摩豆id",
                     "scope": "private/school",
                     "school": "学校id",
-                    "name": "《稻香》鉴赏",
+                    "name": "英语听力",
                     "poster": "视频封面地址",
                     "startTime": 1608433140434, //开始时间(时间戳),
                     "duration": 974, //"上课时长",
@@ -806,7 +806,7 @@ export default {
                     "share": 10, //"分享转发数",
                     "mCount": 50, //"参加本次课堂学生人数"
                     "discuss": 100, //"议课次数,大于1则是优课",
-                    teacher: "朱美丽",
+                    teacher: "朱老师",
                 },
             ]
             this.classList.forEach(item => {

+ 252 - 37
TEAMModelOS/ClientApp/src/components/student-web/HomeView/CourseListView.vue

@@ -1,18 +1,10 @@
 <template>
     <!-- 现在用的页面 -->
     <div class="courselist-view">
-        <!-- <loading :active.sync="isLoad"
-                  :is-full-page="true"
-                  background-color="#000"
-                  :opacity="0.6">
-            <template slot="default">
-                <svg-icon icon-class="loader" class="loader-icon" />
-            </template>
-        </loading> -->
         <Loading v-show="isLoad" bgColor="rgba(0, 0, 0, 0.3)"></Loading>
         <!-- 列表 -->
         <div class="course-list"
-            :class="{ courselistEn: getCurrentLaguage == 'en-us' }"
+            :class="{ courselistEn: getCurrentLaguage == 'en-us', 'hide-sidebar': !getSidebarisOpen}"
         >
             <!--課程加入成功-->
             <div class="warmMessage"
@@ -85,10 +77,10 @@
                                     <!-- 周一 -->
                                     <template slot-scope="{ row }" slot="Mon">
                                         <div v-for="(item, index) in row.fixList" :key="index">
-                                            <div v-if="item.timeWeek == 'MON'"
+                                            <div v-if="item.timeWeek === 'MON'"
                                                 class="table-item"
                                                 @click="clickCell(row, index)"
-                                                :class="{'table-item-selected': unique == item.unique }"
+                                                :class="{'table-item-selected': unique === item.unique }"
                                             >
                                                 <p class="list-name">{{ item.name}}</p>
                                                 <p>({{ item.teaName}})</p>
@@ -181,7 +173,7 @@
                                     <li
                                         class="list-item"
                                         v-for="(person, num) in personList"
-                                        :class="{'list-item-selected': unique == person.unique}"
+                                        :class="{'list-item-selected': unique === person.unique}"
                                         @click="clickCell(person, num, 'list')"
                                         :key="`j+${num}`"
                                     >
@@ -206,21 +198,29 @@
         </div>
         <!-- 基本信息 -->
         <div class="course-content"
-            :class="{ courseContentEn: getCurrentLaguage == 'en-us' }"
+            :class="{ courseContentEn: getCurrentLaguage == 'en-us', 'course-content-span': !getSidebarisOpen }"
             v-if="showInfo"
         >
             <h2>{{ courseNow.name }}</h2>
+            <p class="info-icon">
+                <span>{{ courseNow.teaName }}</span>
+                <span>
+                    <Icon type="ios-paper-outline" :title="$t('studentWeb.courseContent.baseInfo')" size="20" @click="showBasicInfo = true" />
+                    <!-- <Icon type="ios-book-outline" title="课件" size="20" /> -->
+                    <Icon type="ios-contacts-outline" :title="$t('studentWeb.courseContent.nameList')" size="20" @click="showStuList = true" />
+                </span>
+            </p>
             <!-- <h3 class="course-subject">國中/二年級/{{courseNow.courseSubject}}</h3> -->
             <div @click="changeGroupView()"
                 :class="{ 'group-on': isChangeGroupView }"
-                v-if="name == 'tab3'"
+                v-if="name === 'tab3'"
             >
                 <svg-icon icon-class="group" class="group-btn" />
             </div>
 
             <Tabs v-model="name">
                 <!-- 基本资讯 -->
-                <TabPane :label="$t('studentWeb.courseContent.baseInfo')" name="tab1">
+                <!-- <TabPane :label="$t('studentWeb.courseContent.baseInfo')" name="tab1">
                     <Row :gutter="30">
                         <i-col :xs="24" :sm="24" :md="8" :lg="8">
                             <h4 class="basic-title">{{ $t("studentWeb.courseContent.classID") }}</h4>
@@ -235,7 +235,7 @@
                             <h4 class="basic-title">{{ $t("studentWeb.courseContent.teacher") }}</h4>
                             <h4 class="basic-data">{{ courseNow.teaName }}</h4>
 
-                            <h4 class="basic-title">{{ $t("studentWeb.courseContent.co-teacher") }}</h4>
+                            <h4 class="basic-title">{{ $t("studentWeb.courseContent.co-teacher") }}</h4> -->
                             <!-- <span class="basic-data"
                                 v-for="(teacher, index) in courseNow.assistantTeachers"
                                 :key="index"
@@ -244,7 +244,7 @@
                                 <span v-if="index < courseNow.assistantTeachers.length - 1">、</span>
                             </span> -->
 
-                            <h4 class="basic-title">{{ $t("studentWeb.courseContent.addedTime") }}</h4>
+                            <!-- <h4 class="basic-title">{{ $t("studentWeb.courseContent.addedTime") }}</h4>
                             <h4 class="basic-data">{{ courseNow.courseAddDate }}</h4>
                         </i-col>
                         <i-col :xs="24" :sm="24" :md="16" :lg="16">
@@ -252,9 +252,9 @@
                             <img src="mockqrcode.jpg" width="30%" style='margin-left: -12px'>
                         </i-col>
                     </Row>
-                </TabPane>
+                </TabPane> -->
                 <!-- 课程概述 -->
-                <TabPane :label="$t('studentWeb.courseContent.description')" name="tab2">
+                <!-- <TabPane :label="$t('studentWeb.courseContent.description')" name="tab2">
                     {{ courseNow.notice }}
                     <h3 style="margin-top: 30px; margin-bottom: 10px">{{ $t("studentWeb.courseContent.syllabus") }}</h3>
                     <Collapse simple>
@@ -299,9 +299,118 @@
                             </p>
                         </Panel>
                     </Collapse>
-                </TabPane>
+                </TabPane> -->
                 <!-- 同学名单 -->
-                <TabPane :label="$t('studentWeb.courseContent.classmates')" name="tab3">
+                <!-- <TabPane :label="$t('studentWeb.courseContent.classmates')" name="tab3"> -->
+                    <!-- <div v-show="!isChangeGroupView">
+                        <Table :columns="stuCol" :data="stuList">
+                            <template slot-scope="{ row }" slot="no">
+                                <p :class="{'my-name': row.id == userInfo.sub}">
+                                    {{row.no}}
+                                </p>
+                            </template>
+                            <template slot-scope="{ row }" slot="id">
+                                <p :class="{'my-name': row.id == userInfo.sub}">
+                                    {{row.id}}
+                                </p>
+                            </template>
+                            <template slot-scope="{ row }" slot="name">
+                                <p :class="{'my-name': row.id == userInfo.sub}">
+                                    {{row.name}}
+                                </p>
+                            </template>
+                            <template slot-scope="{ row }" slot="groupName">
+                                <p :class="{'my-name': row.id == userInfo.sub}">
+                                    {{row.groupName ? row.groupName : $t("studentWeb.courseContent.noGroup")}}
+                                </p>
+                            </template>
+                        </Table>
+                    </div> -->
+                    <!--小組模式-->
+                    <!-- <div v-show="isChangeGroupView">
+                        <Card class="group-student"
+                            v-for="(group, gIndex) in stuGroup"
+                            :key="gIndex">
+                            <h3 class="group-title" v-if="group.name">
+                                {{ group.name }}
+                            </h3>
+                            <h3 class="group-title" v-else>{{ $t("studentWeb.courseContent.noGroup") }}</h3>
+                            <table>
+                                <tr v-for="(item, index) in group.info"
+                                    :key="`a${index}`"
+                                >
+                                    <td class="student-no" :class="{ 'my-name': item.id == userInfo.sub }">
+                                        {{ item.no }}
+                                    </td>
+                                    <td class="student-name" :class="{ 'my-name': item.id == userInfo.sub }">
+                                        {{ item.name }}
+                                        <span v-if="item.id == userInfo.sub">({{ $t("studentWeb.courseContent.me") }})</span>
+                                    </td>
+                                </tr>
+                            </table>
+                        </Card>
+                    </div> -->
+                <!-- </TabPane> -->
+                <!-- 醍摩豆 -->
+                <!-- <TabPane :label="$t('studentWeb.courseContent.classmates1')" name="tab4" v-if="tmdList.length > 0 || userInfo.scope === 'tmduser'">
+                    <Table :columns="tmdCol" :data="tmdList">
+                        <template slot-scope="{ row }" slot="id">
+                            <p :class="{'my-name': row.id == userInfo.sub}">
+                                {{row.id}}
+                            </p>
+                        </template>
+                        <template slot-scope="{ row }" slot="name">
+                            <p :class="{'my-name': row.id == userInfo.sub}">
+                                {{row.name}}
+                            </p>
+                        </template>
+                    </Table>
+                </TabPane> -->
+                <!-- 课堂记录 -->
+                <TabPane :label="$t('studentWeb.courseContent.classRecord')" name="tab5">
+                        <div class="list-block-box">
+                            <vuescroll>
+                                <div v-for="(item, index) in recordList" :key="index" @click="toClassRecord(item, index)"
+                                    :class="['list-item', ]">
+                                    <ul>
+                                        <!-- <li class="list-item-icon">
+                                            <svg-icon icon-class="hiteach" />
+                                        </li> -->
+                                        <li class="list-item-info">
+                                            <p class="list-item-title">
+                                                <span>{{ item.name }}</span>
+                                                <!-- <span style="margin-left: 20px">老师:{{ item.teacher }}</span> -->
+                                            </p>
+                                            <p class="list-item-time">
+                                                <span>{{ item.startTime }}</span>
+                                                <span>{{ $t('studentWeb.baseInfo.duration') }}:{{ item.time }}</span>
+                                            </p>
+                                        </li>
+                                    </ul>
+                                </div>
+                            </vuescroll>
+                        </div>
+                </TabPane>
+            </Tabs>
+        </div>
+        <Modal v-model="showBasicInfo" :title="$t('studentWeb.courseContent.baseInfo')">
+            <p style="margin-bottom: 10px; font-weight: bold;">{{ $t("studentWeb.courseContent.classID") }}:{{ courseNow.no }}</p>
+
+            <p style="margin-bottom: 10px; font-weight: bold;">{{ $t("studentWeb.courseContent.classTime") }}:{{ courseNow.classTime }}</p>
+
+            <p style="margin-bottom: 10px; font-weight: bold;">{{ $t("studentWeb.courseContent.classroom") }}:{{ courseNow.roomName }}</p>
+
+            <p style="margin-bottom: 10px; font-weight: bold;">{{ $t("studentWeb.courseContent.teacher") }}:{{ courseNow.teaName }}</p>
+
+            <!-- <h4 class="basic-title">{{ $t("studentWeb.courseContent.co-teacher") }}</h4> -->
+            <p style="margin-bottom: 10px; font-weight: bold;">{{ $t("studentWeb.courseContent.addedTime") }}:{{ courseNow.courseAddDate }}</p>
+        </Modal>
+        <Modal v-model="showStuList" :title="$t('studentWeb.courseContent.nameList')">
+            <div @click="changeGroupView()" :class="{ 'group-on': isChangeGroupView }" v-if="list === 'stuList'">
+                <svg-icon icon-class="group" class="group-btn" />
+            </div>
+            <Tabs v-model="list">
+                <TabPane :label="$t('studentWeb.courseContent.classmates')" name="stuList">
                     <div v-show="!isChangeGroupView">
                         <Table :columns="stuCol" :data="stuList">
                             <template slot-scope="{ row }" slot="no">
@@ -351,8 +460,7 @@
                         </Card>
                     </div>
                 </TabPane>
-                <!-- 醍摩豆 -->
-                <TabPane :label="$t('studentWeb.courseContent.classmates1')" name="tab4" v-if="tmdList.length > 0 || userInfo.scope === 'tmduser'">
+                <TabPane :label="$t('studentWeb.courseContent.classmates1')" name="tmdList" v-if="tmdList.length > 0 || userInfo.scope === 'tmduser'">
                     <Table :columns="tmdCol" :data="tmdList">
                         <template slot-scope="{ row }" slot="id">
                             <p :class="{'my-name': row.id == userInfo.sub}">
@@ -367,24 +475,26 @@
                     </Table>
                 </TabPane>
             </Tabs>
-        </div>
+        </Modal>
     </div>
 </template>
 
 <script>
 /* import Loading from "vue-loading-overlay";
 import "vue-loading-overlay/dist/vue-loading.css"; */
-import Loading from '@/common/Loading.vue'
+import Loading from '@/common/Loading.vue';
+import ClassRecord from './../ClassRecord/ClassRecord';
 import { mapGetters, mapState } from 'vuex';
 
 export default {
     name: "CourseListView",
     components: {
-        Loading
+        Loading,
+        ClassRecord,
     },
     data(vm) {
         return {
-            MyNo: "",
+            MyNo: "2",
             MyName: this.$t('studentWeb.courseList-title'),
             isShow: false,
             period: [], //年级列表
@@ -440,7 +550,7 @@ export default {
             courseNow: {},
             showInfo: false,
             isChangeGroupView: false, //控制名单样式
-            name: "tab1",
+            name: "",
             week: [
                 {
                     name: "MON",
@@ -490,6 +600,11 @@ export default {
             stuGroup: [], //学生——小组名单
             stuList: [], //学生——table
             tmdList: [], //醍摩豆列表
+            recordList: [],
+            nowIndex: 0,
+            showBasicInfo: false,
+            showStuList: false,
+            list: "stuList",
         };
     },
 
@@ -642,6 +757,7 @@ export default {
             delete data.schedule
             data.school = subject.stuCourse.school
             data.scope = subject.stuCourse.scope
+            data.courseAddDate = this.dateFormat(subject.stuCourse.createTime)
             data.classId = schedule.classId
             // data.roomId = schedule.room
             // data.teacherId = schedule.teacherId
@@ -741,6 +857,7 @@ export default {
             type:传'list'就是临时课程,不传就是表定课程
         */
         clickCell(row, index, type) {
+            this.name = "tab5"
             let nowClassInfor = undefined
             if(type) {
                 nowClassInfor = this._.cloneDeep(row)
@@ -762,6 +879,7 @@ export default {
                 // this.findPersonInfo(this.courseNow)
                 this.findStuInfor(this.courseNow)
             }
+            this.getRecordList()
             // 螢幕寬度<767px時,直接關掉sidebar
             if (window.innerWidth <= 991) {
                 this.$store.commit("ToggleSidebar", false);
@@ -771,6 +889,79 @@ export default {
         changeView(type) {
             this.currentView = type
         },
+        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;
+        },
+        getRecordList() {
+            this.recordList = [
+                {
+                    "id": "256246355239899136",
+                    "tmdid": "教师醍摩豆id",
+                    "scope": "private/school",
+                    "school": "学校id",
+                    "name": "语文第一节",
+                    "poster": "视频封面地址",
+                    "startTime": 1607435663434, //开始时间(时间戳),
+                    "duration": 5612, //"上课时长",
+                    "tScore": 95, //"t分,科技应用",
+                    "pScore": 95, //"p分,教法应用",
+                    "courseId": "关联的课程id,同时可以拿到,学段id,科目id",
+                    "groupIds": ["选用了IES5固定名单的id,通过名单id也可以拿到学段,年级"],
+                    "periodId": "学段id",
+                    "subject": "科目id",
+                    "grade": ["1,年级"],
+                    "like": 10, //"点赞数",
+                    "share": 10, //"分享转发数",
+                    "mCount": 50, //"参加本次课堂学生人数"
+                    "discuss": 100, //"议课次数,大于1则是优课",
+                },
+                {
+                    "id": "256246355239899136",
+                    "tmdid": "教师醍摩豆id",
+                    "scope": "private/school",
+                    "school": "学校id",
+                    "name": "英语听力",
+                    "poster": "视频封面地址",
+                    "startTime": 1608433140434, //开始时间(时间戳),
+                    "duration": 974, //"上课时长",
+                    "tScore": 95, //"t分,科技应用",
+                    "pScore": 95, //"p分,教法应用",
+                    "courseId": "关联的课程id,同时可以拿到,学段id,科目id",
+                    "groupIds": ["选用了IES5固定名单的id,通过名单id也可以拿到学段,年级"],
+                    "periodId": "学段id",
+                    "subject": "科目id",
+                    "grade": ["1,年级"],
+                    "like": 10, //"点赞数",
+                    "share": 10, //"分享转发数",
+                    "mCount": 50, //"参加本次课堂学生人数"
+                    "discuss": 100, //"议课次数,大于1则是优课",
+                },
+            ]
+            this.recordList.forEach(item => {
+                item.startTime = this.dateFormat(item.startTime)
+                let sec = item.duration % 60
+                let min = parseInt(item.duration / 60)
+                let mins = min >= 60 ? min % 60 : min
+                let hour = parseInt(min / 60)
+                item.time = `${hour < 10 ? ('0' + hour) : hour}:${mins < 10 ? ('0' + mins) : mins}:${sec < 10 ? ('0' + sec) : sec}`
+            })
+        },
+        toClassRecord(item, index) {
+            this.nowIndex = index
+            this.$router.push({
+                path: "stuClassRec",
+                query: {
+                    id: item.id
+                }
+            })
+        },
     },
 
     created() {
@@ -794,9 +985,20 @@ export default {
         ...mapGetters([
             "getCurrentLaguage",
             "getMycID",
-            ""
+            "getSidebarisOpen",
         ])
-    }
+    },
+    watch: {
+        /* name: {
+            handler(n, o) {
+                if(n === "tab5") {
+                    this.$store.commit("ToggleSidebar", false);
+                } else {
+                    this.$store.commit("ToggleSidebar", true);
+                }
+            },
+        }, */
+    },
 };
 </script>
 
@@ -806,22 +1008,29 @@ export default {
 </style>
 
 <style scoped>
-.courselist-view{
-  overflow: hidden;
-}
 
 .hide-sidebar * {
   width: 0px !important;
   transition: ease-out 0.3s;
 }
 .course-content-span{
-  padding: 2% 15%;
-  width: 100% !important;
-  transition: 0.5s;
+    padding: 2%;
+    width: 100% !important;
+    transition: 0.5s;
 }
+
 </style>
 
 <style lang="less">
+.courselist-view{
+    overflow: hidden;
+
+    .ivu-modal-body > p{
+        
+    }
+}
+
+    
 /* iview样式修改 */
 .course-content {
     //分頁組件
@@ -900,7 +1109,7 @@ export default {
         }
         &:last-child {
             margin-top: -16px;
-            padding-top: 10px;
+            // padding-top: 10px;
             height: auto;
         }
     }
@@ -913,6 +1122,12 @@ export default {
         font-weight: bolder;
         color: #24b880;
     }
+    
+    .info-icon .ivu-icon{
+        font-weight: bold;
+        margin-right: 10px;
+        cursor: pointer;
+    }
 }
 
 .list-block{

+ 2 - 1
TEAMModelOS/ClientApp/src/locale/lang/en-US/learnActivity.js

@@ -48,7 +48,8 @@ export default {
         ftStatus: 'Status:',
         ftType: 'Type:',
         ftMode: 'Method:',
-        search: 'Search'
+        search: 'Search',
+        taskTips:'您可以發布閱卷任務,將評測作答數據分配給老師進行線上閱卷評分。 '
     },
 
     //CreateEv

+ 14 - 1
TEAMModelOS/ClientApp/src/locale/lang/en-US/studentWeb.js

@@ -257,7 +257,8 @@ export default {
         classTime: 'Class time',
         unFinished: 'Unfinished',
         Fineshed: 'Completed',
-        Closed: 'Ended'
+        Closed: 'Ended',
+        duration: "时长",
     },
     billboard: {
         description: 'Description',
@@ -595,6 +596,7 @@ export default {
         noCourse: "No course yet",
         baseInfo: 'Basic Information',
         description: 'Course Overview',
+        nameList: "班级名单",
         classmates: 'Classmates list',
         classmates1: 'TEAM Model List',
         classRecord: "课堂记录",
@@ -618,6 +620,17 @@ export default {
         noRoom: "Not yet set",
         courseCode: "Course QR Code",
         me: "me",
+        notes: "电子笔记",
+        type: {
+            all: "全部",
+            msg: "飞讯",
+            file: "文件",
+            img: "图片",
+            link: "超链接",
+            answer: "即问即答",
+            right: "答对",
+            wrong: "答错",
+        },
     },
     'calendar-title': 'Calendar-Second semester of 2020 school year',
     today: 'Today',

+ 25 - 19
TEAMModelOS/ClientApp/src/locale/lang/en-US/unit.js

@@ -1,21 +1,27 @@
 export default {
-  text1: 'class(es)',
-  text2: '%',
-  text3: 'set(s)',
-  text4: 'time(s)',
-  text5: 'min(s)',
-  text6: 'msg(s)',
-  text7: ' people',
-  text8: 'm',
-  text9: ' unit(s)',
-  text10: ' question(s)',
-  text11: 'point(s)',
-  text12: 'total',
-  text13: 'No. Of People',
-  gradeYear: '',
-  minute: 'min(s)',
-  hour: 'hour(s)',
-  second: 'second(s)',
-  day: 'day(s)',
-  week: 'week(s)'
+	text1: 'class(es)',
+	text2: '%',
+	text3: 'set(s)',
+	text4: 'time(s)',
+	text5: 'min(s)',
+	text6: 'msg(s)',
+	text7: ' people',
+	text8: 'm',
+	text9: ' unit(s)',
+	text10: ' question(s)',
+	text11: 'point(s)',
+	text12: 'total',
+	text13: 'No. Of People',
+	gradeYear: '',
+	minute: 'min(s)',
+	hour: 'hour(s)',
+	second: 'second(s)',
+	day: 'day(s)',
+	week: 'week(s)',
+	diffTip1: '月前',
+	diffTip2: '周前',
+	diffTip3: '天前',
+	diffTip4: '小時前',
+	diffTip5: '分鐘前',
+	diffTip6: '剛剛',
 }

+ 2 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/learnActivity.js

@@ -48,7 +48,8 @@ export default {
         ftStatus: '状态:',
         ftType: '类型:',
         ftMode: '模式:',
-        search: '搜索'
+        search: '搜索',
+        taskTips:'您可以发布阅卷任务,将评测作答数据分配给老师进行线上阅卷评分。'
     },
 
     //CreateEv

+ 14 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/studentWeb.js

@@ -257,7 +257,8 @@ export default {
         classTime: '上课时间',
         unFinished: '未完成',
         Fineshed: '已完成',
-        Closed: '已结束'
+        Closed: '已结束',
+        duration: "时长",
     },
     billboard: {
         description: '描述',
@@ -595,6 +596,7 @@ export default {
         noCourse: "暂无课程",
         baseInfo: '基本资讯',
         description: '课程概述',
+        nameList: "班级名单",
         classmates: '同学名单',
         classmates1: '醍摩豆名单',
         classRecord: "课堂记录",
@@ -618,6 +620,17 @@ export default {
         noRoom: "暂无教室",
         courseCode: "课程二维码",
         me: "我",
+        notes: "电子笔记",
+        type: {
+            all: "全部",
+            msg: "飞讯",
+            file: "文件",
+            img: "图片",
+            link: "超链接",
+            answer: "即问即答",
+            right: "答对",
+            wrong: "答错",
+        },
     },
     'calendar-title': '行事历 - 109学年度第二学期',
     today: '今天',

+ 7 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/unit.js

@@ -17,5 +17,11 @@ export default {
   hour:'小时',
   second:'秒',
   day:'天',
-  week:'周'
+  week:'周',
+  diffTip1:'月前',
+  diffTip2:'周前',
+  diffTip3:'天前',
+  diffTip4:'小时前',
+  diffTip5:'分钟前',
+  diffTip6:'刚刚',
 }

+ 2 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/learnActivity.js

@@ -48,7 +48,8 @@ export default {
         ftStatus: '狀態:',
         ftType: '類型:',
         ftMode: '模式:',
-        search: '搜尋'
+        search: '搜尋',
+        taskTips:'您可以發布閱卷任務,將評測作答數據分配給老師進行線上閱卷評分。 '
     },
 
     //建立評量學校/個人

+ 14 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/studentWeb.js

@@ -257,7 +257,8 @@ export default {
         classTime: '上課時間',
         unFinished: '未完成',
         Fineshed: '已完成',
-        Closed: '已結束'
+        Closed: '已結束',
+        duration: "時長",
     },
     billboard: {
         description: '描述',
@@ -595,6 +596,7 @@ export default {
         noCourse: "暫無課程",
         baseInfo: '基本資訊',
         description: '課程概述',
+        nameList: "班級名單",
         classmates: '同學名單',
         classmates1: '醍摩豆名單',
         classRecord: "課堂記錄",
@@ -618,6 +620,17 @@ export default {
         noRoom: "暫無教室",
         courseCode: "課程二維碼",
         me: "我",
+        notes: "電子筆記",
+        type: {
+            all: "全部",
+            msg: "飛訊",
+            file: "檔案",
+            img: "圖片",
+            link: "超連結",
+            answer: "即問即答",
+            right: "答對",
+            wrong: "答錯",
+        },
     },
     'calendar-title': '行事歷 - 109學年度第二學期',
     today: '今天',

+ 25 - 19
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/unit.js

@@ -1,21 +1,27 @@
 export default {
-  text1: '堂',
-  text2: '%',
-  text3: '套',
-  text4: '次',
-  text5: '分鐘',
-  text6: '則',
-  text7: '人',
-  text8: 'm',
-  text9: '台',
-  text10: '道',
-  text11: '分',
-  text12: '共',
-  text13:'人數',
-  gradeYear: '級',
-  minute:'分鐘',
-  hour:'小時',
-  second:'秒',
-  day:'天',
-  week:'週'
+	text1: '堂',
+	text2: '%',
+	text3: '套',
+	text4: '次',
+	text5: '分鐘',
+	text6: '則',
+	text7: '人',
+	text8: 'm',
+	text9: '台',
+	text10: '道',
+	text11: '分',
+	text12: '共',
+	text13: '人數',
+	gradeYear: '級',
+	minute: '分鐘',
+	hour: '小時',
+	second: '秒',
+	day: '天',
+	week: '週',
+	diffTip1: '月前',
+	diffTip2: '周前',
+	diffTip3: '天前',
+	diffTip4: '小時前',
+	diffTip5: '分鐘前',
+	diffTip6: '剛剛',
 }

+ 9 - 3
TEAMModelOS/ClientApp/src/router/routes.js

@@ -1113,12 +1113,12 @@ export const routes = [{
 		children: [
 			//教室序列号
 			{
-				name: 'authroom',
-				path: '/home/auth/classroom',
+				name: 'serial',
+				path: '/home/auth/serial',
 				meta: {
 					activeName: 'auth'
 				},
-				component: resolve => require(['@/view/auth/Classroom.vue'], resolve),
+				component: resolve => require(['@/view/auth/Serial.vue'], resolve),
 			},
 			//产品模块授权
 			{
@@ -1206,6 +1206,12 @@ export const routes = [{
 		name: "informView",
 		path: "informView",
 		component: resolve => require(['@/components/student-web/InformView/InformView'], resolve),
+	},
+	{
+		// 课堂记录
+		name: "stuClassRec",
+		path: "stuClassRec",
+		component: resolve => require(['@/components/student-web/ClassRecord/ClassRecord'], resolve),
 	}
 	]
 },

+ 0 - 3
TEAMModelOS/ClientApp/src/utils/evTools.js

@@ -173,7 +173,6 @@ export default {
 			jsonData.exercise.id = jsonData.id
 			jsonData.exercise.scope = 'private'
 			jsonData.exercise.pid = jsonData.pid
-			jsonData.exercise.knowledge = jsonData.knowledge
 			jsonData.exercise = await this.doAddHost(jsonData.exercise,null,tmdId)
 			r(jsonData.exercise)
 		})
@@ -242,7 +241,6 @@ export default {
 							jsonData.exercise.blob = list[i].blob
 							jsonData.exercise.code = list[i].code
 							jsonData.exercise.scope = list[i].scope
-							jsonData.exercise.knowledge = list[i].knowledge
 							jsonData.exercise.option = jsonData.item[0].option
 							jsonData.exercise.id = list[i].id
 							jsonData.exercise.pid = jsonData.pid
@@ -410,7 +408,6 @@ export default {
 						jsonData.exercise.option = jsonData.item[0].option
 						jsonData.exercise.id = jsonData.id
 						jsonData.exercise.pid = jsonData.pid
-						jsonData.exercise.knowledge = jsonData.knowledge
 						jsonData.exercise.scope = scope || 'private'
 						r(jsonData.exercise)
 					}catch(e){

+ 1 - 0
TEAMModelOS/ClientApp/src/utils/js-fn.js

@@ -271,6 +271,7 @@ function timeFormat(timestamp) {
     return `${Y}/${M < 9 ? '0' + (M + 1) : M + 1}/${D} ${H < 10 ? '0' + H : H}:${MIN < 10 ? '00' : MIN}`
 }
 function dateFormat(timestamp) {
+    timestamp = timestamp < 10000000000 ? timestamp * 1000 : timestamp
     let date = new Date(timestamp)
     let Y = date.getFullYear()
     let M = date.getMonth()

+ 6 - 6
TEAMModelOS/ClientApp/src/utils/public.js

@@ -859,17 +859,17 @@ export default {
 		var hourC = diffValue / hour;
 		var minC = diffValue / minute;
 		if (monthC >= 1) {
-			result = "" + parseInt(monthC) + "月前";
+			result = "" + parseInt(monthC) + app.$t('unit.diffTip1');
 		} else if (weekC >= 1) {
-			result = "" + parseInt(weekC) + "周前";
+			result = "" + parseInt(weekC) + app.$t('unit.diffTip2');
 		} else if (dayC >= 1) {
-			result = "" + parseInt(dayC) + "天前";
+			result = "" + parseInt(dayC) + app.$t('unit.diffTip3');
 		} else if (hourC >= 1) {
-			result = "" + parseInt(hourC) + "小时前";
+			result = "" + parseInt(hourC) + app.$t('unit.diffTip4');
 		} else if (minC >= 1) {
-			result = "" + parseInt(minC) + "分钟前";
+			result = "" + parseInt(minC) + app.$t('unit.diffTip5');
 		} else
-			result = "刚刚";
+			result = app.$t('unit.diffTip6');
 		return result;
 	},
 	/* 上传视频到BLob */

+ 0 - 5
TEAMModelOS/ClientApp/src/view/auth/Classroom.less

@@ -1,5 +0,0 @@
-.class-auth-container{
-    width: 100%;
-    height: 100%;
-    padding: 20px;
-}

+ 0 - 17
TEAMModelOS/ClientApp/src/view/auth/Classroom.vue

@@ -1,17 +0,0 @@
-<template>
-    <div class="class-auth-container">
-        教室列表
-    </div>
-</template>
-<script>
-export default {
-    data(){
-        return{
-
-        }
-    }
-}
-</script>
-<style lang="less" scoped>
-@import "./Classroom.less";
-</style>

+ 2 - 4
TEAMModelOS/ClientApp/src/view/auth/Index.vue

@@ -6,7 +6,7 @@
                     <span @click="tabClick('product')" :class="['auth-tab',routerName == 'product' ? 'auth-tab-active':'']">
                         服务授权管理
                     </span>
-                    <span @click="tabClick('authroom')" :class="['auth-tab',routerName == 'authroom' ? 'auth-tab-active':'']">
+                    <span @click="tabClick('serial')" :class="['auth-tab',routerName == 'serial' ? 'auth-tab-active':'']">
                         智慧教室授权管理
                     </span>
                 </div>
@@ -22,9 +22,7 @@
                         分配教学空间
                     </span>
                 </div>
-                <keep-alive include="SpaceInfo">
-                    <SpaceInfo></SpaceInfo>
-                </keep-alive>
+                <SpaceInfo></SpaceInfo>
                 <div class="auth-tab-wrap" style="margin-top:30px">
                     <span>
                         产品购买记录

+ 6 - 1
TEAMModelOS/ClientApp/src/view/auth/Product.less

@@ -8,8 +8,13 @@
     border: 1px solid var(--border-color);
     margin-bottom: 20px;
     border-radius: 4px;
-    background: white;
+    // background: white;
+    // background: #f0f0f0;
     position: relative;
+    &:hover{
+        background: #ffffff;
+        box-shadow: 0px 0px 10px #dddddd inset;
+    }
 }
 .product-name{
     font-size: 20px;

+ 6 - 3
TEAMModelOS/ClientApp/src/view/auth/Product.vue

@@ -13,13 +13,16 @@
                 <p class="product-status" style="margin-top:5px">
                     服务启用 / 到期日: --
                 </p>
-                <p class="product-detail" v-show="activeTag.includes(item.name)">
+                <!-- <p class="product-detail" v-show="activeTag.includes(item.name)"> -->
+                <p class="product-detail">
                     详细服务内容:
                 </p>
-                <p class="product-content" v-show="activeTag.includes(item.name)">
+                <!-- <p class="product-content" v-show="activeTag.includes(item.name)"> -->
+                <p class="product-content">
                     待提供产品/模块说明内容
                 </p>
-                <Icon type="ios-arrow-down" @click="toggleStatus(item.name)" :class="['toggle-status', activeTag.includes(item.name) ? 'toggle-status-active' : '']" />
+                <!-- 暂时全部展开 -->
+                <!-- <Icon type="ios-arrow-down" @click="toggleStatus(item.name)" :class="['toggle-status', activeTag.includes(item.name) ? 'toggle-status-active' : '']" /> -->
             </div>
         </vuescroll>
 

+ 172 - 0
TEAMModelOS/ClientApp/src/view/auth/Serial.less

@@ -0,0 +1,172 @@
+.serial-auth-container{
+    width: 100%;
+    height: 100%;
+    // padding: 20px;
+}
+.serial-auth-top{
+    height: 140px;
+    width: 100%;
+    border-bottom: 1px solid var(--border-color);
+    display: flex;
+    position: relative;
+}
+.auth-info-block{
+    width: 22%;
+    padding-left: 20px ;
+    height: 120px;
+}
+.hiteach-auth-text{
+    margin-top: 30px;
+}
+.info-wrap{
+    margin-top: 0px;
+    display: flex;
+    align-items: center;
+}
+.serial-type{
+    background: #1cc0f3;
+    color: white;
+    padding: 2px 8px;
+    margin-right: 5px;
+    font-size: 12px;
+    border-radius: 2px;
+}
+.serial-num{
+    font-size: 30px;
+    display: inline-block;
+    font-weight: 500;
+    margin-left: 15px;
+}
+.serial-filter-wrap{
+    position: absolute;
+    left: 20px;
+    bottom: 2px;
+}
+.room-filter-wrap{
+    position: absolute;
+    right: 10px;
+    bottom: 2px;
+}
+.search-room{
+    width: 180px;
+    margin-left: 15px;
+}
+.serial-mgt-box{
+    width: 100%;
+    height: ~"calc(100% - 190px)";
+}
+.serial-list-box{
+    width: 100%;
+    height: 100%;
+}
+.serial-item{
+    padding: 20px 10px 20px 22px;
+    position: relative;
+    &:hover{
+        background: var(--hover-text-color);
+    }
+    &:hover .serial-action-btn{
+        display: inline-block;
+    }
+}
+.serial-item-active{
+    background: var(--hover-text-color);
+}
+.serial-action-wrap{
+    position: absolute;
+    right: 15px;
+    top: 50%;
+    margin-top: -15px;
+}
+.serial-action-btn{
+    margin-right: 10px;
+    cursor: pointer;
+    color: #2d8cf0;
+    display: none;
+    user-select: none;
+}
+.serial-slt-status{
+    margin-right: 10px;
+    cursor: pointer;
+    color: #2d8cf0;
+}
+.serial-type-tag{
+    color: white;
+    width: fit-content;
+    padding: 1px 6px 2px 6px;
+    border-radius: 2px;
+    font-size: 12px;
+    white-space: nowrap;
+}
+.serial-info{
+    font-size: 16px;
+    margin-left: 10px;
+}
+.serial-auth-tag{
+    border: 1px solid var(--border-color);
+    margin-right: 15px;
+    padding: 2px 8px;
+    border-radius: 2px;
+    white-space: nowrap;
+    font-size: 12px;
+}
+.serial-date{
+    font-weight: 600;
+}
+.use-text{
+    margin-left: 30px;
+}
+.use-count{
+    margin-right: 10px;
+    font-weight: 600;
+}
+.has-expired{
+    color: #ed4014;
+}
+.classroom-list-box{
+    padding-left: 6px;
+    width: 100%;
+    height: 100%;
+}
+.room-item{
+    padding: 20px 10px 20px 22px;
+    position: relative;
+    &:hover{
+        background: var(--hover-text-color);
+    }
+    &:hover .room-action-btn{
+        display: inline-block;
+    }
+}
+
+.room-name{
+    font-size: 16px;
+    font-weight: 600;
+}
+.room-no-tag{
+    background: #5cadff;
+    color: white;
+    font-size: 12px;
+    padding: 1px 8px;
+    margin-left: 10px;
+}
+.room-serial-info{
+    margin-top: 10px;
+}
+.band-serial{
+    // color: #ed4014;
+}
+.room-action-wrap{
+    position: absolute;
+    right: 15px;
+    top: 50%;
+    margin-top: -15px;
+    
+}
+.room-action-btn{
+    margin-right: 10px;
+    cursor: pointer;
+    color: #2d8cf0;
+    display: none;
+    user-select: none;
+}

+ 269 - 0
TEAMModelOS/ClientApp/src/view/auth/Serial.vue

@@ -0,0 +1,269 @@
+<template>
+    <div class="serial-auth-container">
+        <Loading v-if="isLoading"></Loading>
+        <div class="serial-auth-top">
+            <div class="auth-info-block">
+                <p class="hiteach-auth-text">
+                    HiTeach授权数
+                </p>
+                <div class="info-wrap">
+                    <span class="serial-type">
+                        大量
+                    </span>
+                    <span class="serial-text">
+                        授权
+                    </span>
+                    <countTo :startVal='0' :endVal='countData.mult' :duration='500' class="serial-num"></countTo>
+                </div>
+            </div>
+            <div class="auth-info-block">
+                <div class="info-wrap" style="margin-top:50px">
+                    <span class="serial-type">
+                        单一
+                    </span>
+                    <span class="serial-text">
+                        授权
+                    </span>
+                    <countTo :startVal='0' :endVal='countData.single' :duration='500' class="serial-num"></countTo>
+                </div>
+            </div>
+            <div class="auth-info-block">
+                <p class="hiteach-auth-text">可启用装置</p>
+                <div class="info-wrap">
+                    <countTo :startVal='0' :endVal='countData.total' :duration='500' class="serial-num"></countTo>
+                </div>
+            </div>
+            <div class="auth-info-block">
+                <p class="hiteach-auth-text">已启用装置</p>
+                <div class="info-wrap">
+                    <countTo :startVal='0' :endVal='countData.used' :duration='500' class="serial-num"></countTo>
+                </div>
+            </div>
+            <div class="serial-filter-wrap">
+                <Dropdown>
+                    序列号:
+                    <a href="javascript:void(0)">
+                        显示所有
+                        <Icon type="ios-arrow-down"></Icon>
+                    </a>
+                    <DropdownMenu slot="list" transfer>
+                        <DropdownItem>
+                            显示所有
+                        </DropdownItem>
+                        <DropdownItem>
+                            单一授权
+                        </DropdownItem>
+                        <DropdownItem>
+                            大量授权
+                        </DropdownItem>
+                        <DropdownItem>
+                            可使用
+                        </DropdownItem>
+                        <DropdownItem>
+                            已到期
+                        </DropdownItem>
+                    </DropdownMenu>
+                </Dropdown>
+            </div>
+            <div class="room-filter-wrap">
+                <Dropdown placement="bottom-start">
+                    教室:
+                    <a href="javascript:void(0)">
+                        显示所有
+                        <Icon type="ios-arrow-down"></Icon>
+                    </a>
+                    <Input suffix="ios-search" placeholder="搜索" class="search-room"/>
+                    <DropdownMenu slot="list" transfer>
+                        <DropdownItem>
+                            显示所有
+                        </DropdownItem>
+                        <DropdownItem>
+                            未绑定
+                        </DropdownItem>
+                        <DropdownItem>
+                            已绑定
+                        </DropdownItem>
+                        <DropdownItem>
+                            序列号筛选
+                        </DropdownItem>
+                    </DropdownMenu>
+                </Dropdown>
+            </div>
+        </div>
+        <div class="serial-mgt-box">
+            <Split v-model="split1">
+                <div slot="left" class="serial-list-box">
+                    <vuescroll>
+                        <div :class="['serial-item',sltSerial.includes(item.serial) ? 'serial-item-active':'']" v-for="(item) in serialList" :key="item.serial">
+                            <p>
+                                <span class="serial-type-tag" :style="{background:item.deviceMax > 1 ? '#19be6b' : '#1cc0f3'}">
+                                    {{item.deviceMax > 1 ? '大量' : '单一'}}
+                                </span>
+                                <span class="serial-info">
+                                    {{item.serial}}
+                                </span>
+                            </p>
+                            <!-- 序列号权限 -->
+                            <div style="margin-top:10px">
+                                <span v-for="(item) in serialAuthList" :key="item.name" class="serial-auth-tag">
+                                    {{item.name}}
+                                </span>
+                            </div>
+                            <!-- 序列号到期时间 -->
+                            <div style="margin-top:10px">
+                                <span>
+                                    到期日:
+                                </span>
+                                <span class="serial-date" :style="{color:item.expired ? '#ed4014':''}">
+                                    {{$jsFn.dateFormat(item.endDate)}}
+                                </span>
+                                <span v-if="item.expired" class="has-expired">(已到期)</span>
+                                <span class="use-text">使用状况:</span>
+                                <span class="use-count">
+                                    {{`${item.deviceBound.length}/${item.deviceMax}`}}
+                                </span>
+                            </div>
+                            <div class="serial-action-wrap">
+                                <span class="serial-action-btn" v-show="item.deviceBound.length">解绑</span>
+                                <span class="serial-action-btn" v-if="!item.expired" v-show="item.deviceBound.length < item.deviceMax" @click="toggleSelectSerial(item)">
+                                    {{sltSerial.includes(item.serial) ? '取消' : '选择'}}
+                                </span>
+                                <span class="serial-slt-status" v-show="sltSerial.includes(item.serial)">已选中</span>
+                            </div>
+                        </div>
+                    </vuescroll>
+                </div>
+                <div slot="right" class="classroom-list-box">
+                    <vuescroll>
+                        <div v-for="(item,index) in roomList" :key="item.no" class="room-item">
+                            <p class="room-name">
+                                {{item.name}}
+                                <span class="room-no-tag">{{item.no}}</span>
+                            </p>
+                            <p class="room-serial-info">
+                                序列号:
+                                <span class="band-serial" :style="{color:item.serial ? '#2d8cf0':'#ed4014'}">
+                                    {{item.serial || '暂未绑定'}}
+                                </span>
+                            </p>
+                            <div class="room-action-wrap">
+                                <span class="room-action-btn" @click="bindSerial(item,index)">
+                                    绑定
+                                </span>
+                            </div>
+                        </div>
+                    </vuescroll>
+                </div>
+            </Split>
+
+        </div>
+    </div>
+</template>
+<script>
+import CountTo from 'vue-count-to'
+import { mapGetters } from 'vuex'
+export default {
+    components: {
+        CountTo
+    },
+    data() {
+        return {
+            split1: 0.5,
+            isLoading: false,
+            serialList: [],
+            sltSerial: [],
+            serialAuthList: [
+                {
+                    code: '',
+                    name: '苏格拉底议课'
+                },
+                {
+                    code: '',
+                    name: '远距教室服务'
+                },
+                {
+                    code: '',
+                    name: '录播系统'
+                }
+            ]
+        }
+    },
+    methods: {
+        bindSerial(room, index) {
+            let serialInfo = this.serialList.find(item => item.serial == this.sltSerial[0])
+            if (serialInfo) {
+                serialInfo.deviceBound.push(room.id)
+            }
+            this.$set(this.roomList[index], 'serial', this.sltSerial[0])
+            this.sltSerial = []
+        },
+        //选择待绑定的序列号
+        toggleSelectSerial(serialInfo) {
+            let index = this.sltSerial.indexOf(serialInfo.serial)
+            if (index > -1) {
+                this.sltSerial.splice(index, 1)
+            } else {
+                this.sltSerial = []
+                this.sltSerial.push(serialInfo.serial)
+            }
+        },
+        getProductInfo() {
+            this.isLoading = true
+            this.$api.serviceDriveAuth.getSchoolProduct(this.$store.state.userInfo.schoolCode).then(
+                res => {
+                    let timestamp = Date.now()
+                    console.log(timestamp)
+                    res.serial.forEach(item => {
+                        item.expired = item.endDate * 1000 < timestamp
+                    })
+                    this.serialList = res.serial
+                },
+                err => {
+
+                }
+            ).finally(() => {
+                this.isLoading = false
+            })
+        }
+    },
+    mounted() {
+        this.getProductInfo()
+    },
+    computed: {
+        ...mapGetters({
+            periods: 'user/getPeriods', // 學制s
+            aprules: 'schoolBaseInfo/getAprules',
+            roomList: 'user/getRooms',
+        }),
+        countData() {
+            let data = {
+                mult: 0,
+                single: 0,
+                total: 0,
+                used: 0
+            }
+            if (this.serialList.length) {
+                this.serialList.forEach(item => {
+                    if (item.deviceMax > 1) {
+                        data.mult++
+                    } else {
+                        data.single++
+                    }
+                    data.total += item.deviceMax
+                    data.used += item.deviceBound.length
+                })
+            }
+            return data
+        }
+    }
+
+}
+</script>
+<style lang="less" scoped>
+@import "./Serial.less";
+</style>
+<style lang="less">
+.search-room .ivu-input{
+    border-radius: 50px;
+}
+</style>

+ 10 - 5
TEAMModelOS/ClientApp/src/view/auth/SpaceInfo.vue

@@ -34,11 +34,11 @@ export default {
             formatData: [],
             typeCountPie: undefined,
             option: {
-                color:['#cccccc','#5470c6','#91cc75','#fac858','#ee6666','#73c0de','#3ba272','#fc8452','#9a60b4','#ea7ccc'],
+                color: ['#cccccc', '#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'],
                 tooltip: {
                     trigger: 'item',
                     // formatter: '{b}({c})',
-                    formatter: (params)=>{
+                    formatter: (params) => {
                         return `${params.name}:${this.$jsFn.formatBytes(params.value)}`
                     },
                     textStyle: {
@@ -131,7 +131,7 @@ export default {
                     this.option.series[0].data = formatData
                     this.option.title = []
                     this.option.title.push({
-                        text: '{name|' + '总空间' + '}\n{val|' + this.SCHOOL_SPACE + 'GB' + '}',
+                        text: '{name|' + '总空间: ' + '}{val|' + this.SCHOOL_SPACE + 'G' + '}\n' + '{name|' + '已使用: ' + '}{use|' + this.$jsFn.formatBytes(res.total + res.teachSpace) + '}',
                         top: '25%',
                         left: 'center',
                         textStyle: {
@@ -143,10 +143,15 @@ export default {
                                     padding: [10, 0],
                                 },
                                 val: {
-                                    fontSize: 35,
-                                    fontWeight: 'bold',
+                                    fontSize: 24,
+                                    fontWeight: '600',
                                     color: '#303030',
                                 },
+                                use: {
+                                    fontSize: 24,
+                                    fontWeight: '600',
+                                    color: '#ed4014',
+                                },
                             },
                         },
                     })

+ 51 - 16
TEAMModelOS/ClientApp/src/view/classmgt/ClassStudent.vue

@@ -44,13 +44,15 @@
                     </template>
                     <template slot-scope="{ row,index }" slot="no">
                         <span v-show="editIndex !== index" :style="{color:row.no ? '#303030':'red'}">{{row.no || $t('cusMgt.notSet')}}</span>
-                        <Input v-special-char v-model="classList[curClassIndex].students[index].no" v-show="editIndex == index" style="width: 60px;" type="number" />
+                        <!-- <Input v-special-char v-model="classList[curClassIndex].students[index].no" v-show="editIndex == index" style="width: 60px;" type="number" /> -->
+                        <InputNumber :min="1" v-special-char v-model="editNo" v-show="editIndex == index" style="width: 60px;"/>
                         <Icon type="md-checkmark" v-show="editIndex == index" @click="confirmSetNo(classList[curClassIndex].students[index])" class="reset-no-btn" />
                         <Icon type="md-close" v-show="editIndex == index" @click="cancelSetNo()" class="reset-no-btn" />
                     </template>
                     <template slot-scope="{ row,index }" slot="irs">
                         <span v-show="editIndex !== index" :style="{color:row.irs ? '#303030':'red'}">{{row.irs || $t('cusMgt.notSet')}}</span>
-                        <Input v-special-char v-model="classList[curClassIndex].students[index].irs" v-show="editIndex == index" style="width: 60px;" type="number" />
+                        <!-- <Input v-special-char v-model="classList[curClassIndex].students[index].irs" v-show="editIndex == index" style="width: 60px;" type="number" /> -->
+                        <InputNumber :min="1" v-special-char v-model="editIrs" v-show="editIndex == index" style="width: 60px;"/>
                         <Icon type="md-checkmark" v-show="editIndex == index" @click="confirmSetNo(classList[curClassIndex].students[index])" class="reset-no-btn" />
                         <Icon type="md-close" v-show="editIndex == index" @click="cancelSetNo()" class="reset-no-btn" />
                     </template>
@@ -179,12 +181,14 @@ export default {
     },
     data() {
         return {
+            editIrs: 1,
+            editNo: 1,
             btnLoading: false,
             mLoading: true,
             fastType: [],
             fastStatus: false,
-            resetNoBef: 0,
-            resetIRSBef: 0,
+            // resetNoBef: 0,
+            // resetIRSBef: 0,
             editIndex: -1,
             groupLoading: false,
             avatarUrl: '',
@@ -238,6 +242,9 @@ export default {
                     align: 'center',
                     sortable: true,
                     sortMethod: function (a, b, type) {
+                        if(!a){
+                            return -1
+                        }
                         if (type == 'asc') {
                             return parseInt(a) > parseInt(b) ? 1 : -1
                         } else if (type == 'desc') {
@@ -338,24 +345,50 @@ export default {
             }
         },
         confirmSetNo(row) {
+            if (!this.editIrs) {
+                this.$Message.warning(this.$t('schoolBaseInfo.setIrsWarning'))
+                return
+            }
+            if (!this.editNo) {
+                this.$Message.warning(this.$t('schoolBaseInfo.setNoWarning'))
+                return
+            }
+            // //检查irs重复
+            // let irsList = this.classList[this.curClassIndex].students.map(item => item.irs)
+            // let irsRep = irsList.some((item, index) => {
+            //     return irsList.indexOf(item) != index
+            // })
+            // if (irsRep) {
+            //     this.$Message.warning(this.$t('schoolBaseInfo.irsRep'))
+            //     return
+            // }
+            // // 检查no重复
+            // let noList = this.classList[this.curClassIndex].students.map(item => item.no)
+            // let noRep = noList.some((item, index) => {
+            //     return noList.indexOf(item) != index
+            // })
+            // if (noRep) {
+            //     this.$Message.warning(this.$t('schoolBaseInfo.noRep'))
+            //     return
+            // }
             //检查irs重复
-            let irsList = this.classList[this.curClassIndex].students.map(item => item.irs)
-            let irsRep = irsList.some((item, index) => {
-                return irsList.indexOf(item) != index
+            let isRepeat = this.classList[this.curClassIndex].students.some((item, index) => {
+                return index != this.editIndex && item.irs == this.editIrs
             })
-            if (irsRep) {
+            if (isRepeat) {
                 this.$Message.warning(this.$t('schoolBaseInfo.irsRep'))
                 return
             }
             // 检查no重复
-            let noList = this.classList[this.curClassIndex].students.map(item => item.no)
-            let noRep = noList.some((item, index) => {
-                return noList.indexOf(item) != index
+            let noRepeat = this.classList[this.curClassIndex].students.some((item, index) => {
+                return index != this.editIndex && item.no == this.editNo
             })
-            if (noRep) {
+            if (noRepeat) {
                 this.$Message.warning(this.$t('schoolBaseInfo.noRep'))
                 return
             }
+            this.$set(this.classList[this.curClassIndex].students[this.editIndex], 'irs', this.editIrs + '')
+            this.$set(this.classList[this.curClassIndex].students[this.editIndex], 'no', this.editNo + '')
             this.editIndex = -1
             let schoolId = this.$store.state.userInfo.schoolCode
             let data = [row]
@@ -372,14 +405,16 @@ export default {
             })
         },
         cancelSetNo() {
-            this.classList[this.curClassIndex].students[this.editIndex].no = this.resetNoBef
-            this.classList[this.curClassIndex].students[this.editIndex].irs = this.resetIRSBef
+            // this.classList[this.curClassIndex].students[this.editIndex].no = this.resetNoBef
+            // this.classList[this.curClassIndex].students[this.editIndex].irs = this.resetIRSBef
             this.editIndex = -1
         },
         setNo(row, index) {
             this.editIndex = index
-            this.resetNoBef = row.no
-            this.resetIRSBef = row.irs
+            // this.resetNoBef = row.no
+            // this.resetIRSBef = row.irs
+            this.editIrs = row.irs ? parseInt(row.irs) : null
+            this.editNo = row.no ? parseInt(row.no) : null
         },
         confirmSetAvatar() {
             if (!this.avatarUrl) {

+ 1 - 1
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseImport.vue

@@ -335,7 +335,7 @@
 				var regex = /(\<math.*?<\/math\>)/g;
 				var html = res.html
 				var arr = html.match(regex);
-				if(arr && Array.isArray(arr) && arr.length){
+				if(arr && Array.isArray(arr) && arr.length && window.MathJax){
 					// 对所有的mathml进行转换成svg操作
 					arr.forEach(math => {
 						let svgHtml = window.MathJax.mathml2svg(math, { em: 12, ex: 6 })

+ 1 - 1
TEAMModelOS/ClientApp/src/view/forgotPw/Index.vue

@@ -37,7 +37,7 @@
                     <span v-if="applyType == 'email' " style="display: block;text-align: center;font-size: 13px;color: rgb(244, 67, 54);">{{ $t('forgotPW.form.remind')}}</span>
                 </FormItem>
                 <FormItem class="formItem" prop="account">
-                    <Row type="flex" justify="center" align="top">
+                    <Row>
                         <Col v-if="applyType == 'phone'" :span="9">
                         <CountryCode v-model="cCode" />
                         </Col>

+ 9 - 3
TEAMModelOS/ClientApp/src/view/learnactivity/markpaper/MarkSetting.vue

@@ -13,9 +13,11 @@
         </div>
         <div v-show="!isSetting" class="publish-status-box">
             <Icon type="ios-information-circle-outline" class="tips-icon" />
+            <p class="task-tips">{{$t('learnActivity.mgtScEv.taskTips')}}</p>
             <p class="status-text">
-                <span style="font-size:16px;">{{$t('learnActivity.mark.noPublish')}}</span>
+                <span style="font-size:16px;color:#ed4014">{{$t('learnActivity.mark.noPublish')}}</span>
             </p>
+            
             <span class="setting-btn" @click="isSetting = !isSetting">{{$t('learnActivity.mark.publish')}}</span>
         </div>
         <vuescroll v-show="isSetting">
@@ -1044,6 +1046,10 @@ export default {
 }
 </script>
 <style scoped lang="less">
+.task-tips{
+    width: 70%;
+    margin: auto;
+}
 .objective-tips {
     color: #ff9900;
     margin-bottom: 15px;
@@ -1064,7 +1070,7 @@ export default {
     // background: #37363a;
     margin: 60px auto;
     padding: 20px;
-    border-radius: 20px;
+    border-radius: 10px;
     position: relative;
     box-shadow: 0 0px 10px 0px #d5d5d5;
 
@@ -1082,7 +1088,7 @@ export default {
     .status-text {
         // color: #ccc;
         text-align: center;
-        margin-top: 40px;
+        margin-top: 20px;
         font-size: 14px;
     }
     .setting-btn {

+ 11 - 14
TEAMModelOS/ClientApp/src/view/newcourse/MyCourse.vue

@@ -108,22 +108,22 @@
                                     <template slot-scope="{ row }" slot="picture">
                                         <PersonalPhoto :name="row.name || ''" :picture="row.picture" />
                                     </template>
-                                    <template slot-scope="{ row,index }" slot="no">
+                                    <template slot-scope="{ row }" slot="no">
                                         <span :style="{color:row.no ? '#303030':'red'}">{{row.no || $t('cusMgt.notSet')}}</span>
                                     </template>
                                     <template slot-scope="{ row }" slot="type">
                                         <span>{{row.type === 2 ? $t('cusMgt.schoolType') : $t('cusMgt.tmIDType')}}</span>
                                     </template>
-                                    <template slot-scope="{ row,index }" slot="irs">
+                            ``        <template slot-scope="{ row,index }" slot="irs">
                                         <span v-show="editIndex !== index" :style="{color:row.irs ? '#303030':'red'}">{{row.irs || $t('cusMgt.notSet')}}</span>
                                         <InputNumber :min="1" v-model="editIrs" v-show="editIndex == index" style="width: 60px;"></InputNumber>
                                         <Icon type="md-checkmark" v-show="editIndex == index" @click="confirmSetNo(row,index)" class="reset-no-btn" />
                                         <Icon type="md-close" v-show="editIndex == index" @click="cancelSetNo()" class="reset-no-btn" />
                                     </template>
-                                    <template slot-scope="{ row, index }" slot="groupId">
+                                    <template slot-scope="{ row }" slot="groupId">
                                         <span>{{row.groupId ? row.groupId : '--'}}</span>
                                     </template>
-                                    <template slot-scope="{ row, index }" slot="groupName">
+                                    <template slot-scope="{ row }" slot="groupName">
                                         <span>{{row.groupName ? row.groupName : '--'}}</span>
                                     </template>
                                     <template slot-scope="{ row,index }" slot="action">
@@ -423,12 +423,6 @@ export default {
                     align: 'left ',
                     width: '150'
                 },
-                {
-                    title: this.$t('courseManage.classroom.studentTableC1'),
-                    slot: 'no',
-                    align: 'center',
-                    sortable: true
-                },
                 {
                     title: this.$t('courseManage.classroom.studentTableC2'),
                     key: 'name',
@@ -451,6 +445,12 @@ export default {
                     slot: 'groupName',
                     align: 'center'
                 },
+                {
+                    title: this.$t('courseManage.classroom.studentTableC1'),
+                    slot: 'no',
+                    align: 'center',
+                    sortable: true
+                },
                 {
                     title: this.$t('courseManage.classroom.studentTableC9'),
                     slot: 'irs',
@@ -1028,12 +1028,9 @@ export default {
                 return item.id == this.teaClassList[this.curClassIndex].stulist
             })
             if (stulist) {
-                stulist.members.forEach((item, index) => {
-                    item.irs = (index + 1) + ''
-                })
                 let s = stulist.members.find(item=>item.id == row.id)
                 s.irs = this.editIrs + ''
-                schedule.allStu = stulist.members
+                schedule.allStu = this._.cloneDeep(stulist.members)
                 this.saveStuList(stulist)
             }
             this.editIndex = -1

+ 16 - 8
TEAMModelOS/ClientApp/src/view/newcourse/NewCusMgt.vue

@@ -124,8 +124,8 @@
                                                 <template slot-scope="{ row }" slot="picture">
                                                     <PersonalPhoto :name="row.name" :picture="row.picture" />
                                                 </template>
-                                                <template slot-scope="{ row,index }" slot="no">
-                                                    <span>{{row.no}}</span>
+                                                <template slot-scope="{ row }" slot="no">
+                                                    <span :style="{color:row.no ? '#515a6e':'#ed4014'}">{{row.no || $t('cusMgt.notSet')}}</span>
                                                 </template>
                                                 <template slot-scope="{ row ,index}" slot="action">
                                                     <div class="item-tools" v-if="$access.can('admin.*|student-upd')">
@@ -133,11 +133,11 @@
                                                         <Icon type="md-remove-circle" size="18" color="white" style="margin-left:10px" @click="removeStudent(index)" :title="$t('schoolBaseInfo.delStuBtn')" />
                                                     </div>
                                                 </template>
-                                                <template slot-scope="{ row, index }" slot="groupId">
+                                                <template slot-scope="{ row }" slot="groupId">
                                                     <span>{{row.groupId ? row.groupId : '- -'}}</span>
                                                 </template>
-                                                <template slot-scope="{ row, index }" slot="groupName">
-                                                    <span>{{row.groupName ? row.groupName : $t('cusMgt.noGroup')}}</span>
+                                                <template slot-scope="{ row }" slot="groupName">
+                                                    <span :style="{color:row.groupName ? '#515a6e':'#ed4014'}">{{row.groupName || $t('cusMgt.noGroup')}}</span>
                                                 </template>
                                             </Table>
                                         </vuescroll>
@@ -399,14 +399,22 @@ export default {
                 {
                     title: this.$t('courseManage.classroom.studentTableC7'),
                     key: 'id',
-                    align: 'center',
-                    sortable: true
+                    align: 'center'
                 },
                 {
                     title: this.$t('courseManage.classroom.studentTableC1'),
+                    key:'no',
                     slot: 'no',
                     align: 'center',
-                    sortable: true
+                    sortable: true,
+                    sortMethod: function (a, b, type) {
+                        if (!a) return -1
+                        if (type == 'asc') {
+                            return parseInt(a) > parseInt(b) ? 1 : -1
+                        } else if (type == 'desc') {
+                            return parseInt(a) > parseInt(b) ? -1 : 1
+                        }
+                    }
                 }
             ],
             listColumn: [

+ 6 - 6
TEAMModelOS/ClientApp/src/view/regist/Index.vue

@@ -52,7 +52,7 @@
                     <span v-if="applyType == 'email' " style="width: 400px;display: block;text-align: center;font-size: 13px;color: rgb(244, 67, 54);">{{ $t('regist.form.remind')}}</span>
                 </FormItem>
                 <FormItem class="formItem" prop="account">
-                    <Row type="flex" justify="center" align="top">
+                    <Row>
                         <Col v-if="applyType == 'phone'" :span="9">
                         <CountryCode v-model="cCode" />
                         </Col>
@@ -62,15 +62,15 @@
                     </Row>
                 </FormItem>
                 <FormItem class="formItem" prop="pinCode">
-                    <Row type="flex" justify="center" align="top">
-                        <Col :span="9">
+                    <Row>
+                        <i-col :span="9">
                         <Button class="radius-right-0" style="font-size:12px; " long type="primary" @click="sendPinCode()" :disabled="countdown || pinCodeSwitch">
                             {{ sendBtnText }}
                         </Button>
-                        </Col>
-                        <Col :span="15">
+                        </i-col>
+                        <i-col :span="15">
                         <Input class="radius-left-0 input-font-size-12" v-model="registForm.pinCode" :placeholder="$t('regist.form.placeholder.pindCode')"></Input>
-                        </Col>
+                        </i-col>
                     </Row>
                 </FormItem>
                 <FormItem class="formItem" :label="$t('regist.form.text4')" prop="name">

+ 31 - 14
TEAMModelOS/ClientApp/src/view/student-account/stuMgt/StuMgt.vue

@@ -62,13 +62,15 @@
                         <PersonalPhoto :name="row.name" :picture="row.picture" />
                     </template>
                     <template slot-scope="{ row }" slot="classId">
-                        <span>{{ row.className }}</span>
+                        <span :style="{color: row.className ? '#515a6e':'#ed4014'}">{{ row.className || $t('stuAccount.noRelClass') }}</span>
                     </template>
                     <template slot-scope="{ row }" slot="no">
                         <span>{{ row.no }}</span>
                     </template>
                     <template slot-scope="{ row }" slot="gradeName">
-                        <span>{{ $jsFn.getGradeNameByYear(schoolBase, searchPeriod, row.classYear > 0 ? row.classYear : row.year) }}</span>
+                        <span :style="{color: row.classYear > 0 ? '#515a6e':'#ed4014'}">
+                            {{ row.classYear > 0 ? $jsFn.getGradeNameByYear(schoolBase, searchPeriod, row.classYear) : $t('stuAccount.noRelClass')}}
+                        </span>
                     </template>
                     <template slot-scope="{ row }" slot="action">
                         <div class="item-tools" v-if="$access.can('admin.*|student-upd')">
@@ -558,34 +560,48 @@ export default {
                     key: 'id',
                     title: this.$t('stuAccount.account'),
                     align: 'center',
-                    sortable: true,
-                    sortMethod: (a, b, type) => {
-                        if (type == 'asc') {
-                            return a.localeCompare(b)
-                        } else {
-                            return b.localeCompare(a)
-                        }
-                    }
+                    // sortable: true,
+                    // sortMethod: (a, b, type) => {
+                    //     if (type == 'asc') {
+                    //         return a.localeCompare(b)
+                    //     } else {
+                    //         return b.localeCompare(a)
+                    //     }
+                    // }
                 },
                 {
                     key: 'name',
                     title: this.$t('stuAccount.stuName'),
-                    align: 'center',
-                    sortable: true
+                    align: 'center'
                 },
                 {
                     key: 'year',
                     title: this.$t('stuAccount.academicYear'),
                     align: 'center',
                     width: 140,
-                    sortable: true
+                    sortable: true,
+                    sortMethod: (a, b, type) => {
+                        if (type == 'desc') {
+                            return parseInt(a) > parseInt(b) ? -1 : 1
+                        } else {
+                            return parseInt(a) < parseInt(b) ? -1 : 1
+                        }
+                    }
                 },
                 {
                     slot: 'gradeName',
+                    key: 'classYear',
                     title: this.$t('stuAccount.grade'),
                     align: 'center',
                     width: 140,
-                    sortable: true
+                    sortable: true,
+                    sortMethod: (a, b, type) => {
+                        if (type == 'desc') {
+                            return parseInt(a) > parseInt(b) ? -1 : 1
+                        } else {
+                            return parseInt(a) < parseInt(b) ? -1 : 1
+                        }
+                    }
                 },
                 {
                     slot: 'classId',
@@ -600,6 +616,7 @@ export default {
                     width: 120,
                     sortable: true,
                     sortMethod: (a, b, type) => {
+                        if (!a) return -1
                         if (type == 'desc') {
                             return parseInt(a) > parseInt(b) ? -1 : 1
                         } else {

+ 9 - 5
TEAMModelOS/ClientApp/src/view/student-web/App.vue

@@ -55,9 +55,9 @@
                  <!-- hiteach课堂记录 -->
                 <!-- <MenuItem name="5" to="/studentWeb/hiteachView" :title="$t('studentWeb.type.hiteach')">
                     <div>
-                        <svg-icon icon-class="hiteach" class="tabIcon4" /> -->
-                        <!-- <span class="develop">{{ $t("studentWeb.public.develop") }}</span> -->
-                        <!-- <span v-show="MyNo != 5">{{ $t('studentWeb.type.hiteach') }}</span>
+                        <svg-icon icon-class="hiteach" class="tabIcon4" />
+                        <span class="develop">{{ $t("studentWeb.public.develop") }}</span>
+                        <span v-show="MyNo != 5">{{ $t('studentWeb.type.hiteach') }}</span>
                     </div>
                 </MenuItem> -->
                 <!-- 自主学习 -->
@@ -67,9 +67,13 @@
                         <span class="develop">{{ $t("studentWeb.public.develop") }}</span>
                     </div>
                 </MenuItem> -->
-                <MenuItem name="4" to="/studentWeb/eventView" :title="$t('studentWeb.type.activity')">
+                <MenuItem name="3" to="/studentWeb/eventView" :title="$t('studentWeb.type.activity')">
                     <svg-icon icon-class="selflearning" class="tabIcon2" />
-                    <span v-show="MyNo != 4">{{ $t('studentWeb.type.activity') }}</span>
+                    <span v-show="MyNo != 3">{{ $t('studentWeb.type.activity') }}</span>
+                </MenuItem>
+                <MenuItem name="2" to="/studentWeb/courseList" :title="$t('studentWeb.courseList-title')">
+                    <svg-icon icon-class="course" class="tabIcon5" />
+                    <span v-show="MyNo != 2">{{ $t('studentWeb.course') }}</span>
                 </MenuItem>
                 <MenuItem name="1" to="/studentWeb/homeView" :title="$t('studentWeb.type.home')">
                     <svg-icon icon-class="home" class="tabIcon3" />

+ 25 - 14
TEAMModelOS/ClientApp/src/view/task/index.vue

@@ -196,7 +196,7 @@ export default {
             })
             if (this.taskListShow.length) {
                 this.findTaskData()
-            }else{
+            } else {
                 this.isLoading = false
             }
         },
@@ -285,18 +285,29 @@ export default {
         */
         async toByQuView(data) {
             // 批量阅卷路由(开发中,暂未完成)
-            if (this.$store.state.config.srvAdrType != 'product') {
-                this.$router.push({
-                    name: 'ByQu2',
-                    params: {
-                        task: this.taskList[this.curTaskIndex], //阅卷任务数据
-                        stusInfo: this.markData.objs, //已阅和进行中的学生数据
-                        paperData: this.fullPaper, //试卷数据
-                        quIndex: data
-                    }
-                })
-                return
-            }
+            // if (this.$store.state.config.srvAdrType != 'product') {
+            //     this.$router.push({
+            //         name: 'ByQu2',
+            //         params: {
+            //             task: this.taskList[this.curTaskIndex], //阅卷任务数据
+            //             stusInfo: this.markData.objs, //已阅和进行中的学生数据
+            //             paperData: this.fullPaper, //试卷数据
+            //             quIndex: data
+            //         }
+            //     })
+            //     return
+            // }
+            // 开放批量阅卷功能
+            this.$router.push({
+                name: 'ByQu2',
+                params: {
+                    task: this.taskList[this.curTaskIndex], //阅卷任务数据
+                    stusInfo: this.markData.objs, //已阅和进行中的学生数据
+                    paperData: this.fullPaper, //试卷数据
+                    quIndex: data
+                }
+            })
+            return
             if (this.taskList[this.curTaskIndex].progress == 'pending') {
                 this.$Message.warning(this.$t('task.pendingTips'))
                 return
@@ -415,7 +426,7 @@ export default {
                 err => {
                     console.log(err)
                 }
-            ).finally(()=>{
+            ).finally(() => {
                 this.isLoading = false
             })
         },

+ 7 - 6
TEAMModelOS/ClientApp/src/view/user/BandPhone.vue

@@ -6,7 +6,7 @@
             </h3>
             <Form class="validForm" ref="sendForm" :model="sendForm" :rules="sendRules" label-position="top" @keydown.enter.native="handleSubmit('sendForm')" style="color: white;">
                 <FormItem class="formItem" prop="account">
-                    <Row type="flex" justify="center" align="top">
+                    <Row>
                         <Col v-if="applyType == 'phone'" :span="9">
                         <CountryCode v-model="cCode" />
                         </Col>
@@ -255,20 +255,21 @@ export default {
                                 err => {
                                     this.$Message.error(this.$t('login.loginerr'))
                                 }
-                            )
+                            ).finally((()=>{
+                                this.loading = false
+                            }))
                         },
                         err => {
-                            console.error(err)
+                            this.loading = false
                             this.$Message.error(this.$t('login.banderr'))
                         }
                     )
                 },
                 err => {
                     this.$Message.error(this.$t('login.getinfoerr'))
+                    this.loading = false
                 }
-            ).finally(() => {
-                this.loading = false
-            })
+            )
         },
         /**
          * 发送验证

+ 13 - 73
TEAMModelOS/Controllers/Analysis/AchievementController.cs

@@ -3665,15 +3665,17 @@ namespace TEAMModelOS.Controllers.Analysis
                 var doc = _DOXC2HTMLTranslator.Translate(fileDto.file.OpenReadStream());
                 (List<HTEXLib.DOCX.Models.ItemInfo> tests, List<string> error) = _HTML2ITEMV3Translator.Translate(doc);
                 List<Task<string>> tasks = new List<Task<string>>();
-                PaperDto paperDto = new PaperDto();
-                paperDto.id = Guid.NewGuid().ToString();
-                paperDto.name = fileDto.name;
-                paperDto.code = fileDto.code;
-                paperDto.scope = "school";
-                paperDto.multipleRule = fileDto.multipleRule;
-                paperDto.gradeIds = fileDto.gradeIds;
-                paperDto.subjectId = fileDto.subjectId;
-                paperDto.periodId = fileDto.periodId;
+                PaperDto paperDto = new()
+                {
+                    id = Guid.NewGuid().ToString(),
+                    name = fileDto.name,
+                    code = fileDto.code,
+                    scope = "school",
+                    multipleRule = fileDto.multipleRule,
+                    gradeIds = fileDto.gradeIds,
+                    subjectId = fileDto.subjectId,
+                    periodId = fileDto.periodId
+                };
                 foreach (HTEXLib.DOCX.Models.ItemInfo item in tests)
                 {
                     Slides slides = new();
@@ -3904,38 +3906,7 @@ namespace TEAMModelOS.Controllers.Analysis
                                                         foreach (char a in aa)
                                                         {
                                                             ans.Add(a.ToString());
-                                                            oans.Add(a.ToString());
-                                                            /* switch (a)
-                                                             {
-                                                                 case '1':
-                                                                     ans.Add("A");
-                                                                     oans.Add("A");
-                                                                     break;
-                                                                 case '2':
-                                                                     ans.Add("B");
-                                                                     oans.Add("B");
-                                                                     break;
-                                                                 case '3':
-                                                                     ans.Add("C");
-                                                                     oans.Add("C");
-                                                                     break;
-                                                                 case '4':
-                                                                     ans.Add("D");
-                                                                     oans.Add("D");
-                                                                     break;
-                                                                 case '5':
-                                                                     ans.Add("E");
-                                                                     oans.Add("E");
-                                                                     break;
-                                                                 case '6':
-                                                                     ans.Add("F");
-                                                                     oans.Add("F");
-                                                                     break;
-                                                                 default:
-                                                                     ans.Add("");
-                                                                     oans.Add("");
-                                                                     break;
-                                                             }*/
+                                                            oans.Add(a.ToString());                                                           
                                                         }
                                                         objective.Add(oans);
                                                         answers.Add(ans);
@@ -3943,38 +3914,7 @@ namespace TEAMModelOS.Controllers.Analysis
                                                     else
                                                     {
                                                         ans.Add(aa);
-                                                        oans.Add(aa);
-                                                        /*switch (aa)
-                                                        {
-                                                            case "1":
-                                                                ans.Add("A");
-                                                                oans.Add("A");
-                                                                break;
-                                                            case "2":
-                                                                ans.Add("B");
-                                                                oans.Add("B");
-                                                                break;
-                                                            case "3":
-                                                                ans.Add("C");
-                                                                oans.Add("C");
-                                                                break;
-                                                            case "4":
-                                                                ans.Add("D");
-                                                                oans.Add("D");
-                                                                break;
-                                                            case "5":
-                                                                ans.Add("E");
-                                                                oans.Add("E");
-                                                                break;
-                                                            case "6":
-                                                                ans.Add("F");
-                                                                oans.Add("F");
-                                                                break;
-                                                            default:
-                                                                ans.Add("");
-                                                                oans.Add("");
-                                                                break;
-                                                        }*/
+                                                        oans.Add(aa);                                                        
                                                         objective.Add(oans);
                                                         answers.Add(ans);
                                                     }

+ 7 - 1
TEAMModelOS/Controllers/Knowledge/KnowledgesController.cs

@@ -171,7 +171,13 @@ namespace TEAMModelOS.Controllers
                         return BadRequest(new { notinnew });
                     }
                 }
-                _ = _httpTrigger.RequestHttpTrigger(new { old_new = old_new, school = $"{school}" }, _option.Location, "knowledge-change");
+                try
+                {
+                    _ = _httpTrigger.RequestHttpTrigger(new { old_new = old_new, school = $"{school}" }, _option.Location, "knowledge-change");
+                }
+                catch (Exception ex) {
+                    //暂不处理
+                }
             }
             return Ok(new { knowledge });
         }

+ 8 - 4
TEAMModelOS/Controllers/School/ClassController.cs

@@ -276,12 +276,16 @@ namespace TEAMModelOS.Controllers
                 }
                 List<Task<ItemResponse<Course>>> taskCourses = new List<Task<ItemResponse<Course>>>();
                 List<Course> courses = new List<Course>();
-                await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<Course>(
-                    queryText: $"select value(c) from c where c.id in ({string.Join(",", cids.Select(o => $"'{o}'"))})",
-                    requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Course-{code}") }))
+                if(cids.IsNotEmpty())
                 {
-                    courses.Add(item);
+                    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<Course>(
+                       queryText: $"select value(c) from c where c.id in ({string.Join(",", cids.Select(o => $"'{o}'"))})",
+                       requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Course-{code}") }))
+                    {
+                        courses.Add(item);
+                    }
                 }
+               
                 //courses.ForEach(c => c.schedule.Where(s => !string.IsNullOrEmpty(s.classId)&& s.classId.Equals(id))
                 foreach (Course course in courses)
                 {

+ 2 - 2
TEAMModelOS/Controllers/School/StudentController.cs

@@ -2630,7 +2630,7 @@ namespace TEAMModelOS.Controllers
                         var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(school_code.GetString().ToLower(), BlobContainerSasPermissions.Read);
 
                         //換取AuthToken,提供給前端
-                        var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id.GetString(), name.GetString(), picture.GetString(), _option.JwtSecretKey,scope: Constant.ScopeStudent, schoolID: school_code.GetString(), roles: new[] { "student" });
+                        var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id.GetString(), name.GetString(), picture.GetString(), _option.JwtSecretKey,scope: Constant.ScopeStudent, Website: "IES", schoolID: school_code.GetString(), roles: new[] { "student" });
                         var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
                         var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
                         var token = await CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, _option.Location.Replace("-Dep","").Replace("-Test",""));
@@ -2770,7 +2770,7 @@ namespace TEAMModelOS.Controllers
                             }
                         }
                         //換取AuthToken,提供給前端
-                        var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id.GetString(), name.GetString(), picture.GetString(), _option.JwtSecretKey, scope: Constant.ScopeStudent, schoolID: school_code.GetString(), roles: new[] { "student" });
+                        var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id.GetString(), name.GetString(), picture.GetString(), _option.JwtSecretKey, Website: "IES", scope: Constant.ScopeStudent, schoolID: school_code.GetString(), roles: new[] { "student" });
                         //其他訊息
                         dynamic school = new ExpandoObject();
                         //回傳

+ 1 - 1
TEAMModelOS/Controllers/School/TmdUserController.cs

@@ -133,7 +133,7 @@ namespace TEAMModelOS.Controllers
                     }
                 }
                 //換取AuthToken,提供給前端
-                var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey,   scope: Constant.ScopeTmdUser, roles: new[] { "student" });
+                var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", scope: Constant.ScopeTmdUser, roles: new[] { "student" });
                 if (!string.IsNullOrEmpty(defaultschool)) { 
 
                 }

+ 2 - 1
TEAMModelOS/Controllers/Teacher/InitController.cs

@@ -201,6 +201,7 @@ namespace TEAMModelOS.Controllers
                                     var location = _option.Location;
                                     await _notificationService.SendNotification(clientID, clientSecret, location, url, notification);
                                 }
+                                _ = _azureStorage.SaveLog("transfer-admin-role", new { request,userid,name,school,schoolName=schoolBase.name, targetTecher=_targetTecher, }.ToJsonString(),bizId:$"{userid}-{schoolBase.id}-{_targetTecher}", httpContext: HttpContext, dingDing: _dingDing, scope: "school");
                                 return Ok(new { status = 1 });
                             }
                             else
@@ -425,7 +426,7 @@ namespace TEAMModelOS.Controllers
                     roles.Add("area");
                 }
                 //TODO JJ,更新Token时,在取得学校资讯时,没有传入schoolId
-                var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, scope: Constant.ScopeTeacher, schoolID: school_code.ToString(), standard: school_base.standard, roles: roles.ToArray(), permissions: permissions.ToArray());
+                var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", scope: Constant.ScopeTeacher, schoolID: school_code.ToString(), standard: school_base.standard, roles: roles.ToArray(), permissions: permissions.ToArray());
 
                 //取得班级
                 List<object> school_classes = new List<object>();

+ 15 - 0
TEAMModelOS/Controllers/XTest/TestController.cs

@@ -23,6 +23,7 @@ using System.Text;
 using System.Text.Json;
 using System.Text.RegularExpressions;
 using System.Threading.Tasks;
+using TEAMModelOS.Filter;
 using TEAMModelOS.Models;
 using TEAMModelOS.SDK;
 using TEAMModelOS.SDK.DI;
@@ -616,6 +617,20 @@ namespace TEAMModelOS.Controllers
             List<ScTeacher> teachers = await _azureStorage.FindListByDict<ScTeacher>(new Dictionary<string, object> { { "PartitionKey", "ScTeacher" }, { "areaId", $"{areaId}" } });
             return Ok(teachers.Select(x =>new  {x.areaId,x.PXID,x.TID,x.TeacherName,x.tmdid,x.SchoolName,x.DisName }));
         }
+
+        /// 删除
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [AuthToken(Roles = "admin,teacher")]
+        [HttpPost("get-save-log")]
+        public async Task<IActionResult> SaveLog(JsonElement request)
+        {
+            School school = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>("hbcn", new PartitionKey("Base"));
+            _=  _azureStorage.SaveLog("find-school", school.ToJsonString(),httpContext:HttpContext,dingDing:_dingDing,scope:"school");
+            return Ok(school);
+        }
     }
 
 }

+ 3 - 1
TEAMModelOS/Filter/AuthTokenAttribute.cs

@@ -38,7 +38,7 @@ namespace TEAMModelOS.Filter
             public void OnResourceExecuting(ResourceExecutingContext context)
             {
                 bool pass = false;
-                string id = string.Empty, name = string.Empty, picture = string.Empty, school = string.Empty, standard = string.Empty,scope=string.Empty;
+                string id = string.Empty, name = string.Empty, picture = string.Empty, school = string.Empty, standard = string.Empty,scope=string.Empty,  website=string.Empty;
                 List<string> _role = new List<string>();
                 var authtoken = context.HttpContext.GetXAuth("AuthToken");
                 if (!string.IsNullOrWhiteSpace(authtoken) && JwtAuthExtension.ValidateAuthToken(authtoken, _option.JwtSecretKey))
@@ -50,6 +50,7 @@ namespace TEAMModelOS.Filter
                     picture = jwt.Claims.FirstOrDefault(claim => claim.Type.Equals("picture"))?.Value;
                     standard = jwt.Claims.FirstOrDefault(claim => claim.Type.Equals("standard"))?.Value;
                     scope = jwt.Claims.FirstOrDefault(claim => claim.Type.Equals("scope"))?.Value;
+                    website = jwt.Claims.FirstOrDefault(claim => claim.Type.Equals("website"))?.Value;
                     if (!string.IsNullOrWhiteSpace(_roles))
                     {
                         var roles = jwt.Claims.Where(c => c.Type.Equals("roles"));
@@ -86,6 +87,7 @@ namespace TEAMModelOS.Filter
                     context.HttpContext.Items.Add("Standard", standard);
                     context.HttpContext.Items.Add("Roles", _role);
                     context.HttpContext.Items.Add("Scope", scope);
+                    context.HttpContext.Items.Add("Website", website);
                 }
                 else
                     context.Result = new BadRequestResult();

+ 1 - 2
TEAMModelOS/Services/Common/TeacherService.cs

@@ -252,12 +252,11 @@ namespace TEAMModelOS.Services
                 }
             }
             //換取AuthToken,提供給前端
-            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, scope: Constant.ScopeTeacher, standard: areaa != null ? areaa.standard : "", roles: roles.ToArray());
+            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", scope: Constant.ScopeTeacher, standard: areaa != null ? areaa.standard : "", roles: roles.ToArray());
             //取得Teacher Blob 容器位置及SAS 
             await _azureStorage.GetBlobContainerClient(id).CreateIfNotExistsAsync(PublicAccessType.None); //嘗試創建Teacher私有容器,如存在則不做任何事,保障容器一定存在
             var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(id, BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Delete);
             var (osblob_uri, osblob_sas) = roles.Contains("area") ? _azureStorage.GetBlobContainerSAS("teammodelos", BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Delete) : _azureStorage.GetBlobContainerSAS("teammodelos", BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List);
-
             return new TeacherInfo {
                 auth_token = auth_token,
                 blob_uri = blob_uri,

+ 3 - 3
TEAMModelOS/TEAMModelOS.csproj

@@ -38,9 +38,9 @@
     <SpaRoot>ClientApp\</SpaRoot>
     <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
     <UserSecretsId>078b5d89-7d90-4f6a-88fc-7d96025990a8</UserSecretsId>
-    <Version>5.2201.11</Version>
-    <AssemblyVersion>5.2201.11.1</AssemblyVersion>
-    <FileVersion>5.2201.11.1</FileVersion>
+    <Version>5.2201.14</Version>
+    <AssemblyVersion>5.2201.14.1</AssemblyVersion>
+    <FileVersion>5.2201.14.1</FileVersion>
     <Description>TEAMModelOS(IES5)版本更新。</Description>
     <PackageReleaseNotes>版本说明</PackageReleaseNotes>
   </PropertyGroup>