Selaa lähdekoodia

Merge branch 'TPE/develop' into develop3.0

osbert 4 vuotta sitten
vanhempi
commit
1675253754
35 muutettua tiedostoa jossa 4577 lisäystä ja 2211 poistoa
  1. 4 4
      TEAMModelOS.SDK/Models/Cosmos/School/SchoolProduct.cs
  2. 3 0
      TEAMModelOS/ClientApp/src/api/index.js
  3. 12 1
      TEAMModelOS/ClientApp/src/api/schoolSetting.js
  4. 25 0
      TEAMModelOS/ClientApp/src/api/spaceAuth.js
  5. 63 45
      TEAMModelOS/ClientApp/src/api/stuAccount.js
  6. 1 1
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  7. 7 1
      TEAMModelOS/ClientApp/src/store/index.js
  8. 233 0
      TEAMModelOS/ClientApp/src/store/module/spaceAuth.js
  9. 65 0
      TEAMModelOS/ClientApp/src/store/module/studentAclassOneAuth.js
  10. 38 1
      TEAMModelOS/ClientApp/src/store/module/user.js
  11. 1 0
      TEAMModelOS/ClientApp/src/view/schoolmgmt/ClassroomSetting/ClassroomSetting.less
  12. 105 97
      TEAMModelOS/ClientApp/src/view/schoolmgmt/ClassroomSetting/ClassroomSetting.vue
  13. 69 53
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/AclassOneChart.vue
  14. 8 3
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/HiteachAuthList.vue
  15. 2 2
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/ServiceList.less
  16. 93 150
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/ServiceList.vue
  17. 21 17
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceChart.vue
  18. 7 0
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceStatus.less
  19. 28 13
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceStatus.vue
  20. 1 0
      TEAMModelOS/ClientApp/src/view/student-account/AclassOneAuth.less
  21. 271 425
      TEAMModelOS/ClientApp/src/view/student-account/AclassOneAuth.vue
  22. 1 1
      TEAMModelOS/ClientApp/src/view/student-account/AddStudent.vue
  23. 132 159
      TEAMModelOS/ClientApp/src/view/student-account/AuthNumChart.vue
  24. 364 396
      TEAMModelOS/ClientApp/src/view/student-account/Index.vue
  25. 29 0
      TEAMModelOS/ClientApp/src/view/teachermgmt/components/userList/Index.less
  26. 1242 775
      TEAMModelOS/ClientApp/src/view/teachermgmt/components/userList/Index.vue
  27. 273 0
      TEAMModelOS/ClientApp/src/view/teachermgmt/components/userList/SubComponent/SpaceAuth.less
  28. 453 0
      TEAMModelOS/ClientApp/src/view/teachermgmt/components/userList/SubComponent/SpaceAuth.vue
  29. 237 0
      TEAMModelOS/ClientApp/src/view/teachermgmt/components/userList/SubComponent/SpaceStatusChart.vue
  30. 44 14
      TEAMModelOS/Controllers/Client/HiTeachController.cs
  31. 117 1
      TEAMModelOS/Controllers/School/ClassRoomController.cs
  32. 6 4
      TEAMModelOS/Controllers/School/CourseController.cs
  33. 619 46
      TEAMModelOS/Controllers/School/SchoolController.cs
  34. 1 1
      TEAMModelOS/Controllers/School/SchoolTeacherController.cs
  35. 2 1
      TEAMModelOS/appsettings.Development.json

+ 4 - 4
TEAMModelOS.SDK/Models/Cosmos/School/SchoolProduct.cs

@@ -24,10 +24,10 @@ namespace TEAMModelOS.SDK.Models
 
     public class Aclassone
     {
-        public List<string> ids { get; set; }
+        public List<string> ids { get; set; } //固定分配的學生ID
+        public List<string> outids { get; set; } //過期被回收的學生ID(無法使用,待數量購足後回復移至ids)
         public int total { get; set; }
         public int used { get; set; }
-        public int less { get; set; }
     }
 
     public class SerialInfoBase
@@ -138,8 +138,8 @@ namespace TEAMModelOS.SDK.Models
     }
     public class ServiceProductAclassoneResult : ServiceProductResult
     {
-        public int used { get; set; }
-        public int less { get; set; }
+        public int staUsed { get; set; }
+        public int dynUsed { get; set; }
     }
     public class ServiceProductAuth
     {

+ 3 - 0
TEAMModelOS/ClientApp/src/api/index.js

@@ -25,6 +25,7 @@ import regist from './regist'
 import forgetPW from './forgetPW'
 import classroom from './classroom'
 import serviceDriveAuth from './serviceDriveAuth'
+import spaceAuth from './spaceAuth'
 
 export default {
     accessToken,
@@ -51,6 +52,8 @@ export default {
     forgetPW,
     classroom,
     serviceDriveAuth,
+    spaceAuth,
+
     // 获取登录跳转链接
     getLoginLink: function (data) {
         return post('api/login/login', data)

+ 12 - 1
TEAMModelOS/ClientApp/src/api/schoolSetting.js

@@ -59,8 +59,19 @@ export default {
     upsertGroup: function (data) {
         return post('/school/classroom/upsert-group', data)
     },
+
+    // Hiteach 與教室連接
+    hiteachLink: function (data) {
+        return post('/school/classroom/hiteach-link', data)
+    },
+
+    // 指定教室切斷 Hiteach 連接
+    hiteachUnlinkByClassId: function (data) {
+        return post('/school/classroom/hiteach-unlink-classId', data)
+    },
+
     //根据私有班级id集合查询班级详细信息
     getClassByIds: function (data) {
         return post('/school/classroom/name', data)
-    },
+    }
 }

+ 25 - 0
TEAMModelOS/ClientApp/src/api/spaceAuth.js

@@ -0,0 +1,25 @@
+import { post } from '@/api/http'
+
+export default {
+  //設定老師空間API,有三種動作如註解
+  teacherSpace: function(data, action, teachers) {
+    console.log('進到API中')
+    //console.log(data, action, teachers)
+    return post('/school/init/teacher-space', {
+      school_code: 'hbcn',
+      action: 'baseSpace', // baseSpace: 目前教師可設定的總空間,
+      // upd: 修改教師雲端空間
+      // retract: 收回全部的教師空間
+      teachers: [
+        {
+          id: '1608805222',
+          size: 4
+        },
+        {
+          id: '1608090194',
+          size: 4
+        }
+      ]
+    })
+  }
+}

+ 63 - 45
TEAMModelOS/ClientApp/src/api/stuAccount.js

@@ -1,49 +1,67 @@
 import { fetch, post } from '@/api/http'
 
 export default {
-    saveStudent: function(schoolId, data) {
-        let format = {
-            "grant_type": "create",
-            "schoolId": schoolId,
-            "students": []
-        }
-        format.students.push(...data)
-        return post('/student/student-manage', format)
-    },
-    //��ѯѧ��
-    findStudent: function(schoolId) {
-        let format = {
-            "grant_type": "read",
-            "schoolId": schoolId
-        }
-        return post('/student/student-manage', format)
-    },
-    saveAllStudent: function(schoolId, data) {
-        let format = {
-            "grant_type": "update",
-            "schoolId": schoolId,
-            "students": []
-        }
-        format.students.push(...data)
-        return post('/student/student-manage', format)
-    },
-    /**
-     * 刪除學生
-     * @param {any} data
-     */
-    deleteStudent: function(schoolId, data) {
-        let format = {
-            "grant_type": "delete",
-            "schoolId": schoolId,
-            "students": []
-        }
-        format.students.push(...data)
-        return post('/student/student-manage', format)
-    },
-    updateStudent: function(data) {
-        return post('/api/Student/upsert', data)
-    },
-    updateAllStudent: function(data) {
-        return post('/api/Student/upsertAll', data)
-    },
+  saveStudent: function(schoolId, data) {
+    let format = {
+      grant_type: 'create',
+      schoolId: schoolId,
+      students: []
+    }
+    format.students.push(...data)
+    return post('/student/student-manage', format)
+  },
+  //��ѯѧ��
+  findStudent: function(schoolId) {
+    let format = {
+      grant_type: 'read',
+      schoolId: schoolId
+    }
+    return post('/student/student-manage', format)
+  },
+  saveAllStudent: function(schoolId, data) {
+    let format = {
+      grant_type: 'update',
+      schoolId: schoolId,
+      students: []
+    }
+    format.students.push(...data)
+    return post('/student/student-manage', format)
+  },
+  /**
+   * 刪除學生
+   * @param {any} data
+   */
+  deleteStudent: function(schoolId, data) {
+    let format = {
+      grant_type: 'delete',
+      schoolId: schoolId,
+      students: []
+    }
+    format.students.push(...data)
+    return post('/student/student-manage', format)
+  },
+  updateStudent: function(data) {
+    return post('/api/Student/upsert', data)
+  },
+  updateAllStudent: function(data) {
+    return post('/api/Student/upsertAll', data)
+  },
+  //取得AClassOne數目API
+  getSchoolAclassOne: function(data) {
+    return post('/school/init/get-school-aclassone', { school_code: data })
+  },
+  //設定固定AClassOne學生ID API
+  setSchoolAclassOneState: function(data, studentIds) {
+    return post('/school/init/set-school-aclasson-sta', {
+      school_code: data,
+      student_ids: studentIds //回傳預設定學生帳號的陣列,ex:["ID0001","ID0002","ID0003"]
+    })
+  },
+  //回收AClassOne學生ID API
+  recallSchoolAclassOne: function(data) {
+    //※mode: 非必須,sta:回收固定  dync:回收動態
+    return post('/school/init/recall-school-aclasson', {
+      school_code: data
+    })
+  }
 }

+ 1 - 1
TEAMModelOS/ClientApp/src/common/BaseLayout.vue

@@ -115,7 +115,7 @@
                                 icon: 'iconfont icon-class-mgt',
                                 name: '教室管理',
                                 router: '/home/classroom',
-                                tag: '*',
+                                tag: '',
                                 role: 'admin',
                                 permission: 'classroom-upd|classroom-read'
                             },

+ 7 - 1
TEAMModelOS/ClientApp/src/store/index.js

@@ -13,6 +13,10 @@ import studentWeb from './module/studentWeb'
 import scboard from './module/scboard'
 import serviceDriveAuth from './module/serviceDriveAuth'
 import GLOBAL from '@/static/Global.js'
+import spaceAuth from './module/spaceAuth'
+import studentAclassOneAuth from './module/studentAclassOneAuth'
+
+
 Vue.use(Vuex)
 
 // TYPES
@@ -75,6 +79,8 @@ export default new Vuex.Store({
         teachers,
         studentWeb,
         scboard,
-        serviceDriveAuth
+        serviceDriveAuth,
+        spaceAuth,
+        studentAclassOneAuth
     }
 })

+ 233 - 0
TEAMModelOS/ClientApp/src/store/module/spaceAuth.js

@@ -0,0 +1,233 @@
+import apiTools from '@/api'
+export default {
+  namespaced: true,
+  state: {
+    teacherSpace: [],
+    schoolUsedSpace: 300, //學校其他已經使用的空間
+    usedSpace: 0, //目前已使用空間,
+    currentAssignedSpace: 0, //已分配給教師空間
+    originalSpace: 2500,
+    maxFixedAssignedValue: 0, //資料進來,可分的最大值
+    currentPeopleNum: 0, //目前套用分配的人數
+    currentSelectedTeacherId: [], //目前所選存放的老師的id,勾選給空間用
+
+    pieNumData: [
+      { value: 0, name: '學校已使用空間' },
+      { value: 0, name: '已分配給教師空間' },
+      { value: 0, name: '剩餘空間' }
+    ]
+  },
+  getters: {
+    getOriginalSpace(state) {
+      return state.originalSpace
+    },
+    getSchoolUsedSpace(state) {
+      return state.schoolUsedSpace
+    },
+    getCurrentSelectedTeacherId(state) {
+      return state.currentSelectedTeacherId
+    },
+    getPieNumData(state) {
+      return state.pieNumData
+    },
+    getMockTeacherSpace(state) {
+      return state.teacherSpace
+    },
+    getMockTeacherSpaceOriginal(state) {
+      return state.teacherSpaceOringinal
+    },
+    getMockCurrentAssignedSpace(state) {
+      return state.currentAssignedSpace
+    },
+    getMaxFixedAssignedValue(state) {
+      return state.maxFixedAssignedValue
+    }
+  },
+  mutations: {
+    setCurrentSelectedTeacherId(state, data) {
+      state.currentSelectedTeacherId = data
+      console.log(data)
+    },
+    setPieNumDataZero(state) {
+      state.pieNumData = [
+        {
+          value: state.schoolUsedSpace,
+          name: '學校已使用空間'
+        },
+        {
+          value: 0,
+          name: '已分配給教師空間'
+        },
+        {
+          value: state.originalSpace - state.schoolUsedSpace,
+
+          name: '剩餘空間'
+        }
+      ]
+    },
+    setPieNumDataMultiAssign(state) {
+      state.pieNumData[1].value = state.currentAssignedSpace
+      state.pieNumData[2].value = state.originalSpace - state.schoolUsedSpace - state.currentAssignedSpace
+    },
+    setPieNumData(state) {
+      //假的餅圖資料
+      state.usedSpace = 0 //每次加的時候都先重置
+      //一開始先加總DB初值
+      state.teacherSpace.forEach(element => {
+        state.usedSpace += parseInt(element.spaceInitValue)
+      })
+
+      console.log(state.usedSpace)
+
+      state.pieNumData = [
+        {
+          value: state.schoolUsedSpace,
+          name: '學校已使用空間'
+        },
+        {
+          value: parseInt(state.usedSpace) + parseInt(state.currentAssignedSpace),
+          name: '已分配給教師空間'
+        },
+        {
+          value: state.originalSpace - (parseInt(state.usedSpace) + parseInt(state.currentAssignedSpace)) - state.schoolUsedSpace,
+
+          name: '剩餘空間'
+        }
+      ]
+    },
+
+    setMockSpaceZero(state) {
+      state.teacherSpace.forEach(element => {
+        element.space = 0
+        element.spaceVariableValue = 0
+        element.spaceInitValue = 0
+      })
+      state.usedSpace = 0
+      state.currentAssignedSpace = 0
+    },
+    //固定容量分配給所有帳號
+    setMultiAssignCurrentVariableValue(state, data) {
+      state.usedSpace = 0
+      state.currentAssignedSpace = 0
+      state.currentAssignedSpace = data
+    },
+
+    //固定容量分配給當前勾選的帳號
+    setAssignSelectVariableValue(state, data) {
+      state.usedSpace = 0
+      state.currentAssignedSpace = 0
+      data.forEach(element => {
+        state.currentAssignedSpace += parseInt(element.space)
+      })
+      console.log(state.currentAssignedSpace)
+    },
+    //手動輸入分配
+    setCurrentVariableValue(state, data) {
+      state.currentAssignedSpace = 0
+      data.forEach(element => {
+        state.currentAssignedSpace += parseInt(element.spaceVariableValue)
+      })
+
+      console.log(state.currentAssignedSpace)
+    },
+    setMockTeacherSpace(state, data) {
+      console.log(data)
+      //塞入假資料
+
+      data.forEach(element => {
+        let spaceValue = Math.abs((Math.random() * 100).toFixed(0))
+
+        //模擬DB初值
+        Object.defineProperty(element, 'spaceInitValue', {
+          value: element.size,
+          writable: true
+        })
+        //模擬接收讓使用者修改時的暫存值,留意input 進來會從數字變字串
+        Object.defineProperty(element, 'space', {
+          value: element.size,
+          writable: true
+        })
+        //模擬存放各帳號目前變更的空間量
+        Object.defineProperty(element, 'spaceVariableValue', {
+          value: 0,
+          writable: true
+        })
+      })
+
+      state.teacherSpace = data
+      state.maxFixedAssignedValue = state.originalSpace - state.schoolUsedSpace
+      console.log(state.maxFixedAssignedValue)
+    },
+    setVuexResetAllSpace(state) {
+      state.teacherSpace = []
+      state.schoolUsedSpace = 300 //學校其他已經使用的空間
+      state.usedSpace = 0 //目前已使用空間,
+      state.currentAssignedSpace = 0 //已分配給教師空間
+      state.originalSpace = 2500
+      state.maxFixedAssignedValue = 0 //資料進來,可分的最大值
+      state.currentPeopleNum = 0 //目前套用分配的人數
+      state.currentSelectedTeacherId = [] //目前所選存放的老師的id,勾選給空間用
+      state.pieNumData = [
+        { value: 0, name: '學校已使用空間' },
+        { value: 0, name: '已分配給教師空間' },
+        { value: 0, name: '剩餘空間' }
+      ]
+    }
+  },
+  actions: {
+    setTeacherSpace(context, params) {
+     
+      let schoolCode = params.schoolCode
+      let teachers = params.teachers
+      let action='baseSpace'
+      apiTools.spaceAuth.teacherSpace(schoolCode,action,teachers).then(
+        (res)=>{
+          console.log(res)
+        },
+        (err) => {
+          reject({
+            code: 0,
+            data: [],
+           
+          });
+        }
+      )
+    }
+  }
+}
+
+/**
+getSchoolAclassOne(context, schoolCode) {
+    return new Promise((resolve, reject) => {
+      apiTools.stuAccount.getSchoolAclassOne(schoolCode).then(
+        (res) => {
+          context.commit("setSchoolAclassOne", res);
+          resolve({
+            code: 1,
+            message: "Get AclassOne數量 API  successful",
+          });
+        },
+        (err) => {
+          reject({
+            code: 0,
+            data: [],
+            message: "Get AclassOne數量 API error!",
+          });
+        }
+      );
+    });
+  },
+
+   setSchoolAclassOneState(context, params){
+      //console.log(params)
+      let schoolCode=params.schoolCode;
+      let studentIds=params.studentIds
+      apiTools.stuAccount.setSchoolAclassOneState(schoolCode, studentIds).then(
+        (res) => {
+         //console.log(res)
+         //資料回來刷新
+         context.commit("setSchoolAclassOne", res);
+        }
+      )
+    }
+*/

+ 65 - 0
TEAMModelOS/ClientApp/src/store/module/studentAclassOneAuth.js

@@ -0,0 +1,65 @@
+import apiTools from '@/api'
+export default {
+  namespaced: true,
+  state: {
+    firstOpen: true, //第一次打開控件,處理echart Canvas初始化寬度問題,延時生成
+    schoolAclassOneInfoNum: undefined
+  },
+  getters: {
+    getSchoolAclassOneInfoNum(state) {
+      return state.schoolAclassOneInfoNum
+    },
+    getIsFirstOpenAuth(state) {
+      return state.firstOpen
+    }
+  },
+  mutations: {
+    setSchoolAclassOne(state, data) {
+      state.schoolAclassOneInfoNum = data
+      //console.log(state.schoolAclassOneInfoNum);
+    },
+    setFirstOpenAuth(state, status) {
+      state.firstOpen = status
+      //console.log(state.firstOpen)
+    }
+  },
+  actions: {
+    getSchoolAclassOne(context, schoolCode) {
+      return new Promise((resolve, reject) => {
+        apiTools.stuAccount.getSchoolAclassOne(schoolCode).then(
+          res => {
+            context.commit('setSchoolAclassOne', res)
+            resolve({
+              code: 1,
+              message: 'Get AclassOne數量 API  successful'
+            })
+          },
+          err => {
+            reject({
+              code: 0,
+              data: [],
+              message: 'Get AclassOne數量 API error!'
+            })
+          }
+        )
+      })
+    },
+    setSchoolAclassOneState(context, params) {
+      //console.log(params)
+      let schoolCode = params.schoolCode
+      let studentIds = params.studentIds
+      apiTools.stuAccount.setSchoolAclassOneState(schoolCode, studentIds).then(res => {
+        //console.log(res)
+        //資料回來刷新
+        context.commit('setSchoolAclassOne', res)
+      })
+    },
+    recallSchoolAclassOne(context, schoolCode) {
+      apiTools.stuAccount.recallSchoolAclassOne(schoolCode).then(res => {
+        console.log(res)
+        //資料回來刷新
+        context.commit('setSchoolAclassOne', res)
+      })
+    }
+  }
+}

+ 38 - 1
TEAMModelOS/ClientApp/src/store/module/user.js

@@ -153,6 +153,7 @@ export default {
         getClasses: state => {
             return state.schoolProfile.school_classes
         }
+
     },
     mutations: {
         setSchoolUserList(state, data) {
@@ -226,6 +227,32 @@ export default {
         setSchoolCode(state, data) {
             state.schoolCode = data
         },
+        addClasses(state, data) {
+            let school_profile = localStorage.getItem('school_profile')
+            if(school_profile){
+                let schoolProfile = JSON.parse(decodeURIComponent(school_profile,"utf-8"))
+                schoolProfile.school_classes.unshift(data)
+                state.schoolProfile.school_classes = schoolProfile.school_classes
+                localStorage.setItem('school_profile', encodeURIComponent(JSON.stringify(schoolProfile), "utf-8"))
+            }
+        },
+        delClasses(state, id) {
+            let school_profile = localStorage.getItem('school_profile')
+            if(school_profile){
+                let schoolProfile = JSON.parse(decodeURIComponent(school_profile,"utf-8"))
+                let orgIndex = -1;
+
+                for (let i in schoolProfile.school_classes) {
+                    if (schoolProfile.school_classes[i].id == id) {
+                        orgIndex = i
+                        break
+                    }
+                }
+                schoolProfile.school_classes.splice(orgIndex, 1) // 刪掉指定教室
+                state.schoolProfile.school_classes = schoolProfile.school_classes
+                localStorage.setItem('school_profile', encodeURIComponent(JSON.stringify(schoolProfile), "utf-8"))
+            }
+        },
     },
     actions: {
 		// 获取学校基本信息
@@ -690,6 +717,16 @@ export default {
                     )
                 }
             )
-        }
+        },
+        
+        // 新增教室
+        addSchoolClasses(context, data) {
+            context.commit('addClasses', data)
+        },
+
+        // 刪除教室
+        delSchoolClasses(context, id) {
+            context.commit('delClasses', id)
+        },
     }
 }

+ 1 - 0
TEAMModelOS/ClientApp/src/view/schoolmgmt/ClassroomSetting/ClassroomSetting.less

@@ -280,6 +280,7 @@
 .hiteach-code-wrap {
     width: 65%;
     height: 100%;
+    position: relative;
     &-header {
         width: ~"calc(100% - 30px)";
         margin-left: 20px;

+ 105 - 97
TEAMModelOS/ClientApp/src/view/schoolmgmt/ClassroomSetting/ClassroomSetting.vue

@@ -144,14 +144,12 @@
                                         </Option>
                                     </Select>
                                 </FormItem>
-                                <FormItem>
-                                    <!-- <Button @click="testSave()">TEST</Button> -->
-                                    </FormItem>
                             </Form>
                         </vuescroll>
                     </div>
                     <!--HiTeach序列号列表-->
-                    <div class="hiteach-code-wrap">
+                    <div class="hiteach-code-wrap" >
+                        <Loading v-show="hiteachListLoading"></Loading>
                         <div class="hiteach-code-wrap-header">
                             <p>
                                 <!-- {{$t('schoolBaseInfo.hiteachList')}} -->
@@ -214,11 +212,12 @@
                                     </div>
                                     <div v-if="item.subOpen" id="hiteach-1">
                                         <template v-if="item.deviceBound.length >0">
-                                            <div v-for="(dbItem, dbIndex) in item.deviceBound" :key="dbIndex" class="hiteach-collapse-sub gradient" :class="{'linked': dbItem.classId != '' && dbItem.classId != null && classroomListShow[curClassIndex].id != dbItem.classId}" @click="pushCheckbox(dbItem, (dbItem.classId && classroomListShow[curClassIndex].id != dbItem.classId), classroomListShow[curClassIndex].id, dbIndex)">
+                                            <!-- <div v-for="(dbItem, dbIndex) in item.deviceBound" :key="dbIndex" class="hiteach-collapse-sub gradient" :class="{'linked': dbItem.classId != '' && dbItem.classId != null && classroomListShow[curClassIndex].id != dbItem.classId}"> -->
+                                            <div v-for="(dbItem, dbIndex) in item.deviceBound" :key="dbIndex" class="hiteach-collapse-sub gradient" :class="{'linked': dbItem.classId != '' && dbItem.classId != null && classroomListShow[curClassIndex].id != dbItem.classId}" >
                                                 <!-- 被關聯 disabled -->
-                                                <Checkbox :id="'checkbox-' + (dbItem.uuid + dbItem.uuid2)" @on-change="watchUpdate" style="margin-right: 70px;" v-model="dbItem.hiteachLink" :disabled="dbItem.classId != '' && dbItem.classId != null && classroomListShow[curClassIndex].id != dbItem.classId" ></Checkbox>
+                                                <Checkbox :id="'checkbox-' + (dbItem.uuid + dbItem.uuid2)" @on-change="whatUpdHiLink(dbItem, classroomListShow[curClassIndex].id, item.id)" style="margin-right: 70px;" v-model="dbItem.hiteachLink" :disabled="dbItem.classId != '' && dbItem.classId != null && classroomListShow[curClassIndex].id != dbItem.classId" ></Checkbox>
 
-                                                <div class="hiteach-collapse-sub-detail" >
+                                                <div class="hiteach-collapse-sub-detail"  @click="pushCheckbox(dbItem, (dbItem.classId && classroomListShow[curClassIndex].id != dbItem.classId))">
                                                     <ul>
                                                         <li>
                                                             <h3>{{ dbItem.pcname }}</h3>
@@ -392,6 +391,7 @@
                 currentTabIndex: 0,
                 isListLoading: false,
                 isSaveLoading: false,
+                hiteachListLoading: true,
                 addCode: false,
                 isInit: true,
                 updated: false,
@@ -543,7 +543,7 @@
         computed: {
             ...mapGetters({
                 periods: 'user/getPeriods', // 學制s
-                aprules: 'schoolBaseInfo/getAprules',          
+                aprules: 'schoolBaseInfo/getAprules',
             }),            
             filterPeriodName: function(){
                 let data = this.periods
@@ -1005,16 +1005,60 @@
                                         this.$Message.error(res.v)
                                     } else {
 
-                                        // 修改Hiteach
-                                        
+                                        // Hiteach 與教室綁定
+                                        this.$api.schoolSetting.hiteachLink({
+                                            school_code: this.$store.state.userInfo.schoolCode,
+                                            linkList: this.updHiteachLink
+                                        }).then( hiteachLinkRes =>{
+                                            
+                                            if(hiteachLinkRes.error == 0){
+                                                let orgHiData = JSON.parse(this.orgHiteachData)
 
+                                                this.updHiteachLink.forEach(item => {
+                                                    let tempIndex;
+                                                    let temp = orgHiData.find( (hiItem, hiIndex) => {
+                                                        tempIndex = hiIndex
+                                                        return hiItem.id == item.id
+                                                    })
+                                                    if(temp.deviceBound != null && Array.isArray(temp.deviceBound) && temp.deviceBound.length > 0){
+                                                        temp.deviceBound.forEach( (dbItem, dbIndex, dbArray) => {
+                                                            if( (dbItem.uuid + dbItem.uuid2) == (item.uuid + item.uuid2)){
+                                                                dbItem.classId = item.classId
 
-                                        this.$Message.success(this.$t('schoolBaseInfo.csTips3'))                                        
-                                        this.updated = false
-                                        if (option == 'insert') {
-                                            this.classroomListShow[this.curClassIndex].code = 'Class-'+ this.classroomListShow[this.curClassIndex].code
-                                            this.classroomList.unshift(this.classroomListShow[this.curClassIndex])
-                                        }
+                                                                if(item.classId != null){
+                                                                    dbItem.hiteachLink = true
+                                                                } else {
+                                                                    dbItem.hiteachLink = false
+                                                                }
+                                                            }
+                                                        })
+
+                                                        orgHiData[tempIndex] = temp
+                                                    }
+                                                })
+                                                
+                                                // 原本API 內容
+                                                this.$Message.success(this.$t('schoolBaseInfo.csTips3'))                                        
+                                                this.updated = false
+                                                this.updHiteachLink = []
+                                                if (option == 'insert') {
+                                                    this.classroomListShow[this.curClassIndex].code = 'Class-'+ this.classroomListShow[this.curClassIndex].code
+                                                    this.classroomList.unshift(this.classroomListShow[this.curClassIndex])
+                                                    this.$store.dispatch('user/addSchoolClasses', this.classroomListShow[this.curClassIndex]);
+                                                }
+
+                                                // 新增加
+                                                this.hiteachData = orgHiData
+                                                this.orgHiteachData = JSON.stringify(orgHiData)
+                                            }
+                                        },err => {
+                                            this.$Message.error('Hiteach 綁定失敗!')
+                                        }).finally(
+                                            () => {
+                                                this.isSaveLoading = false
+                                                this.isListLoading = false
+                                            }
+                                        )
                                     }
                                     
                                 } else {
@@ -1024,7 +1068,8 @@
                             err => {
                                 this.$Message.error('API error!')
                             }
-                        ).finally(
+                        )
+                        .finally(
                             () => {
                                 this.isSaveLoading = false
                                 this.isListLoading = false
@@ -1076,7 +1121,6 @@
                     this.$Message.warning('您暂无次操作权限!')
                 }
             },
-
             delClassroom(index) {
                 this.isListLoading = true
                 if (this.classroomListShow[index].option !== 'insert') {
@@ -1098,28 +1142,20 @@
                                         break
                                     }
                                 }
-                                // 刪掉指定的classId
-                                let orgHiData = JSON.parse(this.orgHiteachData)
-                                orgHiData.forEach( item => {
-                                    if(Array.isArray(item.deviceBound) && item.deviceBound.length > 0){
-                                        item.deviceBound.forEach(db => {
-                                            if(db.classId == this.classroomListShow[index].id) {
-                                                db.classId = null
-                                                db.hiteachLink = false
-                                            }
-                                        })
-                                    }
+
+                                this.$api.schoolSetting.hiteachUnlinkByClassId({
+                                    classId: this.classroomListShow[index].id,
+                                    school_code: this.$store.state.userInfo.schoolCode
                                 })
-                                
-                                this.hiteachData = orgHiData
-                                this.orgHiteachData = JSON.stringify(orgHiData)
+                                this.unlinkHiteach(this.classroomListShow[index].id) // 刪掉指定的classId                                
+                                this.$store.dispatch('user/delSchoolClasses', this.classroomListShow[index].id);
 
                                 this.classroomList.splice(originIndex, 1)
                                 this.classroomListShow.splice(index, 1)
 
-
                                 this.$Message.success(this.$t('schoolBaseInfo.csTips7'))
                                 this.updated = false
+                                this.updHiteachLink = []
                             }
                         },
                         (err) => {
@@ -1134,10 +1170,11 @@
                         this.updateBefore = JSON.stringify(this.classroomListShow[this.curClassIndex])
                     }
                     this.classroomListShow.splice(index, 1)
+                    this.unlinkHiteach(this.classroomListShow[index].id)// 刪掉指定的classId
                     this.isListLoading = false
                     this.updated = false
+                    this.updHiteachLink = []
                 }
-
             },
             uploadSchoolPlan(res, file) {
                 this.$Message.success(this.$t('schoolBaseInfo.csTips5'))
@@ -1154,6 +1191,7 @@
                                     this.delClassroom(this.curClassIndex)
                                 } else {
                                     this.updated = false
+                                    this.updHiteachLink = []
                                     this.$set(this.classroomListShow, this.curClassIndex, JSON.parse(this.updateBefore))
                                     this.curClassIndex = index
                                     this.updateBefore = JSON.stringify(this.classroomListShow[this.curClassIndex])
@@ -1332,6 +1370,7 @@
                 let data = []
                 await this.$api.classroom.GetHiteachList(this.$store.state.user.schoolCode).then(
                     (res) => {
+                        this.hiteachListLoading = false;
                         data = res.serial
                     },
                     (err) => {
@@ -1362,84 +1401,53 @@
                 }
                 return style
             },
-            pushCheckbox(item, disabled, classId, index){
+            pushCheckbox(item, disabled){
                 if(!disabled){
                     document.getElementById('checkbox-' + item.uuid + item.uuid2).click()
-
-                    // let arrayIndex = null
-                    // this.updHiteachLink.forEach( (item, index) => {
-                    //     if(item.deviceId == item.deviceId){
-                    //         arrayIndex = index
-                    //     }
-                    // })
-
-                    // if(arrayIndex != null){
-                    //     this.updHiteachLink.splice(arrayIndex, 1)
-                    // }
-
-                    // this.updHiteachLink.push(
-                    //     {
-                    //         uuid: item.uuid,
-                    //         uuid2: item.uuid2,
-                    //         classId: item.hiteachLink ? classId : null,
-                    //         deviceId: item.deviceId
-                    //     }
-                    // )
-                    
-                    // console.log(this.updHiteachLink)
                 }
             },
-            testSave(){ // 保留這個Fuction 等API來再用
+            whatUpdHiLink: function(item, classId, id) {
+                if(item){
+                    this.updated = true
 
-                // 準備saveData 給API
-                let saveData = {
-                    classId : this.classroomListShow[this.curClassIndex].id, // 被綁定的教室id
-                    linkList: []
-                }
+                    let arrayIndex = null
+                    this.updHiteachLink.forEach( (updItem, updIndex) => {
+                        // 利用相加字串來判斷
+                        if(updItem.uuid + updItem.uuid2 == item.uuid + item.uuid2){
+                            arrayIndex = updIndex
+                        }
+                    })
 
-                this.showHiteachData.forEach( item => {
-                    if(Array.isArray(item.deviceBound) && item.deviceBound.length > 0){
-                        item.deviceBound.forEach(db => {
-                            if(db.classId == null && db.hiteachLink) {
-                                saveData.linkList.push({
-                                    uuid: db.uuid,
-                                    uuid2: db.uuid2,
-                                    id: item.id, // 產品id
-                                    deviceId: db.deviceId // 裝置id
-                                })
-                            }
-                        })
+                    if(arrayIndex != null){
+                        this.updHiteachLink.splice(arrayIndex, 1)
                     }
-                })
-
-                console.log(saveData, 'dsfdsfsdfsdfsdf')
-
 
-
-                // API callback 後
+                    this.updHiteachLink.push(
+                        {
+                            id: id,
+                            uuid: item.uuid,
+                            uuid2: item.uuid2,
+                            classId: item.hiteachLink ? classId : null,
+                            deviceId: item.deviceId
+                        }
+                    )
+                }
+            },
+            unlinkHiteach: function(classId){
+                // 刪掉指定的classId
                 let orgHiData = JSON.parse(this.orgHiteachData)
-
-                saveData.linkList.forEach(item => {
-                    let tempIndex;
-                    let temp = orgHiData.find( (hiItem, hiIndex) => {
-                        tempIndex = hiIndex
-                        return hiItem.id == item.id
-                    })
-                    if(Array.isArray(temp.deviceBound) && temp.deviceBound.length > 0){
-                        temp.deviceBound.forEach( (dbItem, dbIndex, dbArray) => {
-                            if(dbItem.deviceId == item.deviceId){
-                                dbItem.classId = saveData.classId
-                                dbItem.hiteachLink = true
+                orgHiData.forEach( item => {
+                    if(Array.isArray(item.deviceBound) && item.deviceBound.length > 0){
+                        item.deviceBound.forEach(db => {
+                            if(db.classId == classId) {
+                                db.classId = null
+                                db.hiteachLink = false
                             }
                         })
-
-                        orgHiData[tempIndex] = temp
                     }
                 })
-
                 this.hiteachData = orgHiData
                 this.orgHiteachData = JSON.stringify(orgHiData)
-
             }
         },
         mounted() {

+ 69 - 53
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/AclassOneChart.vue

@@ -3,9 +3,10 @@
 </template>
 
 <script>
+import { mapGetters } from 'vuex'
 export default {
-  name: "AclassOneChart",
-  props:['less','used','avaliable'],
+  name: 'AclassOneChart',
+  props: ['less', 'used', 'avaliable'],
   data() {
     return {
       e: event,
@@ -13,95 +14,111 @@ export default {
 
       mockdata: {
         pieNumData: [
-          { value: this.used, name: "固定分配數" },
-          { value: this.less, name: "動態分配數" },
-          { value:this.avaliable-this.less-this.used, name: "剩餘分配數" },
-        ],
-      },
-    };
-  },
-  created() {
-    this.$emit("HiteachInfo", this.mockdata.pieNumData);
+          { value: this.used, name: '固定分配數' },
+          { value: this.less, name: '動態分配數' },
+          { value: this.avaliable - this.less - this.used, name: '剩餘分配數' }
+        ]
+      }
+    }
   },
   mounted() {
-    this.drawLine(this.mockdata.pieNumData);
+    this.drawLine(this.mockdata.pieNumData)
+  },
+  watch: {
+    realTimePieNumData: function(val) {
+      if (val !== 0) {
+        this.mockdata = {
+          pieNumData: [
+            { value: this.serviceList[0].staUsed, name: '固定分配數' },
+            { value: this.serviceList[0].dynUsed, name: '動態分配數' },
+            { value: this.serviceList[0].avaliable - this.serviceList[0].dynUsed - this.serviceList[0].staUsed, name: '剩餘分配數' }
+          ]
+        }
+        this.drawLine(this.mockdata.pieNumData)
+      }
+    }
+  },
+  computed: {
+    ...mapGetters({
+      serviceList: 'serviceDriveAuth/getServiceList'
+    }),
+    realTimePieNumData: function() {
+      if (this.serviceList !== undefined || this.serviceList !== null) {
+        return this.serviceList
+      } else return 0
+    }
   },
+
   methods: {
     drawLine(pieNumData) {
       //基于准备好的dom,初始化echarts实例
 
-      let myChart = this.$echarts.init(
-        document.getElementById("aclassOne-chart")
-      ); //绘制图表
+      let myChart = this.$echarts.init(document.getElementById('aclassOne-chart')) //绘制图表
       myChart.setOption({
         tooltip: {
-          trigger: "item",
-          formatter: "{b}: {c} ({d}%)",
+          trigger: 'item',
+          formatter: '{b}: {c} ({d}%)'
         },
         legend: {
           show: false,
-          orient: "horizontal",
-          icon: "circle",
+          orient: 'horizontal',
+          icon: 'circle',
 
           textStyle: {
-            color: "white",
+            color: 'white'
           },
-           
+
           left: 20,
-          bottom: 0,
+          bottom: 0
         },
-        color: ["#00f492", "#f862bb", "gray"],
+        color: ['#00f492', '#f862bb', 'gray'],
 
         series: [
           {
-            type: "pie",
-            radius: ["45%", "100%"],
-            top:20,
+            type: 'pie',
+            radius: ['45%', '100%'],
+            top: 20,
             right: 10,
-            left:0,
+            left: 0,
             bottom: 10,
             avoidLabelOverlap: true,
             label: {
               show: false,
-              position: "center",
-              formatter: "{percent|{d}% }\n {b}",
+              position: 'center',
+              formatter: '{percent|{d}% }\n {b}',
               lineHeight: 20,
 
               rich: {
                 percent: {
-                  color: "white",
+                  color: 'white',
                   fontSize: 20,
                   lineHeight: 30,
-                  padding: [0, 0, 0, 10],
-                },
-              },
+                  padding: [0, 0, 0, 10]
+                }
+              }
             },
             startAngle: 90,
             emphasis: {
               label: {
                 show: false,
-                fontSize: "18",
-                fontWeight: "bold",
-              },
+                fontSize: '18',
+                fontWeight: 'bold'
+              }
             },
             labelLine: {
-              show: false,
+              show: false
             },
             itemStyle: {
-              borderColor: "#2b2a2f",
-              borderWidth: 0,
+              borderColor: '#2b2a2f',
+              borderWidth: 0
             },
-            data: this.mockdata.pieNumData,
-          },
-        ],
-      });
-
-     
-
-     
-    },
-  },
-};
+            data: this.mockdata.pieNumData
+          }
+        ]
+      })
+    }
+  }
+}
 </script>
 
 <style>
@@ -109,7 +126,6 @@ export default {
   width: 120%;
   height: 70px;
   margin-top: -20px;
-   margin-left: -33%;
- 
+  margin-left: -33%;
 }
-</style>
+</style>

+ 8 - 3
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/HiteachAuthList.vue

@@ -160,10 +160,11 @@
                 :key="index"
                 class="more-app"
                 :class="{
-                  'bought-app': i.aprule.hasOwnProperty(item) == true,
+                  'bought-app': i.aprule!=null&&i.aprule.hasOwnProperty(item) == true,
                 }"
                 >{{ $t("schoolBaseInfo." + item) }}</span
               >
+             
             </p>
             <Row>
               <i-col :xs="24" :sm="24" :md="24" :lg="7">
@@ -212,10 +213,13 @@
             <p class="pc-title">
               {{ pc.pcname == null ? "未命名機台" : pc.pcname }}
             </p>
-            <p class="nomal-text">
+            <p class="nomal-text" v-if="pc.ip!==null">
               {{ pc.ip }} | {{ pc.uuid != null ? pc.uuid : pc.uuid2 }}
             </p>
-            <p class="nomal-text">
+             <p class="nomal-text" v-else>
+              {{ pc.uuid != null ? pc.uuid : pc.uuid2 }}
+            </p>
+            <p class="nomal-text" v-if="pc.os!==null">
               {{ pc.os }}&nbsp;{{ pc.cpu }}&nbsp;{{
                 (pc.ram / 1000).toFixed(1)
               }}G RAM
@@ -310,6 +314,7 @@ export default {
   },
   methods: {
     //先讓使用者確定是否解除授權
+   
     comfirmRemovePC(mac) {
       this.isOpenRemove = true;
       this.currentConfirmRemovePC = mac;

+ 2 - 2
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/ServiceList.less

@@ -140,7 +140,7 @@
       .time-bar{
         background-color:  #4a4a4d;
         border-radius: 5px;
-        width:95%;
+       
         height: 4px;
         top:8px;
         position: relative;
@@ -148,7 +148,7 @@
       }
       .remain-bar{
         border-radius: 5px;
-        width:90%;
+       
         height: 4px;
         top:-4px;
         position: relative;

+ 93 - 150
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/ServiceList.vue

@@ -1,85 +1,44 @@
 <template>
   <div class="service-list">
     <div class="service-listTitle listTitle-noMargin">服務授權列表</div>
-    <div
-      class="serviceList-wrap"
-      :style="{ height: windowHeight - 116 + 'px' }"
-    >
-      <div
-        class="service-item"
-        v-for="(item, index) in serviceList"
-        :key="index"
-      >
+    <div class="serviceList-wrap" :style="{ height: windowHeight - 116 + 'px' }">
+      <div class="service-item" v-for="(item, index) in serviceList" :key="index">
         <div class="detail-btn" @click="setIntroOpen(index)">
-          <Icon
-            v-show="serviceIntroIsOpen[index].isOpen == false"
-            type="ios-arrow-down"
-          />
-          <Icon
-            v-show="serviceIntroIsOpen[index].isOpen == true"
-            type="ios-arrow-up"
-          />
+          <Icon v-show="serviceIntroIsOpen[index].isOpen == false" type="ios-arrow-down" />
+          <Icon v-show="serviceIntroIsOpen[index].isOpen == true" type="ios-arrow-up" />
         </div>
         <div v-for="(proName, index) in serviceNameList" :key="index">
           <p class="service-name" v-if="item.prodCode == proName">
             {{ $t(`serviceDriveAuth['${proName}']`) }}
           </p>
         </div>
-         <p class="unbuy" v-if="item.hasOwnProperty('notbuy')==true">未購買</p>
+        <p class="unbuy" v-if="item.hasOwnProperty('notbuy') == true">未購買</p>
         <p class="isbuy" v-else>已購買</p>
-       
-        <p class="nomal-text" >
+
+        <p class="nomal-text">
           服務啟用 / 到期日:
-          <span class="strong-text" v-if="item.startDate != 0"
-            >{{ converTime(item.startDate) }} -
-            {{ converTime(item.endDate) }}</span
-          >
+          <span class="strong-text" v-if="item.startDate != 0">{{ converTime(item.startDate) }} - {{ converTime(item.endDate) }}</span>
           <span class="strong-text" v-if="item.startDate == 0">--</span>
         </p>
-        <div v-if="item.hasOwnProperty('notbuy')!=true">
+        <div v-if="item.hasOwnProperty('notbuy') != true">
           <Row class="time-bar-wrap">
-            <i-col :xs="24" :sm="24" :md="24" :lg="16">
+            <i-col :xs="24" :sm="24" :md="24" :lg="16" style="padding-right: 10px">
               <div class="time-bar" v-if="item.startDate != 0"></div>
-
               <div
                 class="remain-bar"
                 v-if="item.startDate != 0"
                 :style="{
-                  width:
-                    (
-                      (dateRemain(today(), converTime(item.endDate)) /
-                        dateRemain(
-                          converTime(item.startDate),
-                          converTime(item.endDate)
-                        )) *
-                      100
-                    ).toFixed(2) + '%',
-                  'background-color': colorPercent(
-                    converTime(item.startDate),
-                    converTime(item.endDate)
-                  )
-                    ? '#ad3435'
-                    : '#1cc0f3',
+                  width: (dateRemain(today(), converTime(item.endDate)) / dateRemain(converTime(item.startDate), converTime(item.endDate))).toFixed(2) * 100 + '%',
+                  'background-color': colorPercent(converTime(item.startDate), converTime(item.endDate)) ? '#ad3435' : '#1cc0f3'
                 }"
               ></div>
             </i-col>
-            <i-col
-              :xs="24"
-              :sm="24"
-              :md="24"
-              :lg="8"
-              v-if="item.startDate != 0"
-            >
+            <i-col :xs="24" :sm="24" :md="24" :lg="8" v-if="item.startDate != 0">
               <span class="remainDay">剩餘</span>
               <span
                 class="remainDay-text"
                 :style="{
-                  color: colorPercent(
-                    converTime(item.startDate),
-                    converTime(item.endDate)
-                  )
-                    ? '#ad3435'
-                    : '#1cc0f3',
+                  color: colorPercent(converTime(item.startDate), converTime(item.endDate)) ? '#ad3435' : '#1cc0f3'
                 }"
                 >{{ dateRemain(today(), converTime(item.endDate)) }}</span
               ><span class="remainDay">天</span>
@@ -87,64 +46,53 @@
           </Row>
         </div>
         <!--給AclassOne-->
-        <div
-          class="detailinfo-block"
-          v-if="index == 0 && serviceIntroIsOpen[index].isOpen == true"
-        >
-          <p class="nomal-text auth-num">
+        <div class="detailinfo-block" v-if="index == 0 && serviceIntroIsOpen[index].isOpen == true">
+          <p class="nomal-text auth-num" v-if="item.hasOwnProperty('notbuy') == false">
             授權數量:<span class="strong-text">{{ item.avaliable }}</span>
           </p>
-          <Row>
+          <Row v-if="item.hasOwnProperty('notbuy') == false">
             <i-col :xs="24" :sm="24" :md="24" :lg="10">
               <div class="title-rec"></div>
               <p class="nomal-text title-withRec">
-                固定分配數:<span class="strong-text"
-                  >{{ item.used }} ({{
-                    ((item.used / (item.avaliable)) * 100).toFixed(1)
-                  }}%)</span
-                >
+                固定分配數:<span class="strong-text">{{ item.staUsed }} ({{ ((item.staUsed / item.avaliable) * 100).toFixed(1) }}%)</span>
               </p>
               <br />
               <div class="title-rec"></div>
               <p class="nomal-text title-withRec">
-                動態分配數:<span class="strong-text"
-                  >{{ item.less }} ({{
-                    ((item.less / (item.avaliable)) * 100).toFixed(1)
-                  }}%)</span
-                >
+                動態分配數:<span class="strong-text">{{ item.dynUsed }} ({{ ((item.dynUsed / item.avaliable) * 100).toFixed(1) }}%)</span>
               </p></i-col
             >
             <i-col :xs="24" :sm="24" :md="24" :lg="7">
               <p class="nomal-text">
-                總使用率:<span class="strong-text">
-                  {{
-                    (
-                      ((item.less+item.used) / (item.avaliable)) *
-                      100
-                    ).toFixed(1)
-                  }}%</span
-                >
+                總使用率:<span class="strong-text"> {{ (((item.dynUsed + item.staUsed) / item.avaliable) * 100).toFixed(1) }}%</span>
               </p></i-col
             >
-            <i-col :xs="24" :sm="24" :md="24" :lg="7">
-              <AclassOneChart :less='item.less' :used='item.used' :avalible='item.avaliable'
-            /></i-col>
+            <i-col :xs="24" :sm="24" :md="24" :lg="7"> <AclassOneChart :less="item.dynUsed" :used="item.staUsed" :avaliable="item.avaliable"/></i-col>
+          </Row>
+          <p
+            v-if="item.hasOwnProperty('notbuy') == true"
+            class="detail"
+            :style="{
+              'margin-top': item.hasOwnProperty('notbuy') != true ? '0px' : '10px'
+            }"
+          >
+            詳細服務內容:
+          </p>
+          <Row v-if="item.hasOwnProperty('notbuy') == true">
+            <i-col :xs="24" :sm="24" :md="24" :lg="16"> <p class="intro-text">實際行銷簡介文本待提供</p></i-col>
           </Row>
-
           <div class="action-btn" @click="gotoStudentManagent()">
             管理AClassOne授權
           </div>
         </div>
+
         <!--給其他產品-->
-        <div
-          class="detailinfo-block"
-          v-if="index != 0 && serviceIntroIsOpen[index].isOpen == true"
-        >
-          <p class="detail" :style="{'margin-top':item.hasOwnProperty('notbuy')!=true?'0px':'10px' }">詳細服務內容:</p>
+        <div class="detailinfo-block" v-if="index != 0 && serviceIntroIsOpen[index].isOpen == true">
+          <p class="detail" :style="{ 'margin-top': item.hasOwnProperty('notbuy') != true ? '0px' : '10px' }">
+            詳細服務內容:
+          </p>
           <Row>
-            <i-col :xs="24" :sm="24" :md="24" :lg="16">
-              <p class="intro-text">實際行銷簡介文本待提供</p></i-col
-            >
+            <i-col :xs="24" :sm="24" :md="24" :lg="16"> <p class="intro-text">實際行銷簡介文本待提供</p></i-col>
           </Row>
         </div>
       </div>
@@ -153,104 +101,99 @@
 </template>
 
 <script>
-import router from "../../../router";
-import AclassOneChart from "./AclassOneChart";
-import { mapGetters } from "vuex";
+import router from '../../../router'
+import AclassOneChart from './AclassOneChart'
+import { mapGetters } from 'vuex'
 export default {
-  props: ["serviceList", "windowWidth", "windowHeight"],
-  name: "ServiceList",
+  props: ['serviceList', 'windowWidth', 'windowHeight'],
+  name: 'ServiceList',
   data() {
     return {
       serviceIntroIsOpen: {},
-      oriIndex: "",
-    };
+      oriIndex: ''
+    }
   },
   components: {
-    AclassOneChart,
+    AclassOneChart
   },
   computed: {
     ...mapGetters({
-      serviceIntroIsOpenVuex: "serviceDriveAuth/getServiceIntroIsOpen",
-      serviceNameList: "serviceDriveAuth/getServiceNameList",
-    }),
+      serviceIntroIsOpenVuex: 'serviceDriveAuth/getServiceIntroIsOpen',
+      serviceNameList: 'serviceDriveAuth/getServiceNameList'
+    })
   },
   created() {
-    this.serviceIntroIsOpen = this.serviceIntroIsOpenVuex;
+    this.serviceIntroIsOpen = this.serviceIntroIsOpenVuex
   },
 
   watch: {
-    serviceList: function () {
+    serviceList: function() {
       //根據服務的長度生出開關,只要服務數量變動就必須重置
-      let temp = [];
+      let temp = []
 
       for (let i = 0; i < this.serviceList.length; i++) {
         temp.push({
           index: i,
-          isOpen: false,
-        });
-        temp[0].isOpen=true //預設把AclassOne細節打開
+          isOpen: false
+        })
+        temp[0].isOpen = true //預設把AclassOne細節打開
       }
-      this.serviceIntroIsOpen = temp;
-    },
+      this.serviceIntroIsOpen = temp
+    }
   },
 
   methods: {
-    converTime: function (time) {
+    converTime: function(time) {
       //time: Unix timestamp(秒)
-      let datetime = new Date();
+      let datetime = new Date()
       //let timerecount = time * 1000 - datetime.getTimezoneOffset() * 60 * 1000;
-      let timerecount = time * 1000;
-      datetime.setTime(timerecount);
-      let year = datetime.getFullYear();
-      let month = datetime.getMonth() + 1;
-      let date = datetime.getDate();
-      let hour = datetime.getHours();
-      let ampm = hour >= 12 ? "PM" : "AM";
-      hour %= 12;
-      hour = hour || 12; // the hour '0' should be '12'
-      let minute = datetime.getMinutes();
-      return year + "." + month + "." + date;
+      let timerecount = time * 1000
+      datetime.setTime(timerecount)
+      let year = datetime.getFullYear()
+      let month = datetime.getMonth() + 1
+      let date = datetime.getDate()
+      let hour = datetime.getHours()
+      let ampm = hour >= 12 ? 'PM' : 'AM'
+      hour %= 12
+      hour = hour || 12 // the hour '0' should be '12'
+      let minute = datetime.getMinutes()
+      return year + '.' + month + '.' + date
     },
     gotoStudentManagent() {
-      this.$router.push("/home/studentAccount");
+      this.$router.push('/home/studentAccount')
     },
-    colorPercent: function (a, b) {
-      if (this.dateRemain(this.today(), b) / this.dateRemain(a, b) < 0.3)
-        return true;
-      else return false;
+    colorPercent: function(a, b) {
+      if (this.dateRemain(this.today(), b) / this.dateRemain(a, b) < 0.3) return true
+      else return false
     },
     setIntroOpen(index) {
-      this.serviceIntroIsOpen[index].isOpen = !this.serviceIntroIsOpen[index]
-        .isOpen;
+      this.serviceIntroIsOpen[index].isOpen = !this.serviceIntroIsOpen[index].isOpen
       //console.log(index, this.serviceIntroIsOpen);
     },
     today() {
-      var today = new Date();
-      var dd = String(today.getDate()).padStart(2, "0");
-      var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0!
-      var yyyy = today.getFullYear();
+      var today = new Date()
+      var dd = String(today.getDate()).padStart(2, '0')
+      var mm = String(today.getMonth() + 1).padStart(2, '0') //January is 0!
+      var yyyy = today.getFullYear()
 
-      today = yyyy + "/" + mm + "/" + dd;
-      return today;
+      today = yyyy + '/' + mm + '/' + dd
+      return today
     },
     dateRemain(today, endday) {
-      let yyyy = endday.substr(0, 4);
-      let mm = endday.substr(5, 1);
-      let dd = endday.substr(7, 2);
-      let strEndday = yyyy + "/" + mm + "/" + dd;
+      let strEndday = endday.replace('.', '/')
 
       //console.log(today, strEndday);
 
-      let oDate1 = new Date(today);
-      let oDate2 = new Date(strEndday);
-      let iDays = parseInt(Math.abs(oDate1 - oDate2) / 1000 / 60 / 60 / 24); // 把相差的毫秒數轉換為天數
+      let oDate1 = new Date(today)
+      let oDate2 = new Date(strEndday)
+      let iDays = parseInt(Math.abs(oDate1 - oDate2) / 1000 / 60 / 60 / 24) // 把相差的毫秒數轉換為天數
       //console.log(iDays);
-      return iDays;
-    },
-  },
-};
+      return iDays
+    }
+  }
+}
 </script>
 
-<style lang='less'>
-@import "./ServiceList.less";
-</style>
+<style lang="less">
+@import './ServiceList.less';
+</style>

+ 21 - 17
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceChart.vue

@@ -17,38 +17,42 @@ export default {
       },
     };
   },
-  mounted() {
-    //this.drawLine(this.mockdata.pieNumData);
-  },
-  created() {
-    this.$emit("StudentSourceInfo", this.mockdata.pieNumData);
-  },
+
   watch: {
     spaceStatus: function (value) {
+      if (value != undefined) {
+        console.log(value);
+      }
+
       (this.mockdata.pieNumData = [
-        { value: value.doc, name: "文件", days: this.remainDays },
+        {
+          value: value.doc,
+          name: "文件",
+          days: value.endDate == 0 ? "永久授權" : this.remainDays,
+         
+        },
         { value: value.video, name: "影片", days: this.remainDays },
         { value: value.image, name: "圖片", days: this.remainDays },
         {
           value: value.item + value.paper,
           name: "題目與試卷",
-          days: this.remainDays,
+          days: this.remainDays
         },
         { value: value.other, name: "其他", days: this.remainDays },
         {
           value: value.student,
           name: "學生使用",
-          days: this.remainDays,
+          days: this.remainDays
         },
         {
           value: value.teacher,
           name: "已分配至教師",
-          days: this.remainDays,
+          days: this.remainDays
         },
         {
-          value: value.unUsed,
+          value: value.unUsed > 0?Math.abs(value.unUsed):0,
           name: "未使用",
-          days: this.remainDays,
+          days: this.remainDays
         },
       ]),
         this.drawLine(this.mockdata.pieNumData);
@@ -62,6 +66,7 @@ export default {
 
     drawLine(pieNumData) {
       //基于准备好的dom,初始化echarts实例
+     
       let _this = this;
       let myChart = this.$echarts.init(document.getElementById("space-chart")); //绘制图表
       myChart.setOption({
@@ -73,21 +78,20 @@ export default {
           formatter: function (v) {
             if (v.color != "#48474c") _this._data.currentColor = v.color;
             else _this._data.currentColor = "white";
-
+             console.log(this) 
             return `
             <div class='chart-toolTip'> 
              <p id='color-title'  >${v.data.name}空間</p>
              <p class='title'>${v.data.name}空間數:<span class='value'>&nbsp;${
-              (v.value / Math.pow(2, 20)) < 1024
+              v.value / Math.pow(2, 20) < 1024
                 ? (v.value / Math.pow(2, 20)).toFixed(2) + "&nbspMB"
                 : (v.value / Math.pow(2, 30)).toFixed(2) + "&nbspGB"
             } </span></p>
              <p class='title'>${
                v.data.name
              }空間比率:<span class='value'>&nbsp;${v.percent} %</span></p>
-             <p class='title'>剩餘天數:<span class='value'>&nbsp;${
-               v.data.days
-             } days</span> </p> </div> 
+
+             
             `;
           },
         },

+ 7 - 0
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceStatus.less

@@ -67,6 +67,13 @@
       font-size: 26px;
       font-weight: 600;
     }
+    .info-note{
+      color: #fafafa;
+    }
+    
+  }
+  .over-used{
+    color: red;
   }
   .main-wrap {
     padding: 30px;

+ 28 - 13
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceStatus.vue

@@ -7,14 +7,14 @@
       </div>
     </div>
     <div
-      v-if="isloading == true||spaceStatus==undefined"
+      v-if="isloading == true||spaceStatus==undefined||spaceStatus.notbuy==true"
       class="no-data-text"
       style="margin-bottom: -50%"
     >
       <img src="../../../assets/icon/no_data.png" width="120" />
       <p style="color: #808080">暂无数据</p>
     </div>
-
+   
     <Row
       class="main-wrap"
       :style="{ visibility: isloading == true? 'hidden' : 'visible' }"
@@ -28,9 +28,12 @@
       </i-col>
       <i-col :xs="24" :sm="24" :md="24" :lg="12">
         <p class="nomal-title">已使用空間總數</p>
-        <p class="info-text">
-          <span class="info-num">{{ totalused }}</span>
-          {{ totalusedText }}
+        <p class="info-text" :class="{'over-used':totalused>totalavaliable}">
+          <span class="info-num" >{{ totalused }}</span>
+          {{ totalusedText }} 
+          <Tooltip transfer theme="light" content="空間已到期,超過僅供檢視"  placement="bottom" offset="-20">
+                        <Icon type="ios-alert-outline" color="#1cc0f3" size="20" v-if='totalused>totalavaliable'  style="position:absolute;top:-17px;margin-left:2px" />
+                    </Tooltip>
         </p>
       </i-col>
 
@@ -39,7 +42,7 @@
           <p class="nomal-title">空間使用狀況</p>
           <SpaceChart :spaceStatus="spaceStatus" :remainDays="remainDays" />
           <!--放置餅圖剩下的空間狀態-->
-          <div class="remail-data">
+          <div class="remail-data" v-if="totalavaliable>=totalused">
             <p class="title">未使用空間數</p>
             <p class="num">
               {{ unUsed }}<span class="small-text"> {{ unUsedText }}</span>
@@ -49,21 +52,33 @@
               {{ spaceStatus.unUsedPercent }}<span class="small-text"> %</span>
             </p>
           </div>
+          <div class="remail-data" v-if="totalavaliable<totalused">
+            <p class="title">可使用空間數</p>
+            <p class="num">
+               0 <span class="small-text"> GB</span>
+            </p>
+            <p class="title">可使用空間比率</p>
+            <p class="num">
+              0<span class="small-text"> %</span>
+            </p>
+          </div>
+          
         </div>
       </i-col>
       <i-col :xs="24" :sm="24" :md="24" :lg="24">
         <p class="deadline-title">
-          當前空間到期日:<span class="deadline-time"
+          當前空間到期日:<span class="deadline-time" v-if="totalavaliable>=totalused"
             >{{ converTime(spaceStatus.endDate) }} ({{ remainDays }} 天)</span
-          >
+          ><span v-else>基本 1GB 永久授權</span>
         </p>
 
-        <p class="note">
+        <p class="note" v-if="totalavaliable>=totalused">
           ( 到期後將變更為IES基本空間,超出基本空間值將被收回 )
         </p>
       </i-col>
     </Row>
     <div
+      v-if="spaceStatus.notbuy!=true"
       class="service-list buy-list"
       :style="{ visibility: isloading == true ? 'hidden' : 'visible' }"
     >
@@ -140,6 +155,7 @@ export default {
   },
   watch: {
     spaceStatus: function (value) {
+      console.log(value)
       if (value !== undefined) {
         this.remainDays = this.dateRemain(
           this.today(),
@@ -210,10 +226,9 @@ export default {
       return today;
     },
     dateRemain(today, endday) {
-      let yyyy = endday.substr(0, 4);
-      let mm = endday.substr(5, 1);
-      let dd = endday.substr(7, 2);
-      let strEndday = yyyy + "/" + mm + "/" + dd;
+     
+     
+      let strEndday = endday.replace('.','/');
 
       //console.log(today, strEndday);
 

+ 1 - 0
TEAMModelOS/ClientApp/src/view/student-account/AclassOneAuth.less

@@ -177,6 +177,7 @@
 
     .chart-item {
       margin-bottom: 28px;
+      z-index: 3;
 
       .item-ball {
         display: inline-block;

+ 271 - 425
TEAMModelOS/ClientApp/src/view/student-account/AclassOneAuth.vue

@@ -1,11 +1,7 @@
 <template>
   <div class="aclassone-auth" :class="{ closeShow: closefromBtn == true }">
     <!--收回授權弹窗 -->
-    <Modal
-      class="aclassone-auth-modal"
-      v-model="isOpenRemove"
-      :title="'確定收回所有授權'"
-    >
+    <Modal class="aclassone-auth-modal" v-model="isOpenRemove" :title="'確定收回所有授權'">
       <p>收回授權後使用數會直接設置為0,如欲給予授權可針對目標對象進行套用。</p>
 
       <div slot="footer">
@@ -14,9 +10,7 @@
       </div>
     </Modal>
     <div class="title">
-      服務授權管理<span @click="close()"
-        ><v-icon class="close-icon" iconClass="close"
-      /></span>
+      服務授權管理<span @click="close()"><v-icon class="close-icon" iconClass="close"/></span>
     </div>
     <div class="dashboard-title">AClassONE智慧學伴服務授權</div>
     <p class="dashboard-note">
@@ -25,92 +19,53 @@
     <Row class="dashboard">
       <i-col :xs="24" :sm="8" :md="8" :lg="8">
         <p class="info-num">
-          <CountTo
-            class="auth-info-num"
-            :endVal="totalAuth"
-            :duration="600"
-          ></CountTo>
+          <CountTo class="auth-info-num" :endVal="totalAuth" :duration="600"></CountTo>
         </p>
         <p class="info-text">總授權數</p>
       </i-col>
       <i-col :xs="24" :sm="8" :md="8" :lg="8">
         <p class="info-num">
-          <CountTo
-            class="auth-info-num"
-            :endVal="currentAssignNum"
-            :duration="600"
-          ></CountTo>
+          <CountTo class="auth-info-num" :endVal="currentAssignNum" :duration="600"></CountTo>
         </p>
         <p class="info-text">本次套用數</p>
       </i-col>
       <i-col :xs="24" :sm="8" :md="8" :lg="8">
         <p class="info-num">
-          <CountTo
-            class="auth-info-num"
-            :endVal="pieNumData[1].value+pieNumData[0].value"
-            :duration="600"
-          ></CountTo>
+          <CountTo class="auth-info-num" :endVal="usedAuth" :duration="600"></CountTo>
         </p>
         <p class="info-text">已使用數</p>
       </i-col>
     </Row>
 
     <!--分頁控件--->
-    <span
-      class="tab-btn"
-      :class="{ 'tab-now': currentTab == 0 }"
-      @click="setCurrentTab(0)"
-      >授權總覽</span
-    >
-    <span
-      class="tab-btn"
-      :class="{ 'tab-now': currentTab == 1 }"
-      @click="setCurrentTab(1)"
-      >授權管理</span
-    >
+    <span class="tab-btn" :class="{ 'tab-now': currentTab == 0 }" @click="setCurrentTab(0)">授權總覽</span>
+    <span class="tab-btn" :class="{ 'tab-now': currentTab == 1 }" @click="setCurrentTab(1)">授權管理</span>
     <Row class="tabContent-wrap">
       <!--授權總攬-->
       <div v-if="currentTab == 0">
         <i-col :xs="24" :sm="12" :md="12" :lg="16">
           <p class="chart-title">
-            已購授權總數:<span class="chart-totalnum">356</span>
+            已購授權總數:<span class="chart-totalnum">{{ totalAuth }}</span>
           </p>
           <AuthNumChart :clickTab="clickTab" :dbPieNumData="pieNumData" />
         </i-col>
         <i-col :xs="24" :sm="12" :md="12" :lg="8" class="chart-label">
-          <div
-            class="chart-item"
-            v-for="(item, index) in pieNumData"
-            :key="index"
-          >
-            <div
-              class="item-ball"
-              :style="{ 'background-color': color[index] }"
-            />
+          <div class="chart-item" v-for="(item, index) in pieNumData" :key="index">
+            <div class="item-ball" :style="{ 'background-color': color[index] }" />
             <span class="item-text">{{ item.name }}</span>
             <p class="item-num">{{ item.value }}</p>
           </div>
         </i-col>
 
-        <div class="chart-note">
-          <span class="list-icon">*</span
-          >&nbsp;固定分配之授權由校方統一設置,使用者請於授權管理分頁進行授權對象的設定。動態取用之授權每日23:59:59將自動回收,學生可於次日再次啟用。
-        </div>
+        <div class="chart-note"><span class="list-icon">*</span>&nbsp;固定分配之授權由校方統一設置,使用者請於授權管理分頁進行授權對象的設定。動態取用之授權每日23:59:59將自動回收,學生可於次日再次啟用。</div>
       </div>
       <!--授權管理-->
       <div v-if="currentTab == 1" class="option-group">
         <p class="small-title">快速套用授權</p>
         <Row class="option-item">
-          <i-col :xs="24" :sm="12" :md="12" :lg="22"
-            ><p class="option-text">套用至所有當前勾選項目</p></i-col
-          >
+          <i-col :xs="24" :sm="12" :md="12" :lg="22"><p class="option-text">套用至所有當前勾選項目</p></i-col>
           <i-col :xs="24" :sm="12" :md="12" :lg="2"
-            ><div
-              class="assign-btn"
-              style="margin-top: -2px"
-              @click="assignType(1)"
-              :class="{ 'light-btn': currentAssignType == 1 }"
-            >
+            ><div class="assign-btn" style="margin-top: -2px" @click="assignType(1)" :class="{ 'light-btn': currentAssignType == 1 }">
               套用
             </div></i-col
           >
@@ -119,24 +74,7 @@
           <i-col :xs="24" :sm="12" :md="12" :lg="22"
             ><p class="option-text">
               套用至當前勾選目標第
-              <input
-                :disabled="selected.length == 0"
-                class="input-num"
-                v-model="inputSelectMin"
-                type="number"
-                placeholder="-"
-                :max="selected.length"
-                min="1"
-              />項至第
-              <input
-                :disabled="selected.length == 0"
-                placeholder="-"
-                class="input-num"
-                v-model="inputSelectMax"
-                type="number"
-                :min="inputSelectMin"
-                :max="selected.length"
-              />項
+              <input :disabled="selected.length == 0" class="input-num" v-model="inputSelectMin" type="number" placeholder="-" :max="selected.length" min="1" />項至第 <input :disabled="selected.length == 0" placeholder="-" class="input-num" v-model="inputSelectMax" type="number" :min="inputSelectMin" :max="selected.length" />項
             </p></i-col
           >
           <i-col :xs="24" :sm="12" :md="12" :lg="2"
@@ -146,14 +84,7 @@
               @click="assignType(2)"
               :class="{
                 'light-btn': currentAssignType == 2,
-                'disabled-btn':
-                  inputSelectMin > selected.length == true ||
-                  inputSelectMax > selected.length == true ||
-                  inputSelectMin > inputSelectMax == true ||
-                  inputSelectMax <= 0 ||
-                  inputSelectMax == '' ||
-                  inputSelectMin <= 0 ||
-                  inputSelectMin == '',
+                'disabled-btn': inputSelectMin > selected.length == true || inputSelectMax > selected.length == true || inputSelectMin > inputSelectMax == true || inputSelectMax <= 0 || inputSelectMax == '' || inputSelectMin <= 0 || inputSelectMin == ''
               }"
             >
               套用
@@ -165,21 +96,8 @@
             ><p class="option-text">
               套用至
               <!-- 學制Select -->
-              <Select
-                class="dark-iview-select"
-                v-model="searchPeriod"
-                :placeholder="$t('stuAccount.periodHolder')"
-                clearable
-                @on-clear="currentFilter = -1"
-                @on-open-change="cleanData(0)"
-                @on-change="filterData"
-              >
-                <Option
-                  v-for="(item, index) in periods"
-                  :value="item.id"
-                  :key="index"
-                  >{{ item.name }}</Option
-                > </Select
+              <Select class="dark-iview-select" v-model="searchPeriod" :placeholder="$t('stuAccount.periodHolder')" clearable @on-clear="currentFilter = -1" @on-open-change="cleanData(0)" @on-change="filterData">
+                <Option v-for="(item, index) in periods" :value="item.id" :key="index">{{ item.name }}</Option> </Select
               >所有項目
             </p></i-col
           >
@@ -190,7 +108,7 @@
               @click="assignType(3)"
               :class="{
                 'light-btn': currentAssignType == 3,
-                'disabled-btn': currentFilter != 0 || searchPeriod == '',
+                'disabled-btn': currentFilter != 0 || searchPeriod == ''
               }"
             >
               套用
@@ -203,22 +121,8 @@
               套用至
 
               <!-- 學級Select -->
-              <Select
-                class="dark-iview-select"
-                v-model="searchGrade"
-                :placeholder="$t('stuAccount.gradeHolder')"
-                not-found-text="请先选择学段"
-                clearable
-                @on-clear="currentFilter = -1"
-                @on-open-change="cleanData(1)"
-                @on-change="filterData"
-              >
-                <Option
-                  v-for="(item, index) in grades"
-                  :value="item.id"
-                  :key="index"
-                  >{{ item.name }}</Option
-                > </Select
+              <Select class="dark-iview-select" v-model="searchGrade" :placeholder="$t('stuAccount.gradeHolder')" not-found-text="请先选择学段" clearable @on-clear="currentFilter = -1" @on-open-change="cleanData(1)" @on-change="filterData">
+                <Option v-for="(item, index) in grades" :value="item.id" :key="index">{{ item.name }}</Option> </Select
               >所有項目
             </p></i-col
           >
@@ -229,7 +133,7 @@
               @click="assignType(4)"
               :class="{
                 'light-btn': currentAssignType == 4,
-                'disabled-btn': currentFilter != 1 || searchGrade == '',
+                'disabled-btn': currentFilter != 1 || searchGrade == ''
               }"
             >
               套用
@@ -241,25 +145,8 @@
             ><p class="option-text">
               套用至
               <!-- 教室Select -->
-              <Select
-                v-model="searchClassroom"
-                class="dark-iview-select"
-                ref="classroom"
-                placeholder="選擇班級"
-                clearable
-                @on-clear="currentFilter = -1"
-                @on-open-change="cleanData(2)"
-                @on-change="filterData"
-                :not-found-text="
-                  searchGrade ? '此年级暂无教室' : '请先选择年级'
-                "
-              >
-                <Option
-                  v-for="(item, index) in classes"
-                  :value="item.id"
-                  :key="index"
-                  >{{ item.name }}</Option
-                >
+              <Select v-model="searchClassroom" class="dark-iview-select" ref="classroom" placeholder="選擇班級" clearable @on-clear="currentFilter = -1" @on-open-change="cleanData(2)" @on-change="filterData" :not-found-text="searchGrade ? '此年级暂无教室' : '请先选择年级'">
+                <Option v-for="(item, index) in classes" :value="item.id" :key="index">{{ item.name }}</Option>
                 <Option value="noclass">未关联班级</Option> </Select
               >所有項目
             </p></i-col
@@ -271,7 +158,7 @@
               @click="assignType(5)"
               :class="{
                 'light-btn': currentAssignType == 5,
-                'disabled-btn': currentFilter != 2 || searchClassroom == '',
+                'disabled-btn': currentFilter != 2 || searchClassroom == ''
               }"
             >
               套用
@@ -280,11 +167,7 @@
         </Row>
         <Row class="option-item">
           <i-col :xs="24" :sm="12" :md="12" :lg="22"
-            ><p class="option-text">
-              套用至全校所有項目 (共 {{ students.length }} 個,{{
-                students.length > totalAuth ? "大於總授權數,無法使用" : ""
-              }})
-            </p></i-col
+            ><p class="option-text">套用至全校所有項目 (共 {{ students.length }} 個,{{ students.length > totalAuth ? '大於總授權數,無法使用' : '' }})</p></i-col
           >
           <i-col :xs="24" :sm="12" :md="12" :lg="2"
             ><div
@@ -293,7 +176,7 @@
               @click="assignType(6)"
               :class="{
                 'light-btn': currentAssignType == 6,
-                'disabled-btn': students.length > totalAuth,
+                'disabled-btn': students.length > totalAuth
               }"
             >
               套用
@@ -303,11 +186,7 @@
       </div>
     </Row>
     <div v-if="currentTab == 0" class="save-btn buy-btn">添購其他授權</div>
-    <div
-      v-if="currentTab == 1"
-      class="unAssign-btn"
-      @click="isOpenRemove = true"
-    >
+    <div v-if="currentTab == 1" class="unAssign-btn" @click="isOpenRemove = true">
       收回所有授權
     </div>
     <div v-if="currentTab == 1" class="save-btn" @click="saveAuth()">
@@ -317,30 +196,30 @@
 </template>
 
 <script>
-import CountTo from "vue-count-to";
-import "@/icons/svg/close.svg";
-import AuthNumChart from "./AuthNumChart.vue";
-import { mapGetters, mapMutations } from "vuex";
+import CountTo from 'vue-count-to'
+import '@/icons/svg/close.svg'
+import AuthNumChart from './AuthNumChart.vue'
+import { mapGetters, mapMutations } from 'vuex'
 export default {
-  name: "AclassOneAuth",
-  props: ["closefromBtn", "selected", "students"],
+  name: 'AclassOneAuth',
+  props: ['closefromBtn', 'selected', 'students'],
   components: {
     AuthNumChart,
-    CountTo,
+    CountTo
   },
   data() {
     return {
       isOpenRemove: false,
-      searchPeriod: "",
-      searchGrade: "",
-      searchClassroom: "",
-      inputSelectMin: "",
-      inputSelectMax: "",
+      searchPeriod: '',
+      searchGrade: '',
+      searchClassroom: '',
+      inputSelectMin: '',
+      inputSelectMax: '',
       currentTab: 0,
       clickTab: false,
 
-      totalAuth: 356,
-      usedAuth: 128,
+      totalAuth: 0,
+      usedAuth: 0,
       currentAssignType: 0, //存放目前套用的類目 0為選,1當前選中,2選中並指定長度,3學制,4學籍,5班級,6全校
       currentAssignNum: 0, //存放目前所套用所選之數量
       currentFilter: -1, //當前所選中的篩選器
@@ -351,391 +230,358 @@ export default {
       basicCount: 99,
       pointNum: 0,
       pieNumData: [
-        { value: 104, name: "固定分配授權數" },
-        { value: 24, name: "今日動態取用數" },
-        { value: 228, name: "今日可用授權數" },
+        { value: 0, name: '固定分配授權數' },
+        { value: 0, name: '今日動態取用數' },
+        { value: 0, name: '今日可用授權數' }
       ],
 
-      color: ["#eb974e", "#fb62bb", "#00f492"],
-    };
+      color: ['#eb974e', '#fb62bb', '#00f492'],
+      studentIds: [],
+      sentParams: {
+        schoolCode: '',
+        studentIds: []
+      },
+      finalAdd: 0
+    }
   },
-  
+
   computed: {
     ...mapGetters({
-      periods: "user/getPeriods", // 學制s
-      grades: "user/getGrades", // 年級
-      classes: "user/getClasses", // 教室ID
-      //students: "schoolBaseInfo/getStudent", // 學生List
+      periods: 'user/getPeriods', // 學制s
+      grades: 'user/getGrades', // 年級
+      classes: 'user/getClasses', // 教室ID
+      aclassOneInfoNum: 'studentAclassOneAuth/getSchoolAclassOneInfoNum'
     }),
-    filterGrades: function () {
-      var data = this.grades;
+    filterGrades: function() {
+      var data = this.grades
       if (this.searchPeriod) {
-        let periodId = this.searchPeriod;
-        data = data.filter(function (item) {
-          return item.periodId == periodId;
-        });
-        return data;
+        let periodId = this.searchPeriod
+        data = data.filter(function(item) {
+          return item.periodId == periodId
+        })
+        return data
       } else {
-        return [];
+        return []
       }
     },
-    filterClasses: function () {
-      return this.classes;
-      var data = this.classes;
+    filterClasses: function() {
+      return this.classes
+      var data = this.classes
       if (this.searchGrade) {
-        let gradeId = this.searchGrade;
-        data = data.filter(function (item) {
-          return item.gradeId == gradeId;
-        });
-        return data;
+        let gradeId = this.searchGrade
+        data = data.filter(function(item) {
+          return item.gradeId == gradeId
+        })
+        return data
       } else {
-        return [];
+        return []
       }
-    },
+    }
   },
   watch: {
-    //只要勾選有變動就預設套用第一項
-
-  
-    selected: function (value) {
-      this.currentAssignType = 1;
-      //this.checkSelectIsAuth();
-
-      this.currentAssignNum = this.selected.length;
+    //只要勾選有變動就預設套用第一項,初始化時
+    aclassOneInfoNum: function(value) {
+      if (value != undefined) {
+        this.totalAuth = value.total
+        ;(this.usedAuth = value.staNum + value.dyncNum), (this.pieNumData[0].value = value.staNum)
+        this.pieNumData[1].value = value.dyncNum
+        this.pieNumData[2].value = value.total - (this.pieNumData[1].value + this.pieNumData[0].value)
+      }
     },
+    selected: function(value) {
+      this.currentAssignType = 1
+
+      this.currentAssignNum = this.selected.length
+    }
   },
   methods: {
     ...mapMutations({
-      setStudents: "schoolBaseInfo/setStudents",
+      setStudents: 'schoolBaseInfo/setStudents',
+      setFirstOpenAuth: 'studentAclassOneAuth/setFirstOpenAuth'
     }),
-    //Louise Mock 加入授權數,實際應從vuex讀取db api變動之值
+
     saveAuth() {
-      console.log();
-      let finalAdd = 0;
       if (this.currentAssignType == 0) {
-        this.$Message.warning("請先選擇套用,再按保存");
+        this.$Message.warning('請先選擇套用,再按保存')
       }
 
       //情況1:純勾選
       if (this.currentAssignType == 1) {
-        this.selected.forEach((item) => {
-          if (this.getMockAuthStatus(item.id) == false) {
-            this.setStudentTarget(item.id);
-            finalAdd++;
+        this.selected.forEach(element => {
+          if (this.aclassOneInfoNum.staIds.includes(element.id) == false && this.aclassOneInfoNum.dyncIds.includes(element.id) == false) {
+            this.finalAdd++
+            this.studentIds.push(element.id)
           }
-        });
-
-        if (finalAdd <= this.totalAuth - this.usedAuth) {
-          this.usedAuth = this.usedAuth + finalAdd;
-          this.$Message.success("保存成功,本次新增" + finalAdd + "個");
-          this.$emit("updateAuth");
-        } else {
-          /*if (this.currentAssignNum <= (this.totalAuth - this.usedAuth)) {
-        
-        this.usedAuth = this.usedAuth + this.currentAssignNum;
-      }*/
-          this.$Message.error("保存失敗,超過可用授權數!");
-        }
+        })
       }
-      //情況2:勾選在選目標
-      if (this.currentAssignType == 2) {
-        let chooseSelected = this.selected.slice(
-          this.inputSelectMin - 1,
-          this.inputSelectMax
-        );
-        console.log("篩了再選");
 
-        console.log(chooseSelected);
-
-        chooseSelected.forEach((item) => {
-          if (this.getMockAuthStatus(item.id) == false) {
-            this.setStudentTarget(item.id);
-            finalAdd++;
+      //情況2:勾選再選目標序號
+      if (this.currentAssignType == 2) {
+        let chooseSelected = this.selected.slice(this.inputSelectMin - 1, this.inputSelectMax)
+        chooseSelected.forEach(element => {
+          if (this.aclassOneInfoNum.staIds.includes(element.id) == false && this.aclassOneInfoNum.dyncIds.includes(element.id) == false) {
+            this.finalAdd++
+            this.studentIds.push(element.id)
           }
-        });
-
-        if (finalAdd <= this.totalAuth - this.usedAuth) {
-          this.usedAuth = this.usedAuth + finalAdd;
-          this.$Message.success("保存成功,本次新增" + finalAdd + "個");
-          this.$emit("updateAuth");
-        } else {
-          this.$Message.error("保存失敗,超過可用授權數!");
-        }
+        })
       }
-      //情況3,4,5:選擇學段學級班級,套用數大於總數時須先比對新增數,新增數超過才顯示警告
-      if (
-        this.currentAssignType == 3 ||
-        this.currentAssignType == 4 ||
-        this.currentAssignType == 5
-      ) {
-        let filterDataNow = this.tableFilterData;
-        filterDataNow.forEach((item) => {
-          if (this.getMockAuthStatus(item.id) == false) {
-            this.setStudentTarget(item.id);
-            finalAdd++;
+      //情況3:選擇學段學級班級
+      if (this.currentAssignType == 3 || this.currentAssignType == 4 || this.currentAssignType == 5) {
+        let filterDataNow = this.tableFilterData
+        //console.log(filterDataNow)
+        filterDataNow.forEach(element => {
+          if (this.aclassOneInfoNum.staIds.includes(element.id) == false && this.aclassOneInfoNum.dyncIds.includes(element.id) == false) {
+            this.finalAdd++
+            this.studentIds.push(element.id)
           }
-        });
-        if (finalAdd <= this.totalAuth - this.usedAuth) {
-          this.usedAuth = this.usedAuth + finalAdd;
-          this.$Message.success("保存成功,本次新增" + finalAdd + "個");
-          this.$emit("updateAuth");
+        })
+      }
+
+      this.aclassOneInfoNum.staIds.forEach(element => {
+        if (this.studentIds.includes(element) == false) {
+          //記得把目前有的固定分配加回去
+          this.studentIds.push(element)
+        }
+      })
+
+      if (this.finalAdd <= this.aclassOneInfoNum.total - this.aclassOneInfoNum.staNum - this.aclassOneInfoNum.dyncNum) {
+        this.sentParams.schoolCode = this.$store.state.user.schoolCode
+        this.sentParams.studentIds = this.studentIds
+        //傳給後端
+        this.$store.dispatch('studentAclassOneAuth/setSchoolAclassOneState', this.sentParams)
+        if (this.finalAdd == 0) {
+          this.$Message.success('保存成功,本次尚無新增授權數量')
         } else {
-          this.$Message.error("保存失敗,超過可用授權數!");
+          this.$Message.success('保存成功,本次新增' + this.finalAdd + '個固定分配')
+          //保存後重置狀態
+          this.pieNumData[0].value = this.aclassOneInfoNum.total - this.aclassOneInfoNum.dyncNum
+          this.pieNumData[2].value = this.aclassOneInfoNum.total - (this.pieNumData[1].value + this.pieNumData[0].value)
         }
+      } else {
+        this.$Message.error('保存失敗,超過可用授權數!')
       }
-      this.pieNumData[0].value = this.usedAuth;
-
-      this.pieNumData[2].value = this.totalAuth - this.usedAuth - 24;
-
-      this.currentFilter = -1;
-      this.searchPeriod = "";
-      this.searchGrade = "";
-      this.searchClassroom = "";
-      this.currentAssignNum = 0;
-      this.inputSelectMin = "";
-      this.inputSelectMax = "";
-      this.currentAssignType = 0;
-    },
-    //Louise Mock 授權狀態
-    resetAll() {
-      this.usedAuth = 0;
-      this.currentAssignNum = 0;
-      this.students.forEach((item) => {
-        item.isGivenAuth = false;
-      });
-      this.$emit("updateAuth");
-      this.isOpenRemove = false;
-      this.$Message.success("已收回全數授權!");
-
-      //重製餅圖
-      this.pieNumData[0].value = 0;
-      this.pieNumData[1].value = 0;
-      this.pieNumData[2].value = 356;
-    },
-    setStudentTarget(id) {
-      let targetIndex = this.students.findIndex((item) => item.id == id);
-      console.log(this.students[targetIndex]);
-      this.students[targetIndex].isGivenAuth = true;
-      this.setStudents(this.students);
 
-      //return this.students[targetIndex];
+      this.currentFilter = -1
+      this.searchPeriod = ''
+      this.searchGrade = ''
+      this.searchClassroom = ''
+      this.currentAssignNum = 0
+      this.inputSelectMin = ''
+      this.inputSelectMax = ''
+      this.currentAssignType = 0
+      this.studentIds = []
+      this.sentParams.studentIds = []
+      this.finalAdd = 0
     },
-    getMockAuthStatus(id) {
-      let targetIndex = this.students.findIndex((item) => item.id == id);
-      return this.students[targetIndex].isGivenAuth;
-    },
-    //比對把已經授權的過濾掉
-    checkSelectIsAuth() {
-      let finalAdd = 0;
-      this.selected.forEach((item) => {
-        console.log(item);
-        if (this.getMockAuthStatus(item.id) == true) {
-          finalAdd += 0;
-        } else {
-          finalAdd++;
+    getSchoolAclassOne(schoolCode) {
+      this.$store.dispatch('studentAclassOneAuth/getSchoolAclassOne', schoolCode).then(
+        res => {
+          this.isloading = false
+          this.$Message.success('取得getSchoolAclassOne API資料')
+        },
+        err => {
+          this.$Message.error('取得getSchoolAclassOne API資料失敗')
         }
-      });
-      this.currentAssignNum = finalAdd;
+      )
+    },
+    resetAll() {
+      this.$store.dispatch('studentAclassOneAuth/recallSchoolAclassOne', this.$store.state.user.schoolCode)
+      this.$Message.success('全部收回成功')
+      this.isOpenRemove = false
     },
 
     assignType(type) {
-      this.currentAssignType = type;
+      this.currentAssignType = type
       switch (type) {
         case 0:
-          this.currentFilter = -1;
-          this.currentAssignNum = 0;
-          break;
+          this.currentFilter = -1
+          this.currentAssignNum = 0
+          break
         case 1:
-          this.currentFilter = -1;
-          this.searchPeriod = "";
-          this.searchGrade = "";
-          this.searchClassroom = "";
+          this.currentFilter = -1
+          this.searchPeriod = ''
+          this.searchGrade = ''
+          this.searchClassroom = ''
           if (this.selected.length == 0) {
-            this.$Message.warning("請先勾選欲授權之學生 !");
+            this.$Message.warning('請先勾選欲授權之學生 !')
           }
-          this.currentAssignNum = this.selected.length;
-          //this.checkSelectIsAuth()
+          this.currentAssignNum = this.selected.length
 
-          break;
+          break
         case 2:
-          this.currentFilter = -1;
-          this.searchPeriod = "";
-          this.searchGrade = "";
-          this.searchClassroom = "";
+          this.currentFilter = -1
+          this.searchPeriod = ''
+          this.searchGrade = ''
+          this.searchClassroom = ''
           if (this.selected.length == 0) {
-            this.$Message.warning("請先勾選欲授權之學生");
+            this.$Message.warning('請先勾選欲授權之學生')
           }
-          if (
-            this.inputSelectMin > this.selected.length == true ||
-            this.inputSelectMax > this.selected.length == true ||
-            this.inputSelectMin > this.inputSelectMax == true ||
-            this.inputSelectMax <= 0 ||
-            this.inputSelectMax == "" ||
-            this.inputSelectMin <= 0 ||
-            this.inputSelectMin == ""
-          ) {
-            this.$Message.warning("請設定有效的目標項 !");
-            this.currentAssignNum = 0;
+          if (this.inputSelectMin > this.selected.length == true || this.inputSelectMax > this.selected.length == true || this.inputSelectMin > this.inputSelectMax == true || this.inputSelectMax <= 0 || this.inputSelectMax == '' || this.inputSelectMin <= 0 || this.inputSelectMin == '') {
+            this.$Message.warning('請設定有效的目標項 !')
+            this.currentAssignNum = 0
           } else {
-            this.currentAssignNum =
-              parseInt(this.inputSelectMax) -
-              (parseInt(this.inputSelectMin) - 1);
+            this.currentAssignNum = parseInt(this.inputSelectMax) - (parseInt(this.inputSelectMin) - 1)
           }
 
-          break;
+          break
         case 3:
           if (this.currentFilter == 0) {
-            this.currentAssignNum = this.tableFilterData.length;
+            this.currentAssignNum = this.tableFilterData.length
             if (this.tableFilterData.length == 0) {
-              this.$Message.warning("目前所選學制人數為 0 人!");
+              this.$Message.warning('目前所選學制人數為 0 人!')
             }
           } else {
-            this.$Message.warning("請先選學制!");
-            this.currentAssignNum = 0;
+            this.$Message.warning('請先選學制!')
+            this.currentAssignNum = 0
           }
-          break;
+          break
         case 4:
           if (this.currentFilter == 1) {
-            this.currentAssignNum = this.tableFilterData.length;
+            this.currentAssignNum = this.tableFilterData.length
             if (this.tableFilterData.length == 0) {
-              this.$Message.warning("目前所選學級人數為 0 人!");
+              this.$Message.warning('目前所選學級人數為 0 人!')
             }
           } else {
-            this.$Message.warning("請先選學級!");
-            this.currentAssignNum = 0;
+            this.$Message.warning('請先選學級!')
+            this.currentAssignNum = 0
           }
-          break;
+          break
         case 5:
           if (this.currentFilter == 2) {
-            this.currentAssignNum = this.tableFilterData.length;
+            this.currentAssignNum = this.tableFilterData.length
             if (this.tableFilterData.length == 0) {
-              this.$Message.warning("目前所選班級人數為 0 人!");
+              this.$Message.warning('目前所選班級人數為 0 人!')
             }
           } else {
-            this.$Message.warning("請先選班級!");
-            this.currentAssignNum = 0;
+            this.$Message.warning('請先選班級!')
+            this.currentAssignNum = 0
           }
-          break;
+          break
 
         case 6:
           {
-            this.currentFilter = -1;
-            this.searchPeriod = "";
-            this.searchGrade = "";
-            this.searchClassroom = "";
+            this.currentFilter = -1
+            this.searchPeriod = ''
+            this.searchGrade = ''
+            this.searchClassroom = ''
             if (this.students.length > this.totalAuth) {
-              this.$Message.warning("全校人數超過所購買授權數,無法使用");
-              this.currentAssignNum = 0;
+              this.$Message.warning('全校人數超過所購買授權數,無法使用')
+              this.currentAssignNum = 0
             } else {
-              this.currentAssignNum = this.students.length;
+              this.currentAssignNum = this.students.length
             }
           }
-          break;
+          break
       }
     },
-    cleanData: function (type) {
+    cleanData: function(type) {
       if (type == 0) {
-        this.currentFilter = 0;
-        this.currentAssignNum = 0;
-        this.currentAssignType = 0; //切換選單就跟隨所選的那一項進行套用
-        this.searchGrade = "";
-        this.searchClassroom = "";
+        this.currentFilter = 0
+        this.currentAssignNum = 0
+        this.currentAssignType = 0 //切換選單就跟隨所選的那一項進行套用
+        this.searchGrade = ''
+        this.searchClassroom = ''
       }
       if (type == 1) {
-        this.currentFilter = 1;
-        this.currentAssignNum = 0;
-        this.currentAssignType = 0;
-        this.searchPeriod = "";
-        this.searchClassroom = "";
+        this.currentFilter = 1
+        this.currentAssignNum = 0
+        this.currentAssignType = 0
+        this.searchPeriod = ''
+        this.searchClassroom = ''
       }
       if (type == 2) {
-        this.currentFilter = 2;
-        this.currentAssignNum = 0;
-        this.currentAssignType = 0;
-        this.searchPeriod = "";
-        this.searchGrade = "";
+        this.currentFilter = 2
+        this.currentAssignNum = 0
+        this.currentAssignType = 0
+        this.searchPeriod = ''
+        this.searchGrade = ''
       }
       //只要選中選擇器,其他兩個就清空
     },
     setCurrentTab(page) {
-      this.currentTab = page;
+      this.setFirstOpenAuth(false)
+      this.currentTab = page
       if (this.currentTab == 0) {
-        this.clickTab = true;
-      } else this.clickTab = false;
+        this.clickTab = true
+      } else {
+        this.clickTab = false
+      }
     },
     close() {
-      this.clickTab = false;
-      this.currentAssignType = 0;
-      this.currentAssignNum = 0;
-      this.$emit("closeAuth", {
-        action: 0,
-      });
+      this.setFirstOpenAuth(false)
+      this.clickTab = false
+      this.currentAssignType = 0
+      this.currentAssignNum = 0
+      this.$emit('closeAuth', {
+        action: 0
+      })
     },
     /**根据学段、年级、班级搜索 */
-    filterData: function () {
-      let data = this.students;
+    filterData: function() {
+      let data = this.students
 
       // 筛选没有关联班级的学生
-      if (this.searchClassroom == "noclass") {
+      if (this.searchClassroom == 'noclass') {
         if (this.searchClassroom) {
-          let id = this.searchClassroom;
-          data = data.filter(function (item) {
-            return item.classId == null;
-          });
+          let id = this.searchClassroom
+          data = data.filter(function(item) {
+            return item.classId == null
+          })
         }
 
-        this.tableFilterData = data;
-        this.pointNum = this.basicCount;
-        this.tableShowData = data.slice(0, this.basicCount);
-        return;
+        this.tableFilterData = data
+        this.pointNum = this.basicCount
+        this.tableShowData = data.slice(0, this.basicCount)
+        return
       }
 
       // 帳號資訊搜尋
       if (this.searchText) {
-        let text = this.searchText;
-        data = data.filter(function (item) {
-          return item.name.indexOf(text) >= 0 || item.id.indexOf(text) >= 0;
-        });
+        let text = this.searchText
+        data = data.filter(function(item) {
+          return item.name.indexOf(text) >= 0 || item.id.indexOf(text) >= 0
+        })
         // console.log(data);
       }
 
       // 學制篩選
       if (this.searchPeriod) {
-        let id = this.searchPeriod;
-        data = data.filter(function (item) {
-          return item.periodId == id;
-        });
+        let id = this.searchPeriod
+        data = data.filter(function(item) {
+          return item.periodId == id
+        })
       }
 
       // 年級篩選
       if (this.searchGrade) {
-        let id = this.searchGrade;
-        data = data.filter(function (item) {
-          return item.gradeId == id;
-        });
+        let id = this.searchGrade
+        data = data.filter(function(item) {
+          return item.gradeId == id
+        })
       }
 
       // 教室篩選
       if (this.searchClassroom) {
-        let id = this.searchClassroom;
-        data = data.filter(function (item) {
-          return item.classId == id;
-        });
+        let id = this.searchClassroom
+        data = data.filter(function(item) {
+          return item.classId == id
+        })
       }
 
-      this.tableFilterData = data;
-      this.pointNum = this.basicCount;
-      this.tableShowData = data.slice(0, this.basicCount);
+      this.tableFilterData = data
+      this.pointNum = this.basicCount
+      this.tableShowData = data.slice(0, this.basicCount)
 
-      console.log("tableFilterData");
-      console.log(this.tableFilterData);
-    },
-  },
-};
+      //console.log('tableFilterData')
+      //console.log(this.tableFilterData)
+    }
+  }
+}
 </script>
 
 <style lang="less">
-@import "./AclassOneAuth.less";
+@import './AclassOneAuth.less';
+
 .aclassone-auth-modal {
   .ivu-modal-header {
     border-radius: 5px 5px 0px 0px !important;

+ 1 - 1
TEAMModelOS/ClientApp/src/view/student-account/AddStudent.vue

@@ -389,7 +389,7 @@
                             }
                         ]
 
-                        if(this.isRepeat(apiData)){
+                        if(this.isRepeat(apiData[0])){
                             this.$Message.error('請檢查 帳號資訊 或 座號重複 了。')
                             this.isLoading = false
                         } else {

+ 132 - 159
TEAMModelOS/ClientApp/src/view/student-account/AuthNumChart.vue

@@ -3,206 +3,179 @@
 </template>
 
 <script>
+import { mapGetters } from 'vuex'
 export default {
-  name: "AuthNumChart",
-  props: ["clickTab", "dbPieNumData"],
+  name: 'AuthNumChart',
+  props: ['clickTab', 'dbPieNumData'],
   data() {
     return {
-      currentColor: "#fff",
+      currentColor: '#fff',
       e: event,
-      index: 0,
-     
-    };
+      index: 0
+    }
   },
 
   mounted() {
-    console.log(this.dbPieNumData);
-    //console.log( this.clickTab)
     setTimeout(
       () => {
-        this.drawLine(this.dbPieNumData);
+        this.drawLine(this.dbPieNumData)
       },
-      this.clickTab == false ? 2700 : 0 //等側邊介面就定位再繪圖,如果是切Tab則直接繪製
-    );
+      this.clickTab == false || this.isFirstOpenAuth == true ? 2300 : 0
+    )
+  },
+  computed: {
+    ...mapGetters({
+      isFirstOpenAuth: 'studentAclassOneAuth/getIsFirstOpenAuth'
+    })
   },
- 
   methods: {
     drawLine(pieNumData) {
       //基于准备好的dom,初始化echarts实例
-      let _this = this;
-      let myChart = this.$echarts.init(
-        document.getElementById("authNum-chart")
-      ); //绘制图表
-      myChart.setOption({
-        tooltip: {
-          trigger: "item",
-          backgroundColor: "rgba(50,50,50,0.9)",
-          borderColor: "#8D8D8D",
-          borderWidth: 1,
-          formatter: function (v) {
-            if (v.color != "#48474c") _this._data.currentColor = v.color;
-            else _this._data.currentColor = "white";
+      let _this = this
+      if (document.getElementById('authNum-chart') !== null) {
+        let myChart = this.$echarts.init(document.getElementById('authNum-chart')) //绘制图表
+        myChart.setOption({
+          tooltip: {
+            trigger: 'item',
+            backgroundColor: 'rgba(50,50,50,0.9)',
+            borderColor: '#8D8D8D',
+            borderWidth: 1,
+            formatter: function(v) {
+              if (v.color != '#48474c') _this._data.currentColor = v.color
+              else _this._data.currentColor = 'white'
 
-            return `
+              return `
             <div class='chart-toolTip'> 
              <p id='color-title'>
              ${v.data.name}</p>
             <p class='value'>${v.value}&nbsp;(${v.percent}%)</p>
              </div> 
-            `;
-          },
-        },
-        /*legend: {
-          show:false,
-          animation:false,
-          orient: "vertical",
-          icon: "circle",
-          top: 40,
-          textStyle: {
-            color: "white",
-          },
-
-          //抓值印在標籤上
-          formatter: function (name) {
-            let data = pieNumData;
-            let total = 0; //回傳自行運算百分比
-            let target;
-            for (let i = 0; i < data.length; i++) {
-              total += data[i].value;
-              if (data[i].name == name) {
-                target = data[i].value;
-              }
+            `
             }
-            let arr = ((target / total) * 100).toFixed(2) + "%";
-            //console.log(arr)
-            return name + " \n " + arr;
           },
-          right: 0,
-        },*/
-        color: ["#eb974e", "#fb62bb", "#00f492"],
 
-        series: [
-          {
-            type: "pie",
-            radius: ["50%", "90%"],
-            //right: 100,
-            avoidLabelOverlap: true,
-            hoverOffset: 0, //調整餅圖凸顯時的高度
-            label: {
-              show: false,
-              position: "center",
+          color: ['#eb974e', '#fb62bb', '#00f492'],
 
-              formatter:
-                "{title1|{b}空間數}\n{num|{c}}{chracter|GB}\n\n{title2|空間占比}\n {percent|{d}}{chracter|%}",
-              align: "left",
-              rich: {
-                title1: { fontWeight: "bold", padding: [2, 15, 2, 0] },
-                title2: { fontWeight: "bold", padding: [2, 63, 2, 0] },
-                chracter: {
-                  fontSize: 10,
-                  color: "white",
-                  padding: [0, 0, 10, 3],
-                },
-                picon: {
-                  color: "white",
-                },
-                percent: {
-                  color: "white",
-                  fontSize: 30,
-                  fontWeight: "bold",
-                },
-                num: {
-                  color: "white",
-                  fontSize: 30,
-                  fontWeight: "bold",
-                  padding: [2, 0, 2, -20],
-                },
-              },
-            },
-            startAngle: 90,
-            emphasis: {
+          series: [
+            {
+              type: 'pie',
+              radius: ['50%', '90%'],
+              //right: 100,
+              avoidLabelOverlap: true,
+              hoverOffset: 0, //調整餅圖凸顯時的高度
               label: {
-                show: false, //把餅圖中間預設顯示各類圖例的效果關掉
-                fontSize: "18",
-                fontWeight: "bold",
-              },
-            },
-            labelLine: {
-              show: false,
-            },
-            itemStyle: {
-              borderColor: "#2b2a2f",
-              borderWidth: 3,
-            },
-            data: this.dbPieNumData,
-          },
-        ],
-      });
+                show: false,
+                position: 'center',
 
-      // Set the default selected highlights
-      myChart.dispatchAction({
-        type: "highlight",
-        seriesIndex: 0,
-        dataIndex: 7,
-      });
-      myChart.on("mousemove", function (e) {
-        //在滑動時修改提示框樣式時的方法!!!
-        document.getElementById("color-title").style.color =
-          _this._data.currentColor;
-      });
+                formatter: '{title1|{b}空間數}\n{num|{c}}{chracter|GB}\n\n{title2|空間占比}\n {percent|{d}}{chracter|%}',
+                align: 'left',
+                rich: {
+                  title1: { fontWeight: 'bold', padding: [2, 15, 2, 0] },
+                  title2: { fontWeight: 'bold', padding: [2, 63, 2, 0] },
+                  chracter: {
+                    fontSize: 10,
+                    color: 'white',
+                    padding: [0, 0, 10, 3]
+                  },
+                  picon: {
+                    color: 'white'
+                  },
+                  percent: {
+                    color: 'white',
+                    fontSize: 30,
+                    fontWeight: 'bold'
+                  },
+                  num: {
+                    color: 'white',
+                    fontSize: 30,
+                    fontWeight: 'bold',
+                    padding: [2, 0, 2, -20]
+                  }
+                }
+              },
+              startAngle: 90,
+              emphasis: {
+                label: {
+                  show: false, //把餅圖中間預設顯示各類圖例的效果關掉
+                  fontSize: '18',
+                  fontWeight: 'bold'
+                }
+              },
+              labelLine: {
+                show: false
+              },
+              itemStyle: {
+                borderColor: '#2b2a2f',
+                borderWidth: 3
+              },
+              data: this.dbPieNumData
+            }
+          ]
+        })
 
-      myChart.on("mouseover", function (e) {
-        //Highlight the hovering piece
-        document.getElementById("color-title").style.color =
-          _this._data.currentColor;
+        // Set the default selected highlights
         myChart.dispatchAction({
-          type: "downplay",
+          type: 'highlight',
           seriesIndex: 0,
-          dataIndex: 0,
-        });
+          dataIndex: 7
+        })
+        myChart.on('mousemove', function(e) {
+          //在滑動時修改提示框樣式時的方法!!!
+          document.getElementById('color-title').style.color = _this._data.currentColor
+        })
 
-        if (e.dataIndex == 0) {
+        myChart.on('mouseover', function(e) {
+          //Highlight the hovering piece
+          document.getElementById('color-title').style.color = _this._data.currentColor
           myChart.dispatchAction({
-            type: "highlight",
+            type: 'downplay',
             seriesIndex: 0,
-            dataIndex: e.dataIndex,
-          });
-        }
-        if (e.dataIndex != this.index) {
+            dataIndex: 0
+          })
+
+          if (e.dataIndex == 0) {
+            myChart.dispatchAction({
+              type: 'highlight',
+              seriesIndex: 0,
+              dataIndex: e.dataIndex
+            })
+          }
+          if (e.dataIndex != this.index) {
+            myChart.dispatchAction({
+              type: 'downplay',
+              seriesIndex: 0,
+              dataIndex: this.index
+            })
+          }
+        })
+        myChart.on('mouseout', function(e) {
+          this.index = e.dataIndex
+          document.getElementById('color-title').style.color = _this._data.currentColor
           myChart.dispatchAction({
-            type: "downplay",
+            type: 'highlight',
             seriesIndex: 0,
-            dataIndex: this.index,
-          });
-        }
-      });
-      myChart.on("mouseout", function (e) {
-        this.index = e.dataIndex;
-        document.getElementById("color-title").style.color =
-          _this._data.currentColor;
-        myChart.dispatchAction({
-          type: "highlight",
-          seriesIndex: 0,
-          dataIndex: this.index,
-        });
-      });
-      //自適應
-      setTimeout(function () {
-        window.onresize = function () {
-          myChart.resize();
-        };
-      }, 200);
-    },
-  },
-};
+            dataIndex: this.index
+          })
+        })
+        //自適應
+        setTimeout(function() {
+          window.onresize = function() {
+            myChart.resize()
+          }
+        }, 100)
+      }
+    }
+  }
+}
 </script>
 
 <style lang="less">
 #authNum-chart {
-  position: relative;
+  width: 300px;
   height: 170px;
   margin-top: 20px;
   margin-bottom: 40px;
+  position:relative;
   z-index: 2;
   .chart-toolTip {
     padding: 10px;

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 364 - 396
TEAMModelOS/ClientApp/src/view/student-account/Index.vue


+ 29 - 0
TEAMModelOS/ClientApp/src/view/teachermgmt/components/userList/Index.less

@@ -25,6 +25,14 @@
                 float: right;
                 color: #ccc;
             }
+            .share-icon{
+                position: relative;
+            top: 0px;
+            -moz-transform: scaleX(-1);
+            -webkit-transform: scaleX(-1);
+            -o-transform: scaleX(-1);
+            transform: scaleX(-1);
+            }
         }
         .userListContent{
             padding-left: 20px;
@@ -155,5 +163,26 @@
         .authBtn{
             padding: 15px;
         }
+       
     }
+    //空間輸入框
+    .input-num{
+        text-align: center;
+         color: #c0c0c0;
+         font-weight: 700;
+         font-size: 14px;
+         outline: none;
+         background-color: #333237;
+         width: 60px;
+         border-radius: 8px;
+         padding:4px 5px;
+         margin:0 5px;
+         top:0px;
+         position: relative;
+         border:#4a4a4d 1px solid !important ;
+         &:focus{
+          border:#909096 1px solid !important ;
+         }
+          
+      }
 }

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1242 - 775
TEAMModelOS/ClientApp/src/view/teachermgmt/components/userList/Index.vue


+ 273 - 0
TEAMModelOS/ClientApp/src/view/teachermgmt/components/userList/SubComponent/SpaceAuth.less

@@ -0,0 +1,273 @@
+.space-auth {
+  .dark-iview-select{
+    margin:0px 10px;
+    position: relative;
+   
+    width: 120px;
+     .ivu-select-selection{
+      outline: none !important;
+      border-radius: 5px !important;
+      background-color: #333237 !important;
+      border:#4a4a4d 1px solid !important ;
+      &:active,&:hover,&:focus{
+        outline: none !important;
+        border:#909096 1px solid !important ;
+       }
+      
+     }
+    
+  }
+  position: absolute;
+  background-color: #2b2a2f;
+  width: 30%;
+  overflow: hidden;
+  //top:120px;
+  //right: 0%;
+  z-index: 22;
+  color: white;
+  padding-left: 20px;
+
+  .title {
+    color: #bab8c0;
+    font-size: 12px;
+    font-weight: 600;
+    padding: 13px 0px;
+    border-bottom: 1px solid #434247;
+  }
+
+  .close-icon {
+    position: absolute;
+    right: 2%;
+    top: 9px;
+    font-size: 24px;
+    cursor: pointer;
+
+    &:hover {
+      color: #1cc0f3;
+    }
+  }
+  .title-btn-group {
+    position: absolute;
+    display: inline-block;
+    right: 50px;
+    top: 8px;
+    .assign-btn {
+      cursor: pointer;
+      position: relative;
+      margin: 0px 5px;
+      line-height: 20px;
+      background-color: #1cc0f3;
+      color: #f5f5f5;
+      text-align: center;
+      display: inline-block;
+      font-size: 10px;
+      padding: 3px 16px;
+      font-weight: 700;
+      border-radius: 3px;
+      &:hover {
+        color: #f5f5f5;
+        transition: 0.2s;
+        background-color: darken(#1cc0f3, 10%);
+      }
+    }
+  }
+  .scroll-sprite {
+    overflow: auto;
+    .controlarea-title{
+      font-size: 10px;
+      font-weight: 400;
+      color: #797979;
+      margin: 25px 6px 30px 6px;   
+    }
+    .small-title {
+      font-size: 10px;
+      font-weight: 400;
+      color: #797979;
+      margin: 25px 6px 15px 6px;
+    }
+
+    .chart-view {
+      animation: 0.5s fadeIn;
+      margin-left: -15%;
+      border-bottom: 1px solid #434247;
+      .chart-title {
+        font-size: 16px;
+        font-weight: 500;
+        text-align: center;
+        color: #777777;
+
+        .chart-totalnum {
+          color: #1cc0f3;
+          font-weight: 700;
+        }
+      }
+
+      .chart-label {
+        margin-bottom: 0px;
+        margin-left: -5%;
+      }
+
+      .chart-item {
+        margin-bottom: 26px;
+
+        .item-ball {
+          display: inline-block;
+          height: 10px;
+          margin-right: 5px;
+          width: 10px;
+          border-radius: 50%;
+          background-color: #fff;
+        }
+
+        .item-text {
+          color: #777777;
+          font-weight: 700;
+          font-size: 10px;
+        }
+
+        .item-num {
+          font-size: 20px;
+          font-weight: 700;
+          letter-spacing: 2px;
+          color: #fff;
+          margin-left: 15px;
+          .small-text {
+            font-size: 12px;
+          }
+        }
+      }
+    }
+    .scroll-infoWrap {
+      padding-top:1px;
+      top: 0px;
+      position: sticky;
+      background-color: #2b2a2f;
+      animation: 0.7s fadeIn;
+      z-index: 2;
+    }
+    .scroll-view {
+      position: relative;
+
+      margin-left: -20px;
+      padding-left: 40px;
+      border-bottom: 1px solid #434247;
+
+      .chart-item {
+        margin-bottom: 26px;
+
+        .item-ball {
+          display: inline-block;
+          height: 10px;
+          margin-right: 5px;
+          width: 10px;
+          border-radius: 50%;
+          background-color: #fff;
+        }
+
+        .item-text {
+          color: #777777;
+          font-weight: 700;
+          font-size: 10px;
+        }
+
+        .item-num {
+          font-size: 20px;
+          font-weight: 600;
+          letter-spacing: 1px;
+          color: #fff;
+          margin-left: 15px;
+          .small-text {
+            font-size: 12px;
+          }
+        }
+      }
+    }
+    .option-item{
+      margin-left:20px;
+      height:48px;
+      .option-text{
+        color: #797979;
+        font-size: 13px;
+        font-weight: 400;
+        line-height: 24px;
+        .strong-text{
+          font-weight: 900 !important;
+          color:  #c0c0c0;
+        }
+      }
+      .input-num{
+        text-align: center;
+         color: #c0c0c0;
+         font-weight: 700;
+         font-size: 14px;
+         outline: none;
+         background-color: #333237;
+         width: 160px;
+         border-radius: 8px;
+         padding:4px 3px;
+         margin:0 5px;
+         top:-4px;
+         position: relative;
+         border:#4a4a4d 1px solid !important ;
+         &:focus{
+          border:#909096 1px solid !important ;
+         }
+          
+      }
+      .assign-btn{
+        
+        cursor: pointer;
+        position: absolute;
+        line-height: 20px;
+       
+        right:40px;
+        background-color: #515151;
+        color: #c0c0c0;
+        text-align: center;
+        display: inline-block;
+        font-size: 10px;
+        padding:2px 16px;
+        font-weight: 700;
+        border-radius: 5px;
+        &:hover {
+          color: #f5f5f5;
+          transition: 0.2s;
+          background-color: #1cc0f3
+        }
+       
+      }  
+    }
+    
+  }
+  @keyframes spaceAuthSlideIn {
+    0% {
+      right: -30%;
+    }
+
+    100% {
+      right: 0%;
+    }
+  }
+  @keyframes spaceAuthSlideOut {
+    0% {
+      right: 0%;
+    }
+
+    100% {
+      right: -30%;
+    }
+  }
+  @keyframes fadeIn {
+    0% {
+      opacity: 0;
+    }
+    100% {
+      opacity: 1;
+    }
+  }
+  
+}
+.closeShow {
+  animation: spaceAuthSlideOut 0.7s !important;
+  right: 30%;
+}

+ 453 - 0
TEAMModelOS/ClientApp/src/view/teachermgmt/components/userList/SubComponent/SpaceAuth.vue

@@ -0,0 +1,453 @@
+<template>
+  <div class="space-auth" :class="{ closeShow: closefromBtn == true }">
+    <!--收回授權弹窗 -->
+    <Modal class="space-auth-modal" v-model="isOpenRemove" :title="'確定收回所有空間'">
+      <p>確定後全部每位教師都直接設置為0GB。</p>
+
+      <div slot="footer">
+        <Button type="text" @click="isOpenRemove = false">取消</Button>
+        <Button type="primary" @click="resetAllSpace()">確定</Button>
+      </div>
+    </Modal>
+    <div class="title">
+      分配教學空間<span @click="closeAuth()"><v-icon class="close-icon" iconClass="close"/></span>
+      <div class="title-btn-group">
+        <div class="assign-btn" @click="isOpenRemove = true">回收空間</div>
+        <div class="assign-btn" @click="confirmSave()">保存變更</div>
+      </div>
+    </div>
+    <div id="scrollTarget" class="scroll-sprite" :style="{ height: tableHeight + 12 + 'px' }">
+      <p class="small-title">空間分配狀態</p>
+      <Row class="chart-view">
+        <i-col :xs="24" :sm="12" :md="12" :lg="15" align="center">
+          <p class="chart-title">
+            總空間量:<span class="chart-totalnum">{{ numberWithCommas(2500) }} GB</span>
+          </p>
+          <SpaceStatusChart />
+        </i-col>
+        <i-col :xs="24" :sm="12" :md="12" :lg="9" class="chart-label">
+          <div class="chart-item" v-for="(item, index) in pieNumData" :key="index">
+            <div class="item-ball" :style="{ 'background-color': color[index] }" />
+            <span class="item-text">{{ item.name }}</span>
+            <p class="item-num">
+              {{ numberWithCommas(item.value) }}
+              <span class="small-text">GB</span>
+            </p>
+          </div>
+        </i-col>
+      </Row>
+
+      <div>
+        <!--目前只需要單人-->
+        <div>
+          <p class="controlarea-title">單人固定容量分配 (目前可被分配總量:{{ getMaxFixedAssignedValue }}GB)</p>
+
+          <Row class="option-item">
+            <p class="option-text">
+              每人分配
+              <input step="1" @keydown="preventDot" type="number" placeholder="-" min="1" class="input-num" v-model="multiAssignValue" />
+              GB至
+            </p>
+          </Row>
+          <Row class="option-item">
+            <i-col :xs="24" :sm="12" :md="12" :lg="16">
+              <p class="option-text">當前勾選之教師帳號</p>
+            </i-col>
+            <i-col :xs="24" :sm="12" :md="12" :lg="8">
+              <div class="assign-btn" @click="setSelectedSpace()">套用</div>
+            </i-col>
+          </Row>
+          <Row class="option-item">
+            <i-col :xs="24" :sm="12" :md="12" :lg="16">
+              <p class="option-text">所有<span class="strong-text">進階權限</span>之教師帳號</p>
+            </i-col>
+            <i-col :xs="24" :sm="12" :md="12" :lg="8">
+              <div class="assign-btn" @click="setAdvancedPerMissionSpace(0)">
+                套用
+              </div>
+            </i-col>
+          </Row>
+          <Row class="option-item">
+            <i-col :xs="24" :sm="12" :md="12" :lg="16">
+              <p class="option-text">所有<span class="strong-text">一般權限</span>之教師帳號</p>
+            </i-col>
+            <i-col :xs="24" :sm="12" :md="12" :lg="8">
+              <div class="assign-btn" @click="setAdvancedPerMissionSpace(1)">
+                套用
+              </div>
+            </i-col>
+          </Row>
+
+          <Row class="option-item">
+            <i-col :xs="24" :sm="12" :md="12" :lg="16">
+              <p class="option-text">
+                職稱為
+                <Select v-model="selectJob" class="dark-iview-select">
+                  <Option :value="item" v-for="(item, index) in this.jobs" :key="index">{{ item == null ? '教師' : item }}</Option></Select
+                >的教師帳號
+              </p>
+            </i-col>
+            <i-col :xs="24" :sm="12" :md="12" :lg="8">
+              <div class="assign-btn" @click="setJobSpace()">套用</div>
+            </i-col>
+          </Row>
+          <Row class="option-item">
+            <i-col :xs="24" :sm="12" :md="12" :lg="16">
+              <p class="option-text">所有教師帳號</p>
+            </i-col>
+            <i-col :xs="24" :sm="12" :md="12" :lg="8">
+              <div class="assign-btn" @click="setAllSpace()">套用</div>
+            </i-col>
+          </Row>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import SpaceStatusChart from './SpaceStatusChart.vue'
+import { mapGetters, mapMutations } from 'vuex'
+import '@/icons/svg/close.svg'
+import CountTo from 'vue-count-to'
+import { forEach } from 'jszip'
+
+export default {
+  name: 'SpaceAuth',
+  components: {
+    SpaceStatusChart,
+    CountTo
+  },
+  props: ['closefromBtn', 'tableHeight'],
+
+  data() {
+    return {
+      selectJob: '教師',
+      areaScrollTop: 0,
+      multiAssignValue: '-',
+      color: ['#eb974e', '#fb62bb', '#00f492'],
+      isOpenRemove: false,
+      sentParams: {
+        schoolCode: '',
+        teachers: [{ id: '', size: 0 }]
+      }
+    }
+  },
+  computed: {
+    ...mapGetters({
+      pieNumData: 'spaceAuth/getPieNumData',
+      getMockTeacherSpace: 'spaceAuth/getMockTeacherSpace',
+      getCurrentSelectedTeacherId: 'spaceAuth/getCurrentSelectedTeacherId',
+      getMaxFixedAssignedValue: 'spaceAuth/getMaxFixedAssignedValue'
+    }),
+    advancedAcountNum: function() {
+      let num = 0
+      this.getMockTeacherSpace.forEach(item => {
+        if (item.permissionCount > 0) {
+          num++
+        }
+      })
+      return num
+    },
+    normalAcountNum: function() {
+      let num = 0
+      this.getMockTeacherSpace.forEach(item => {
+        if (item.permissionCount == 0) {
+          num++
+        }
+      })
+      return num
+    },
+    jobs: function() {
+      //撈出職稱
+      let temp = []
+      this.getMockTeacherSpace.forEach(element => {
+        if (temp.includes(element.job) == false && element.job != null) {
+          temp.push(element.job)
+        }
+      })
+      temp.push('教師')
+      console.log(temp)
+      return temp
+    }
+  },
+  watch: {
+    multiAssignValue: function(val) {
+      //input初步過濾負數與英文
+      if (val == '') {
+        this.$Message.warning('請手動輸入數字!')
+        this.multiAssignValue = '-'
+      }
+      //輸入的數字為浮點數
+      if (this.isFloat(parseFloat(val)) == true) {
+        this.$Message.warning('輸入了小數點,目前最小單位為1GB!')
+
+        this.multiAssignValue = this.multiAssignValue.replace('.', '')
+      }
+      //輸入的數字為N.0
+      if (val[val.length - 1] == '0' && val[val.length - 2] == '.') {
+        this.$Message.warning('輸入了小數點,目前最小單位為1GB!')
+        this.multiAssignValue = this.multiAssignValue.replace('.', '')
+      }
+
+      //輸入的數字開頭為0
+      if (this.isFloat(parseFloat(val)) == false && val.length > 1 && val[0] == 0) {
+        this.$Message.warning('輸入了開頭為0的數字,請重新輸入!')
+        this.multiAssignValue = '-'
+      }
+    }
+  },
+  methods: {
+    ...mapMutations({
+      setMockSpaceZero: 'spaceAuth/setMockSpaceZero',
+      setPieNumDataZero: 'spaceAuth/setPieNumDataZero',
+      setMockTeacherSpace: 'spaceAuth/setMockTeacherSpace', //把每位老師的假資料暫存在vuex
+      setPieNumData: 'spaceAuth/setPieNumData',
+      setCurrentVariableValue: 'spaceAuth/setCurrentVariableValue',
+      setMultiAssignCurrentVariableValue: 'spaceAuth/setMultiAssignCurrentVariableValue',
+      setPieNumDataMultiAssign: 'spaceAuth/setPieNumDataMultiAssign',
+      setAssignSelectVariableValue: 'spaceAuth/setAssignSelectVariableValue',
+      setCurrentSelectedTeacherId: 'spaceAuth/setCurrentSelectedTeacherId'
+    }),
+    confirmSave() {
+      this.sentParams.schoolCode = this.$store.state.user.schoolCode
+      this.sentParams.teachers = [
+        {
+          id: '1608805222',
+          size: 8
+        }
+      ]
+      this.$store.dispatch('spaceAuth/setTeacherSpace', this.sentParams).then(
+        res => {
+          //this.isloading = false
+          this.$Message.success('保存成功')
+        },
+        err => {
+          this.$Message.error('保存失敗')
+        }
+      )
+    },
+    setAllSpace() {
+      if (this.multiAssignValue != '-') {
+        if (this.multiAssignValue > this.getMaxFixedAssignedValue / this.getMockTeacherSpace.length) {
+          this.$Message.error('超出剩餘空間量,套用失敗')
+          this.multiAssignValue = '-'
+        } else {
+          this.getMockTeacherSpace.forEach(element => {
+            element.space = this.multiAssignValue
+            //暫定已模擬連續輸入時先直接寫入DB
+            element.spaceInitValue = this.multiAssignValue
+          })
+
+          this.setCurrentVariableValue(this.getMockTeacherSpace)
+          this.setMultiAssignCurrentVariableValue(this.multiAssignValue * this.getMockTeacherSpace.length)
+          this.setPieNumDataMultiAssign()
+          this.$Message.success('套用完成')
+          this.multiAssignValue = '-'
+          this.$emit('reload') //重新整理
+        }
+      } else {
+        this.$Message.warning('先輸入有效數字,再進行套用')
+      }
+      this.$emit('reload') //重新整理
+    },
+    setJobSpace() {
+      let jobFitNum = 0
+      let unchangedUsingSpace = 0
+      if (this.multiAssignValue != '-') {
+        //選擇老師
+        if (this.selectJob == '教師') {
+          this.getMockTeacherSpace.forEach(element => {
+            if (element.job == null) {
+              jobFitNum++
+            }
+          })
+          this.getMockTeacherSpace.forEach(element => {
+            if (element.job != null) {
+              unchangedUsingSpace += parseInt(element.space)
+            }
+          })
+        }
+        //選擇非老師
+        if (this.selectJob != '教師') {
+          this.getMockTeacherSpace.forEach(element => {
+            if (element.job == this.selectJob) {
+              jobFitNum++
+            }
+          })
+          this.getMockTeacherSpace.forEach(element => {
+            if (element.job != this.selectJob) {
+              unchangedUsingSpace += parseInt(element.space)
+            }
+          })
+        }
+
+        if (this.multiAssignValue * jobFitNum + unchangedUsingSpace > this.getMaxFixedAssignedValue) {
+          this.$Message.error('超出剩餘空間量,套用失敗')
+
+          this.multiAssignValue = '-'
+        } else {
+          this.getMockTeacherSpace.forEach(element => {
+            if (element.job == null && this.selectJob == '教師') {
+              element.space = this.multiAssignValue
+              //暫定已模擬連續輸入時先直接寫入DB
+              element.spaceInitValue = this.multiAssignValue
+            }
+            if (element.job == this.selectJob) {
+              element.space = this.multiAssignValue
+              //暫定已模擬連續輸入時先直接寫入DB
+              element.spaceInitValue = this.multiAssignValue
+            }
+          })
+
+          this.setAssignSelectVariableValue(this.getMockTeacherSpace)
+          this.setPieNumDataMultiAssign()
+          this.$Message.success('套用完成')
+          this.multiAssignValue = '-'
+          this.$emit('reload') //重新整理
+        }
+      } else {
+        this.$Message.warning('先輸入有效數字,再進行套用')
+      }
+      jobFitNum = 0
+      unchangedUsingSpace = 0
+      this.$emit('reload') //重新整理
+    },
+    setSelectedSpace() {
+      if (this.multiAssignValue != '-' && this.getCurrentSelectedTeacherId.length != 0) {
+        let unchangedUsingSpace = 0
+        this.getMockTeacherSpace.forEach(element => {
+          if (this.getCurrentSelectedTeacherId.includes(element.id) == false) {
+            unchangedUsingSpace += parseInt(element.space)
+          }
+        })
+        console.log(unchangedUsingSpace)
+        if (unchangedUsingSpace + this.multiAssignValue * this.getCurrentSelectedTeacherId.length > this.getMaxFixedAssignedValue) {
+          this.$Message.error('超出剩餘空間量,套用失敗')
+          this.multiAssignValue = '-'
+          this.$emit('reload') //重新整理
+        } else {
+          this.getMockTeacherSpace.forEach(element => {
+            if (this.getCurrentSelectedTeacherId.includes(element.id)) {
+              element.space = this.multiAssignValue
+              //暫定已模擬連續輸入時先直接寫入DB
+              element.spaceInitValue = this.multiAssignValue
+            }
+          })
+
+          this.setAssignSelectVariableValue(this.getMockTeacherSpace)
+          this.setPieNumDataMultiAssign()
+          this.multiAssignValue = '-'
+          this.setCurrentSelectedTeacherId([])
+          this.$Message.success('套用完成')
+          this.$emit('reload') //重新整理
+        }
+      } else {
+        this.$Message.warning('先輸入有效數字,並進行勾選,再套用')
+        this.multiAssignValue = '-'
+        this.setCurrentSelectedTeacherId([])
+      }
+      this.$emit('reload') //重新整理
+    },
+    setAdvancedPerMissionSpace(type) {
+      if (this.multiAssignValue != '-') {
+        //辨識是否超過總量
+        let unchangedUsingSpace = 0
+        this.getMockTeacherSpace.forEach(element => {
+          if (type == 0 ? element.permissionCount > 0 : element.permissionCount == 0) {
+            unchangedUsingSpace += parseInt(element.space)
+          }
+        })
+        if (unchangedUsingSpace + this.multiAssignValue * this.advancedAcountNum > this.getMaxFixedAssignedValue) {
+          this.$Message.error('超出剩餘空間量,套用失敗')
+          this.multiAssignValue = '-'
+          this.$emit('reload') //重新整理
+        } else {
+          //辨識可以發派時的情況
+          this.getMockTeacherSpace.forEach(element => {
+            if (type == 0 ? element.permissionCount > 0 : element.permissionCount == 0) {
+              element.space = this.multiAssignValue
+              //暫定已模擬連續輸入時先直接寫入DB
+              element.spaceInitValue = this.multiAssignValue
+            }
+          })
+
+          this.setAssignSelectVariableValue(this.getMockTeacherSpace)
+          this.setPieNumDataMultiAssign()
+          this.multiAssignValue = '-'
+          this.setCurrentSelectedTeacherId([])
+          this.$Message.success('套用完成')
+          this.$emit('reload') //重新整理
+        }
+      } else {
+        this.$Message.warning('先輸入有效數字,再套用')
+        this.multiAssignValue = '-'
+        this.setCurrentSelectedTeacherId([])
+      }
+      this.$emit('reload') //重新整理
+    },
+
+    handleScroll(e) {
+      // Any code to be executed when the window is scrolled
+      console.log('目前區塊捲動高度:' + e.currentTarget.scrollTop)
+      this.areaScrollTop = e.currentTarget.scrollTop
+    },
+    numberWithCommas(x) {
+      if (x != undefined) {
+        return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
+      }
+    },
+    closeAuth() {
+      this.$emit('closeSpaceAuth')
+      this.$emit('reload') //重新整理
+    },
+    resetAllSpace() {
+      this.setMockSpaceZero()
+      this.setPieNumDataZero()
+      this.$Message.success('回收成功!')
+      this.$emit('reload') //重新整理
+      this.isOpenRemove = false
+    },
+    isFloat(n) {
+      return n === +n && n !== (n | 0)
+    },
+    preventDot(e) {
+      if (e.keyCode == 190 || e.charCode == 46) {
+        //console.log(true)
+        e.preventDefault()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less">
+@import './SpaceAuth.less';
+.space-auth-modal {
+  .ivu-modal-header {
+    border-radius: 5px 5px 0px 0px !important;
+  }
+  .ivu-modal-footer {
+    border-radius: 0px 0px 5px 5px !important;
+  }
+
+  .ivu-modal-header,
+  .ivu-modal-body,
+  .ivu-modal-footer {
+    background-color: rgb(88, 88, 88) !important;
+    color: white !important;
+    border: none !important;
+  }
+  .ivu-btn-text {
+    color: white;
+    &:hover {
+      background-color: transparent !important;
+      color: #1cc0f3;
+    }
+  }
+  .ivu-modal-header-inner {
+    color: white !important;
+    font-weight: bolder !important;
+  }
+}
+</style>

+ 237 - 0
TEAMModelOS/ClientApp/src/view/teachermgmt/components/userList/SubComponent/SpaceStatusChart.vue

@@ -0,0 +1,237 @@
+<template>
+  <div id="spaceStatus-chart"></div>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+export default {
+  name: "SpaceStatusChart",
+
+  data() {
+    return {
+      currentColor: "#fff",
+      e: event,
+      index: 0,
+    };
+  },
+
+  mounted() {
+    setTimeout(() => {
+      this.drawLine(this.pieNumData);
+    }, 2000);
+  },
+  computed: {
+    ...mapGetters({
+      pieNumData: "spaceAuth/getPieNumData",
+    }),
+  },
+  watch: {
+    pieNumData: {
+      handler(val) {
+        if (val !== null) {
+          this.drawLine(val);
+        }
+      },
+      deep: true,
+    },
+  },
+
+  methods: {
+    drawLine(pieNumData) {
+      //基于准备好的dom,初始化echarts实例
+      let _this = this;
+      let myChart = this.$echarts.init(
+        document.getElementById("spaceStatus-chart")
+      ); //绘制图表
+      myChart.setOption({
+        tooltip: {
+          position: [130, 90],
+          trigger: "item",
+          backgroundColor: "rgba(50,50,50,0.9)",
+          borderColor: "#8D8D8D",
+          borderWidth: 1,
+          formatter: function (v) {
+            if (v.color != "#48474c") _this._data.currentColor = v.color;
+            else _this._data.currentColor = "white";
+
+            return `
+            <div class='chart-toolTip'> 
+             <p id='color-title'>
+             ${v.data.name}</p>
+            <p class='value'>${v.data.value}&nbsp;GB (${v.percent}%)</p>
+             </div> 
+            `;
+          },
+        },
+        /*legend: {
+          show:false,
+          animation:false,
+          orient: "vertical",
+          icon: "circle",
+          top: 40,
+          textStyle: {
+            color: "white",
+          },
+
+          //抓值印在標籤上
+          formatter: function (name) {
+            let data = pieNumData;
+            let total = 0; //回傳自行運算百分比
+            let target;
+            for (let i = 0; i < data.length; i++) {
+              total += data[i].value;
+              if (data[i].name == name) {
+                target = data[i].value;
+              }
+            }
+            let arr = ((target / total) * 100).toFixed(2) + "%";
+            //console.log(arr)
+            return name + " \n " + arr;
+          },
+          right: 0,
+        },*/
+        color: ["#eb974e", "#fb62bb", "#00f492"],
+
+        series: [
+          {
+            type: "pie",
+            radius: ["50%", "90%"],
+            //right: 100,
+            avoidLabelOverlap: true,
+            hoverOffset: 0, //調整餅圖凸顯時的高度
+            label: {
+              show: false,
+              position: "center",
+
+              formatter:
+                "{title1|{b}空間數}\n{num|{c}}{chracter|GB}\n\n{title2|空間占比}\n {percent|{d}}{chracter|%}",
+              align: "left",
+              rich: {
+                title1: { fontWeight: "bold", padding: [2, 15, 2, 0] },
+                title2: { fontWeight: "bold", padding: [2, 63, 2, 0] },
+                chracter: {
+                  fontSize: 10,
+                  color: "white",
+                  padding: [0, 0, 10, 3],
+                },
+                picon: {
+                  color: "white",
+                },
+                percent: {
+                  color: "white",
+                  fontSize: 30,
+                  fontWeight: "bold",
+                },
+                num: {
+                  color: "white",
+                  fontSize: 30,
+                  fontWeight: "bold",
+                  padding: [2, 0, 2, -20],
+                },
+              },
+            },
+            startAngle: 90,
+            emphasis: {
+              label: {
+                show: false, //把餅圖中間預設顯示各類圖例的效果關掉
+                fontSize: "18",
+                fontWeight: "bold",
+              },
+            },
+            labelLine: {
+              show: false,
+            },
+            itemStyle: {
+              borderColor: "#2b2a2f",
+              borderWidth: 3,
+            },
+            data: this.pieNumData,
+          },
+        ],
+      });
+
+      // Set the default selected highlights
+      myChart.dispatchAction({
+        type: "highlight",
+        seriesIndex: 0,
+        dataIndex: 7,
+      });
+      myChart.on("mousemove", function (e) {
+        //在滑動時修改提示框樣式時的方法!!!
+        document.getElementById("color-title").style.color =
+          _this._data.currentColor;
+      });
+
+      myChart.on("mouseover", function (e) {
+        //Highlight the hovering piece
+        document.getElementById("color-title").style.color =
+          _this._data.currentColor;
+        myChart.dispatchAction({
+          type: "downplay",
+          seriesIndex: 0,
+          dataIndex: 0,
+        });
+
+        if (e.dataIndex == 0) {
+          myChart.dispatchAction({
+            type: "highlight",
+            seriesIndex: 0,
+            dataIndex: e.dataIndex,
+          });
+        }
+        if (e.dataIndex != this.index) {
+          myChart.dispatchAction({
+            type: "downplay",
+            seriesIndex: 0,
+            dataIndex: this.index,
+          });
+        }
+      });
+      myChart.on("mouseout", function (e) {
+        this.index = e.dataIndex;
+        document.getElementById("color-title").style.color =
+          _this._data.currentColor;
+        myChart.dispatchAction({
+          type: "highlight",
+          seriesIndex: 0,
+          dataIndex: this.index,
+        });
+      });
+      //自適應
+      setTimeout(function () {
+        window.onresize = function () {
+          myChart.resize();
+        };
+      }, 200);
+    },
+  },
+};
+</script>
+
+<style lang="less">
+#spaceStatus-chart {
+  position: relative;
+  height: 150px;
+  margin-top: 20px;
+
+  margin-bottom: 40px;
+  z-index: 22;
+  .chart-toolTip {
+    position: relative;
+    z-index: 99;
+    padding: 10px;
+    #color-title {
+      font-weight: bolder;
+    }
+    .title {
+      font-size: 10px;
+      color: gray;
+      font-weight: bolder;
+    }
+    .value {
+      font-weight: bolder;
+      color: white;
+    }
+  }
+}
+</style>

+ 44 - 14
TEAMModelOS/Controllers/Client/HiTeachController.cs

@@ -162,7 +162,7 @@ namespace TEAMModelOS.Controllers.Client
                 var id = jwt.Payload.Sub;
 
                 var client = _azureCosmos.GetCosmosClient();
-   
+
                 //取得學校學段、年級、科目
                 List<object> periods = new List<object>();
                 List<object> grades = new List<object>();
@@ -210,6 +210,31 @@ namespace TEAMModelOS.Controllers.Client
                 }
 
                 //該老師排定的學校課程
+                Dictionary<string, int> classStuDic = new Dictionary<string, int>(); //所有班級學生數
+                var querycst = $"SELECT (IS_ARRAY(c.students)) ? ARRAY_LENGTH(c.students) : 0 as stuCnt, c.id FROM c";
+                await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: querycst, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
+                {
+                    var jsoncst = await JsonDocument.ParseAsync(item.ContentStream);
+                    foreach (var obj in jsoncst.RootElement.GetProperty("Documents").EnumerateArray())
+                    {
+                        string classId = obj.GetProperty("id").GetString();
+                        int stuCnt = obj.GetProperty("stuCnt").GetInt32();
+                        classStuDic.Add(classId, stuCnt);
+                    }
+                }
+                Dictionary<string, int> classGrpDic = new Dictionary<string, int>(); //所有班級分組數
+                var querycgp = $"SELECT Count(1) as grpCnt, c.id FROM c JOIN(SELECT DISTINCT t.groupId FROM t IN c.students WHERE t.groupId != null) GROUP BY c.id";
+                await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: querycgp, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
+                {
+                    var jsoncgp = await JsonDocument.ParseAsync(item.ContentStream);
+                    foreach (var obj in jsoncgp.RootElement.GetProperty("Documents").EnumerateArray())
+                    {
+                        string classId = obj.GetProperty("id").GetString();
+                        int grpCnt = obj.GetProperty("grpCnt").GetInt32();
+                        classGrpDic.Add(classId, grpCnt);
+                    }
+                }
+
                 Dictionary<string, object> schoolCoursesDic = new Dictionary<string, object>(); //課程與科目對應表
                 var querysc = $"SELECT c.id, c.name, c.subject FROM c";
                 await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: querysc, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Course-{school_code}") }))
@@ -243,6 +268,8 @@ namespace TEAMModelOS.Controllers.Client
                             classExtobj.name = obj.GetProperty("name").ToString();
                             classExtobj.teacher = obj.GetProperty("teacher").ToObject<object>();
                             classExtobj.scope = obj.GetProperty("scope").ToString();
+                            classExtobj.stuCnt = (classStuDic.ContainsKey(classIdNow)) ? classStuDic[classIdNow] : 0;
+                            classExtobj.grpCnt = (classGrpDic.ContainsKey(classIdNow)) ? classGrpDic[classIdNow] : 0;
                             //課程
                             obj.TryGetProperty("course", out JsonElement courseNow);
                             string courseIdNow = courseNow.GetProperty("id").ToString();
@@ -256,7 +283,6 @@ namespace TEAMModelOS.Controllers.Client
                                 courseExtobj.classes = new List<object>();
                                 courseExtobj.subject = schoolCoursesDic[courseIdNow];
                                 courseExtobj.classes.Add(classExtobj);
-                                courseExtobj.subject = schoolCoursesDic[courseIdNow];
                                 courses.Add(courseExtobj);
                             }
                             else
@@ -629,29 +655,33 @@ namespace TEAMModelOS.Controllers.Client
 
             var client = _azureCosmos.GetCosmosClient();
             List<object> students = new List<object>();
-            var query = $"SELECT cs.id, cs.name, cs.no FROM c JOIN cs IN c.students WHERE c.id = '{class_code}' AND c.scope = '{grant_type}'";
+            var query = $"SELECT c.students FROM c WHERE c.id = '{class_code}' AND c.scope = '{grant_type}'";
             string pk = (grant_type.GetString() == "school") ? $"Class-{school_code}" : $"Class-{id}";
             string container = (grant_type.GetString() == "school") ? "School" : "Teacher";
             await foreach (var item in client.GetContainer("TEAMModelOS", container).GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey(pk) }))
             {
-                using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                var json = await JsonDocument.ParseAsync(item.ContentStream);
                 if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
                 {
-                    foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                    foreach (var classObj in json.RootElement.GetProperty("Documents").EnumerateArray())
                     {
-                        students.Add(obj.ToObject<object>());
+                        foreach (var studentObj in classObj.GetProperty("students").EnumerateArray())
+                        {
+                            dynamic studentsExtobj = new ExpandoObject();
+                            studentsExtobj.id = studentObj.GetProperty("id");
+                            studentsExtobj.name = studentObj.GetProperty("name");
+                            studentsExtobj.no = studentObj.GetProperty("no");
+                            students.Add(studentsExtobj);
+                        }
                     }
                 }
-            }
-            if(students.Count > 0)
-            {
-                return Ok(new { students });
-            }
-            else
-            {
-                return Ok(new { error = 1, message = "No class found!" });
+                else
+                {
+                    return Ok(new { error = 1, message = "No class found!" });
+                }
             }
 
+            return Ok(new { students });
         }
 
         [ProducesDefaultResponseType]

+ 117 - 1
TEAMModelOS/Controllers/School/ClassRoomController.cs

@@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using System;
 using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
 using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
@@ -183,7 +185,7 @@ namespace TEAMModelOS.Controllers
                         }
 
                     }
-                    
+
                 }
                 return Ok(new { classroom });
             }
@@ -431,6 +433,120 @@ namespace TEAMModelOS.Controllers
             return Ok();*/
             //await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").CreateItemAsync("", new PartitionKey($"Class-{code}"));
         }
+
+        [ProducesDefaultResponseType]
+        //[AuthToken(Roles = "Teacher")]
+        [HttpPost("hiteach-link")]
+        public async Task<IActionResult> HiteachLink(JsonElement request)
+        {
+            // 必要檢查
+            if (!request.TryGetProperty("school_code", out JsonElement code)) return BadRequest();
+            if (!request.TryGetProperty("linkList", out JsonElement links)) return BadRequest();
+
+            try
+            {
+                if (links.GetArrayLength() > 0) 
+                {
+                    // [變數宣告]
+                    string school_code = code.ToString(); // 學校簡碼
+                    JsonElement.ArrayEnumerator linkLists = request.GetProperty("linkList").EnumerateArray(); // 需要修改的資料
+                    int link_length = links.GetArrayLength(); // 需要修改的筆數
+
+                    // [取得DB資料]
+                    var client = _azureCosmos.GetCosmosClient();
+                    var response = await client.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(school_code, new PartitionKey("Product"));
+
+                    if (response.Status == 200)
+                    {
+                        var json = await JsonDocument.ParseAsync(response.ContentStream);
+
+                        //軟體
+                        SchoolProduct schoolProductItem = json.ToObject<SchoolProduct>();
+
+                        foreach (var linkList in linkLists)
+                        {
+                            var uuidBoolean = linkList.TryGetProperty("uuid", out var a);
+                            var uuid = a.GetString();
+
+                            var uuid2Boolean = linkList.TryGetProperty("uuid2", out var b);
+                            var uuid2 = b.GetString();
+
+                            var classIdBoolean = linkList.TryGetProperty("classId", out var c);
+                            var classId = c.GetString();
+
+                            var idBoolean = linkList.TryGetProperty("id", out var d);
+                            var id = d.GetString();
+
+                            SerialInfoBaseWithdeviceBound updSerialInfo = schoolProductItem.serial.Where(s => s.id == id).FirstOrDefault();
+                            if (updSerialInfo != null)
+                            {
+                                deviceBound updDeviceBound = updSerialInfo.deviceBound.Where(d => d.uuid == uuid && d.uuid2 == uuid2).FirstOrDefault();
+                                updDeviceBound.classId = classId;
+                            }
+                        }
+
+                        await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<SchoolProduct>(schoolProductItem, school_code, new PartitionKey("Product"));
+                    }
+                }
+                    
+                return Ok(new { error = 0 });
+            }
+            catch (Exception ex)
+            {
+               return BadRequest();
+            }
+        }
+
+        [ProducesDefaultResponseType]
+        //[AuthToken(Roles = "Teacher")]
+        [HttpPost("hiteach-unlink-classId")]
+        public async Task<IActionResult> HiteachUnlinkByClassId(JsonElement request)
+        {
+            // 必要檢查
+            if (!request.TryGetProperty("school_code", out JsonElement code)) return BadRequest();
+            if (!request.TryGetProperty("classId", out JsonElement id)) return BadRequest();
+
+            try
+            {
+                // [變數宣告]
+                string school_code = code.ToString(); // 學校簡碼
+                string classId = id.ToString(); // 教室ID
+
+
+                // [取得DB資料]
+                var client = _azureCosmos.GetCosmosClient();
+                var response = await client.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(school_code, new PartitionKey("Product"));
+
+                if (response.Status == 200)
+                {
+                    var json = await JsonDocument.ParseAsync(response.ContentStream);
+
+                    //軟體
+                    SchoolProduct schoolProductItem = json.ToObject<SchoolProduct>();
+
+                    foreach (SerialInfoBaseWithdeviceBound updSerialInfo in schoolProductItem.serial) 
+                    {
+                        if (updSerialInfo.deviceBound != null) 
+                        {
+                            deviceBound updDeviceBound = updSerialInfo.deviceBound.Where(d => d.classId == classId).FirstOrDefault();
+                            if (updDeviceBound != null)
+                            {
+                                updDeviceBound.classId = null;
+                            }
+                        }
+                        
+                    }
+
+                    await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<SchoolProduct>(schoolProductItem, school_code, new PartitionKey("Product"));
+                }
+
+                return Ok(new { error = 0 });
+            }
+            catch (Exception ex)
+            {
+                return BadRequest();
+            }
+        }
         [ProducesDefaultResponseType]
         //[AuthToken(Roles = "Teacher")]
         [HttpPost("name")]

+ 6 - 4
TEAMModelOS/Controllers/School/CourseController.cs

@@ -19,6 +19,7 @@ using TEAMModelOS.SDK.DI.AzureCosmos.Inner;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Helper.Common.CollectionHelper;
 using TEAMModelOS.SDK.Helper.Common.StringHelper;
+using System.Dynamic;
 
 namespace TEAMModelOS.Controllers
 {
@@ -223,7 +224,8 @@ namespace TEAMModelOS.Controllers
                 sql.Append("select value(c) from c ");
                 Dictionary<string, object> dict = new Dictionary<string, object>();
                 //处理id
-                if (request.ids.IsNotEmpty()) {
+                if (request.ids.IsNotEmpty())
+                {
                     dict.Add("id", request.ids.ToArray());
                 }
                 string school_code = request.code;
@@ -317,7 +319,7 @@ namespace TEAMModelOS.Controllers
             {
                 CourseManagement course = new CourseManagement();
                 //course = room.ToObject<CourseManagement>();
-                var client = _azureCosmos.GetCosmosClient();                
+                var client = _azureCosmos.GetCosmosClient();
                 string code = requert.code;
                 requert.code = "CourseManagement-" + requert.code;
                 /*                if (requert.scope.Equals) { 
@@ -330,7 +332,7 @@ namespace TEAMModelOS.Controllers
                     CourseManagement courseManagement = json.ToObject<CourseManagement>();
                     courseManagement.courses = requert.courses;*/
                     //return Ok(new { error = ResponseCode.DATA_EXIST, V = "课程编码已经存在!" });
-                    course = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync(requert,requert.id, new PartitionKey($"CourseManagement-{code}"));
+                    course = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync(requert, requert.id, new PartitionKey($"CourseManagement-{code}"));
 
                 }
                 else
@@ -339,7 +341,7 @@ namespace TEAMModelOS.Controllers
 
                 }
 
-                    
+
                 return Ok(new { course });
             }
             catch (Exception ex)

+ 619 - 46
TEAMModelOS/Controllers/School/SchoolController.cs

@@ -19,6 +19,7 @@ using System.Net.Http;
 using System.Net;
 using Newtonsoft.Json;
 using System.Linq;
+using StackExchange.Redis;
 
 namespace TEAMModelOS.Controllers
 {
@@ -32,12 +33,17 @@ namespace TEAMModelOS.Controllers
 
         public AzureCosmosFactory _azureCosmos;
         private readonly AzureStorageFactory _azureStorage;
+        private readonly AzureRedisFactory _azureRedis;
         private readonly DingDing _dingDing;
         private readonly Option _option;
-        public SchoolController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, DingDing dingDing, IOptionsSnapshot<Option> option)
+        int baseSpaceSize = 1; //學校保底空間大小(1G)
+        private readonly double bytes = 1073741824;
+
+        public SchoolController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, DingDing dingDing, IOptionsSnapshot<Option> option)
         {
             _azureCosmos = azureCosmos;
             _azureStorage = azureStorage;
+            _azureRedis = azureRedis;
             _dingDing = dingDing;
             _option = option?.Value;
         }
@@ -55,20 +61,19 @@ namespace TEAMModelOS.Controllers
             try
             {
                 School schoolInfo = new School();
-                var client = _azureCosmos.GetCosmosClient();                
-                var response = await client.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(requert.id, new PartitionKey($"Base"));
+                var client = _azureCosmos.GetCosmosClient();
+                var schoolContainer = client.GetContainer("TEAMModelOS", "School");
+                var response = await schoolContainer.ReadItemStreamAsync(requert.id, new PartitionKey($"Base"));
                 if (response.Status == 200)
                 {
-                     
-                     schoolInfo = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").UpsertItemAsync(requert, new PartitionKey($"Base"));
+                    schoolInfo = await schoolContainer.UpsertItemAsync(requert, new PartitionKey($"Base"));
                 }
                 else
                 {
-                      requert.code = "Base";
-                      schoolInfo = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").CreateItemAsync(requert, new PartitionKey($"Base"));
-                    
+                    requert.code = "Base";
+                    schoolInfo = await schoolContainer.CreateItemAsync(requert, new PartitionKey($"Base"));
                 }
-                return Ok(new  { schoolInfo });
+                return Ok(new { schoolInfo });
             }
             catch (Exception ex)
             {
@@ -182,9 +187,10 @@ namespace TEAMModelOS.Controllers
             if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
 
             var client = _azureCosmos.GetCosmosClient();
+            var schoolContainer = client.GetContainer("TEAMModelOS", "School");
             //取得學校學制、年級、教室
             List<object> classes = new List<object>();
-            await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name, c.gradeId FROM c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
+            await foreach (var item in schoolContainer.GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name, c.gradeId FROM c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
             {
                 var jsonc = await JsonDocument.ParseAsync(item.ContentStream);
                 foreach (var classeinfo in jsonc.RootElement.GetProperty("Documents").EnumerateArray())
@@ -198,7 +204,7 @@ namespace TEAMModelOS.Controllers
             }
             List<object> periods = new List<object>();
             List<object> grades = new List<object>();
-            var responsesch = await client.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(school_code.ToString(), new PartitionKey($"Base"));
+            var responsesch = await schoolContainer.ReadItemStreamAsync(school_code.ToString(), new PartitionKey($"Base"));
             if (responsesch.Status == 200)
             {
                 var jsons = await JsonDocument.ParseAsync(responsesch.ContentStream);
@@ -237,14 +243,14 @@ namespace TEAMModelOS.Controllers
         public async Task<IActionResult> GetSchoolProductInfo(JsonElement request)
         {
             if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
+            var clientContainer = _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School");
 
-            var client = _azureCosmos.GetCosmosClient();
             List<deviceBoundRich> serialDeviceUpdList = new List<deviceBoundRich>(); //要更新DB的序號內容
             List<SerialInfoBaseWithdeviceBoundExt> serial = new List<SerialInfoBaseWithdeviceBoundExt>(); //最後要輸出的序號結果
             List<object> service = new List<object>();
             List<object> hard = new List<object>();
             List<deviceForCoreService> uuidList = new List<deviceForCoreService>(); //要向CoreService詢問deviceID及硬體資訊的UUID列表
-            var response = await client.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(school_code.ToString(), new PartitionKey("Product"));
+            var response = await clientContainer.ReadItemStreamAsync(school_code.ToString(), new PartitionKey("Product"));
             if (response.Status == 200)
             {
                 var json = await JsonDocument.ParseAsync(response.ContentStream);
@@ -254,7 +260,7 @@ namespace TEAMModelOS.Controllers
                     foreach (var serialInfo in serialJobj.EnumerateArray())
                     {
                         serial.Add(serialInfo.ToObject<SerialInfoBaseWithdeviceBoundExt>());
-                        if(serialInfo.TryGetProperty("deviceBound", out JsonElement deviceBoundJobj) && !string.IsNullOrWhiteSpace(deviceBoundJobj.ToString()) )
+                        if (serialInfo.TryGetProperty("deviceBound", out JsonElement deviceBoundJobj) && !string.IsNullOrWhiteSpace(deviceBoundJobj.ToString()))
                         {
                             foreach (var deviceBoundRow in deviceBoundJobj.EnumerateArray())
                             {
@@ -307,8 +313,6 @@ namespace TEAMModelOS.Controllers
                     serialRow.deviceBound = deviceBoundArray;
                 }
 
-
-
                 //服務
                 if (json.RootElement.TryGetProperty("service", out JsonElement serviceJobj))
                 {
@@ -346,12 +350,24 @@ namespace TEAMModelOS.Controllers
                         }
                     }
                     //計算各產品時間區域
+                    bool buySpaceFlg = false;
+                    ////取得學校空間使用狀況
+                    var blobClient = _azureStorage.GetBlobContainerClient(school_code.ToString());
+                    long? docSize = await blobClient.GetBlobsSize("doc");
+                    long? videoSize = await blobClient.GetBlobsSize("video");
+                    long? imageSize = await blobClient.GetBlobsSize("image");
+                    long? paperSize = await blobClient.GetBlobsSize("paper");
+                    long? itemSize = await blobClient.GetBlobsSize("item");
+                    long? otherSize = await blobClient.GetBlobsSize("other");
+                    long? studentSize = await blobClient.GetBlobsSize("student");
+                    long teacherSize = 0;
                     if (serviceJobj.TryGetProperty("product", out JsonElement serviceProductJobj))
                     {
+
                         foreach (var serviceProductRow in serviceProductJobj.EnumerateArray())
                         {
                             ServiceProductResult serviceProductResultRow = CalServiceProductAuth(activeMainPeriod, activePeriodOfMain, serviceProductRow.ToObject<ServiceProduct>());
-                            if(!string.IsNullOrWhiteSpace(serviceProductResultRow.prodCode))
+                            if (!string.IsNullOrWhiteSpace(serviceProductResultRow.prodCode))
                             {
                                 switch (serviceProductResultRow.prodCode)
                                 {
@@ -364,27 +380,27 @@ namespace TEAMModelOS.Controllers
                                         serviceProductAclassoneResult.startDate = serviceProductResultRow.startDate;
                                         serviceProductAclassoneResult.endDate = serviceProductResultRow.endDate;
                                         serviceProductAclassoneResult.avaliable = serviceProductResultRow.avaliable;
-                                        serviceProductAclassoneResult.used = 0;
-                                        serviceProductAclassoneResult.less = 0;
-                                        if (json.RootElement.TryGetProperty("aclassone", out JsonElement aclassoneJobj))
+                                        serviceProductAclassoneResult.staUsed = 0;
+                                        serviceProductAclassoneResult.dynUsed = 0;
+                                        if (json.RootElement.TryGetProperty("aclassone", out JsonElement aclassoneJobj) && aclassoneJobj.ValueKind != JsonValueKind.Null)
                                         {
-                                            serviceProductAclassoneResult.used = (aclassoneJobj.TryGetProperty("used", out JsonElement usedJobj)) ? usedJobj.GetInt32() : 0;
-                                            serviceProductAclassoneResult.less = (aclassoneJobj.TryGetProperty("less", out JsonElement lessJobj)) ? lessJobj.GetInt32() : 0;
+                                            int total = (aclassoneJobj.TryGetProperty("total", out JsonElement totalJobj)) ? totalJobj.GetInt32() : 0;
+                                            serviceProductAclassoneResult.staUsed = (aclassoneJobj.TryGetProperty("used", out JsonElement usedJobj)) ? usedJobj.GetInt32() : 0; //固定分配數
+                                            List<string> dyncIdList = GetSchoolDynamicAclassOneIDList(school_code.GetString()); //動態使用ID
+                                            serviceProductAclassoneResult.dynUsed = dyncIdList.Count;
                                         }
                                         service.Add(serviceProductAclassoneResult);
                                         break;
                                     case "IPALYEIY": //智慧教學服務空間
                                         ////取得目前使用狀況
-                                        var blobClient = _azureStorage.GetBlobContainerClient(school_code.ToString());
                                         //取得教師分配空間
-                                        long teacherBlobSize = 0;
                                         var query = $"SELECT SUM(c.size) as size FROM c";
-                                        await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{school_code}") }))
+                                        await foreach (var item in clientContainer.GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{school_code}") }))
                                         {
                                             var jsonts = await JsonDocument.ParseAsync(item.ContentStream);
                                             foreach (var obj in jsonts.RootElement.GetProperty("Documents").EnumerateArray())
                                             {
-                                                teacherBlobSize = obj.GetProperty("size").GetInt64() * 1073741824;//G換算成bytes
+                                                teacherSize = obj.GetProperty("size").GetInt64() * 1073741824;//G換算成bytes
                                             }
                                         }
                                         //欄位取得
@@ -394,17 +410,18 @@ namespace TEAMModelOS.Controllers
                                         serviceSpaceProductInfo.serviceType = serviceProductResultRow.serviceType;
                                         serviceSpaceProductInfo.startDate = serviceProductResultRow.startDate;
                                         serviceSpaceProductInfo.endDate = serviceProductResultRow.endDate;
-                                        serviceSpaceProductInfo.avaliable = (long)serviceProductResultRow.avaliable * 1073741824; //G換算成bytes
-                                        serviceSpaceProductInfo.doc = await blobClient.GetBlobsSize("doc");
-                                        serviceSpaceProductInfo.video = await blobClient.GetBlobsSize("video");
-                                        serviceSpaceProductInfo.image = await blobClient.GetBlobsSize("image");
-                                        serviceSpaceProductInfo.paper = await blobClient.GetBlobsSize("paper");
-                                        serviceSpaceProductInfo.item = await blobClient.GetBlobsSize("item");
-                                        serviceSpaceProductInfo.other = await blobClient.GetBlobsSize("other");
-                                        serviceSpaceProductInfo.student = await blobClient.GetBlobsSize("student");
-                                        serviceSpaceProductInfo.teacher = teacherBlobSize;
+                                        serviceSpaceProductInfo.avaliable = (long)(serviceProductResultRow.avaliable + this.baseSpaceSize) * 1073741824; //G換算成bytes
+                                        serviceSpaceProductInfo.doc = docSize;
+                                        serviceSpaceProductInfo.video = videoSize;
+                                        serviceSpaceProductInfo.image = imageSize;
+                                        serviceSpaceProductInfo.paper = paperSize;
+                                        serviceSpaceProductInfo.item = itemSize;
+                                        serviceSpaceProductInfo.other = otherSize;
+                                        serviceSpaceProductInfo.student = studentSize;
+                                        serviceSpaceProductInfo.teacher = teacherSize;
                                         serviceSpaceProductInfo.history = CalServiceProductOrderHistory(historyMainPeriod, historyPeriodOfMain, serviceProductRow.ToObject<ServiceProduct>()); ////購買紀錄
                                         service.Add(serviceSpaceProductInfo);
+                                        buySpaceFlg = true;
                                         break;
                                     default:
                                         service.Add(serviceProductResultRow);
@@ -413,10 +430,31 @@ namespace TEAMModelOS.Controllers
                             }
                         }
                     }
+                    //學校保底空間追加
+                    if (!buySpaceFlg) //未購買任何空間
+                    {
+                        dynamic serviceSpaceProductInfo = new ExpandoObject();
+                        serviceSpaceProductInfo.prodCode = "IPALYEIY";
+                        serviceSpaceProductInfo.noperiod = false;
+                        serviceSpaceProductInfo.serviceType = "space";
+                        serviceSpaceProductInfo.startDate = 0;
+                        serviceSpaceProductInfo.endDate = 0;
+                        serviceSpaceProductInfo.avaliable = this.baseSpaceSize * 1073741824; //1G換算成bytes
+                        serviceSpaceProductInfo.doc = docSize;
+                        serviceSpaceProductInfo.video = videoSize;
+                        serviceSpaceProductInfo.image = imageSize;
+                        serviceSpaceProductInfo.paper = paperSize;
+                        serviceSpaceProductInfo.item = itemSize;
+                        serviceSpaceProductInfo.other = otherSize;
+                        serviceSpaceProductInfo.student = studentSize;
+                        serviceSpaceProductInfo.teacher = teacherSize;
+                        serviceSpaceProductInfo.history = null;
+                        service.Add(serviceSpaceProductInfo);
+                    }
                 }
 
                 //硬體
-                if (json.RootElement.TryGetProperty("hard", out JsonElement hardJobj))
+                if (json.RootElement.TryGetProperty("hard", out JsonElement hardJobj) && hardJobj.ValueKind != JsonValueKind.Null)
                 {
                     hard.Add(hardJobj.ToObject<object>());
                 }
@@ -425,13 +463,13 @@ namespace TEAMModelOS.Controllers
                 if (serialDeviceUpdList.Count > 0)
                 {
                     SchoolProduct schoolProductItem = json.ToObject<SchoolProduct>();
-                    foreach(deviceBoundRich serialDeviceUpdRow in serialDeviceUpdList)
+                    foreach (deviceBoundRich serialDeviceUpdRow in serialDeviceUpdList)
                     {
                         SerialInfoBaseWithdeviceBound updSerialInfo = schoolProductItem.serial.Where(s => s.serial == serialDeviceUpdRow.serial).FirstOrDefault();
                         deviceBound updDeviceBound = updSerialInfo.deviceBound.Where(d => d.uuid == serialDeviceUpdRow.uuid && d.uuid2 == serialDeviceUpdRow.uuid2).FirstOrDefault();
                         updDeviceBound.deviceId = serialDeviceUpdRow.deviceId;
                     }
-                    await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<SchoolProduct>(schoolProductItem, schoolProductItem.id, new PartitionKey("Product"));
+                    await clientContainer.ReplaceItemAsync<SchoolProduct>(schoolProductItem, schoolProductItem.id, new PartitionKey("Product"));
                 }
 
             }
@@ -483,16 +521,17 @@ namespace TEAMModelOS.Controllers
             {
                 foreach (ServiceProductAuth serviceProductAuthRow in serviceProduct.auth)
                 {
-                    if(!string.IsNullOrWhiteSpace(serviceProductAuthRow.periodId) && !existPeriodIdList.Contains(serviceProductAuthRow.periodId))
+                    if (!string.IsNullOrWhiteSpace(serviceProductAuthRow.periodId) && !existPeriodIdList.Contains(serviceProductAuthRow.periodId))
                     {
                         existPeriodIdList.Add(serviceProductAuthRow.periodId);
                     }
                 }
             }
             List<ServicePeriod> periodOrder = periodList.Where(p => existPeriodIdList.Contains(p.periodId)).OrderBy(p => Int32.Parse(p.periodId)).ToList();
-            
+
             //combine period
-            if (periodOrder.Count() > 0) {
+            if (periodOrder.Count() > 0)
+            {
 
                 //篩選出主週期(複數active主週期對策) ※最終篩出唯一一組 篩選原則:(1)由此產品有的副週期ID篩出 (2)若還有複數筆,則期間長者勝出
                 List<string> existMainPeriodIdList = new List<string>();
@@ -511,10 +550,10 @@ namespace TEAMModelOS.Controllers
                 periodZoneResult.startDate = (periodZone[0].startDate > mainPeriod.startDate) ? periodZone[0].startDate : mainPeriod.startDate;
                 periodZoneResult.endDate = (periodZone[0].endDate < mainPeriod.endDate) ? periodZone[0].endDate : mainPeriod.endDate;
             }
-            
+
             //return
             ServiceProductResult serviceProductResult = new ServiceProductResult();
-            if((serviceProduct.noperiod && periodZoneResult.startDate == 0 && periodZoneResult.endDate == 0) || (!serviceProduct.noperiod && periodZoneResult.startDate > 0 && periodZoneResult.endDate > 0))
+            if ((serviceProduct.noperiod && periodZoneResult.startDate == 0 && periodZoneResult.endDate == 0) || (!serviceProduct.noperiod && periodZoneResult.startDate > 0 && periodZoneResult.endDate > 0))
             {
                 serviceProductResult.prodCode = serviceProduct.prodCode;
                 serviceProductResult.noperiod = serviceProduct.noperiod;
@@ -545,9 +584,9 @@ namespace TEAMModelOS.Controllers
                 foreach (ServiceProductAuth serviceProductAuthRow in serviceProduct.auth)
                 {
                     ServiceProductAuthHistory existServiceProductAuthHistory = serviceProductOrderList.Where(a => a.orderId == serviceProductAuthRow.orderId).FirstOrDefault();
-                    if(existServiceProductAuthHistory != null)
+                    if (existServiceProductAuthHistory != null)
                     {
-                        if(!existServiceProductAuthHistory.periodIdList.Contains(serviceProductAuthRow.periodId))
+                        if (!existServiceProductAuthHistory.periodIdList.Contains(serviceProductAuthRow.periodId))
                         {
                             existServiceProductAuthHistory.periodIdList.Add(serviceProductAuthRow.periodId);
                         }
@@ -567,7 +606,7 @@ namespace TEAMModelOS.Controllers
                         serviceProductOrderList.Add(serviceProductAuthHistoryRow);
                     }
                 }
-                
+
                 foreach (ServiceProductAuthHistory serviceProductOrderRow in serviceProductOrderList)
                 {
                     ServiceProductAuthHistoryStartEnd ServiceProductAuthHistoryStartEndRow = new ServiceProductAuthHistoryStartEnd();
@@ -627,5 +666,539 @@ namespace TEAMModelOS.Controllers
             }
             return periodZone;
         }
+
+        /// <summary>
+        /// 取得某學校AClassOne授權及使用資料 
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("get-school-aclassone")]
+        public async Task<IActionResult> GetSchoolAclassoneInfo(JsonElement request)
+        {
+            if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
+
+            var clientContainer = _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School");
+            var response = await clientContainer.ReadItemStreamAsync(school_code.ToString(), new PartitionKey("Product"));
+            int total = 0; //可分配總數
+            int staNum = 0; //固定分配數
+            int dyncNum = 0; //動態分配數
+            List<string> staIds = new List<string>(); //固定分配學生ID列
+            List<string> dyncIds = new List<string>(); //動態分配學生ID列
+            if (response.Status == 200)
+            {
+                var json = await JsonDocument.ParseAsync(response.ContentStream);
+                if (json.RootElement.TryGetProperty("service", out JsonElement serviceJobj))
+                {
+                    //取得active主週期
+                    List<ServiceMainPeriod> activeMainPeriod = new List<ServiceMainPeriod>();
+                    if (serviceJobj.TryGetProperty("mainperiod", out JsonElement mainperiodJobj))
+                    {
+                        foreach (var mainperiodRow in mainperiodJobj.EnumerateArray())
+                        {
+                            if (mainperiodRow.GetProperty("active").GetBoolean())
+                            {
+                                activeMainPeriod.Add(mainperiodRow.ToObject<ServiceMainPeriod>());
+                            }
+                        }
+                    }
+                    //取得active主週期的副週期
+                    List<ServicePeriod> activePeriodOfMain = new List<ServicePeriod>();
+                    if (serviceJobj.TryGetProperty("period", out JsonElement periodJobj))
+                    {
+                        foreach (var periodRow in periodJobj.EnumerateArray())
+                        {
+                            foreach (ServiceMainPeriod mainPeriodRow in activeMainPeriod)
+                            {
+                                if (periodRow.GetProperty("mainPeriodId").ToString() == mainPeriodRow.mainPeriodId)
+                                {
+                                    activePeriodOfMain.Add(periodRow.ToObject<ServicePeriod>());
+                                }
+                            }
+                        }
+                    }
+                    //取得AClassOne可用數量
+                    if (serviceJobj.TryGetProperty("product", out JsonElement serviceProductJobj))
+                    {
+                        foreach (var serviceProductRow in serviceProductJobj.EnumerateArray())
+                        {
+                            //AClassOne買斷、週期
+                            string prodCode = serviceProductRow.GetProperty("prodCode").GetString();
+                            if (prodCode == "RYGVCPLY" || prodCode == "AEGMCPLY")
+                            {
+                                ServiceProductResult serviceProductResultRow = CalServiceProductAuth(activeMainPeriod, activePeriodOfMain, serviceProductRow.ToObject<ServiceProduct>());
+                                total += serviceProductResultRow.avaliable;
+                            }
+                        }
+                    }
+                    //取得AClassOne固定分配數、動態分配數、固定分配學生ID
+                    if (json.RootElement.TryGetProperty("aclassone", out JsonElement aclassoneJobj))
+                    {
+                        if (aclassoneJobj.ValueKind != JsonValueKind.Null)
+                        {
+                            if (aclassoneJobj.TryGetProperty("ids", out JsonElement ids) && ids.ValueKind != JsonValueKind.Null)
+                            {
+                                staIds = System.Text.Json.JsonSerializer.Deserialize<List<string>>(ids.ToJsonString());
+                                staNum = staIds.Count;
+                            }
+                        }
+                        dyncIds = GetSchoolDynamicAclassOneIDList(school_code.GetString());
+                        dyncNum = dyncIds.Count;
+                    }
+                }
+            }
+
+            return Ok(new { total, staNum, dyncNum, staIds, dyncIds });
+        }
+
+        /// <summary>
+        /// 取得某學校AClassOne動態可使用數 
+        /// </summary>
+        public int GetSchoolDynamicAclassOne(string schoolCode)
+        {
+            try
+            {
+                int result = 0;
+                string key = schoolCode + ":" + "AclassOne" + ":" + "dynamic";
+                var redisClient = _azureRedis.GetRedisClient(8);
+                var dyncount = redisClient.StringGet(key);
+                if (redisClient.KeyExists(key))
+                {
+                    result = Int32.Parse(redisClient.StringGet(key));
+                }
+                return result;
+            }
+            catch (Exception ex)
+            {
+                return 0;
+            }
+        }
+        /// <summary>
+        /// 取得某學校AClassOne動態已取用的ID List 
+        /// </summary>
+        public List<string> GetSchoolDynamicAclassOneIDList(string schoolCode)
+        {
+            string key = schoolCode + ":" + "AclassOne" + ":" + "dynamicIds";
+            var redisClient = _azureRedis.GetRedisClient(8);
+            RedisValue[] redisResult = redisClient.SetMembers(key);
+            List<string> result = Array.ConvertAll(redisResult, x => (string)x).ToList();
+            return result;
+        }
+
+        public long GetSchoolDynamicAclassOneIDCount(string schoolCode)
+        {
+            string key = schoolCode + ":" + "AclassOne" + ":" + "dynamicIds";
+            var redisClient = _azureRedis.GetRedisClient(8);
+            long result = redisClient.SetLength(key);
+            return result;
+        }
+
+        /// <summary>
+        /// [測試用]放入某學校的AClassOne動態學生ID 
+        /// </summary>
+        [ProducesDefaultResponseType]
+        [HttpPost("set-school-aclassone-dync")]
+        public IActionResult SetSchoolAclassoneDync(JsonElement request)
+        {
+            if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
+            request.TryGetProperty("student_id", out JsonElement student_id);
+            var redisClient = _azureRedis.GetRedisClient(8);
+            string key1 = school_code + ":" + "AclassOne" + ":" + "dynamicIds";
+            RedisValue[] redisIdsOriginal = redisClient.SetMembers(key1);
+            var resultIdsOriginal = Array.ConvertAll(redisIdsOriginal, x => (string)x).ToList();
+            redisClient.SetAdd(key1, student_id.GetString());
+            RedisValue[] redisIds = redisClient.SetMembers(key1);
+            var resultIds = Array.ConvertAll(redisIds, x => (string)x).ToList();
+            string key2 = school_code + ":" + "AclassOne" + ":" + "dynamic";
+            if (resultIds.Count > resultIdsOriginal.Count)
+            {
+                redisClient.StringDecrement(key2, 1);
+            }
+            string resultCount = redisClient.StringGet(key2);
+            return Ok(new { resultIds, resultCount });
+        }
+
+        /// <summary>
+        /// 設定某學校AClassOne固定分配授權學生
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("set-school-aclasson-sta")]
+        public async Task<IActionResult> SetSchoolAclassoneStatic(JsonElement request)
+        {
+            if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
+            if (!request.TryGetProperty("student_ids", out JsonElement student_ids)) return BadRequest();
+            try
+            {
+                List<string> studentIds = System.Text.Json.JsonSerializer.Deserialize<List<string>>(student_ids.ToJsonString()); //新的學生ID列
+
+                /* ERROR CODE定義:
+                 * 0: 無錯誤
+                 * 1: AClassOne授權數不足或無AClassOne授權
+                 */
+                int status = 0;
+                string err = "";
+                var clientContainer = _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School");
+                var response = await clientContainer.ReadItemStreamAsync(school_code.ToString(), new PartitionKey("Product"));
+                if (response.Status == 200)
+                {
+                    int total = 0; //(回傳值)可分配總數
+                    int staNum = 0; //(回傳值)固定分配數
+                    int dyncNum = 0; //(回傳值)動態分配數
+                    List<string> staIds = new List<string>(); //(回傳值)固定學生ID列
+                    List<string> dyncIds = new List<string>(); //(回傳值)動態學生ID列
+                    using (Stream stream = response.ContentStream)
+                    {
+                        using (StreamReader streamReader = new StreamReader(stream))
+                        {
+                            string content = streamReader.ReadToEnd();
+                            SchoolProduct schoolProduct = System.Text.Json.JsonSerializer.Deserialize<SchoolProduct>(content);
+                            if (schoolProduct.aclassone != null) //有AClassOne產品授權,schoolProduct.aclassone不會為null
+                            {
+                                total = schoolProduct.aclassone.total;
+                                List<string> studentIdsInDB = schoolProduct.aclassone.ids; //舊的學生ID列
+                                int totalInDB = schoolProduct.aclassone.total;
+                                //STEP1 驗證所給的ID數是否超過AClassOne總數
+                                if (studentIds.Count > totalInDB)
+                                {
+                                    status = 1;
+                                    err = "AclassOne numbers is not enouth.";
+                                    return Ok(new { status, err });
+                                }
+                                //STEP2 將學生ID放入 School.Product.aclassone.ids,更新aclassone.used
+                                staIds = schoolProduct.aclassone.ids = studentIds;
+                                staNum = schoolProduct.aclassone.used = studentIds.Count;
+                                ////Update
+                                await clientContainer.ReplaceItemAsync<SchoolProduct>(schoolProduct, schoolProduct.id, new PartitionKey("Product"));
+                                //STEP3更新Redis.hbcn:AclassOne:dynamic ※註:只更新動態ID可用數,不更動動態ID列表,所以若可用數為0,當天被動態分配到的學生仍可使用至今日0時才會被清掉
+                                int dynamicAclassCount = totalInDB - studentIds.Count; //動態可使用數
+                                dyncNum = (int)GetSchoolDynamicAclassOneIDCount(school_code.GetString()); //動態ID已使用數
+                                var redisClient = _azureRedis.GetRedisClient(8);                        
+                                string keys = school_code.GetString() + ":" + "AclassOne" + ":" + "dynamic";
+                                int updDynAclassCount = (dynamicAclassCount - dyncNum > 0) ? dynamicAclassCount - dyncNum : 0;
+                                redisClient.StringSet(keys, updDynAclassCount);
+                                string keyd = school_code.GetString() + ":" + "AclassOne" + ":" + "dynamicIds";
+                                RedisValue[] redisIds = redisClient.SetMembers(keyd);
+                                dyncIds = Array.ConvertAll(redisIds, x => (string)x).ToList();
+
+                                return Ok(new { status, err, total, staNum, dyncNum, staIds, dyncIds });
+                            }
+                            else
+                            {
+                                status = 1;
+                                err = "AClassOne has no authorization.";
+                                return Ok(new { status, err });
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    return BadRequest();
+                }
+            }
+            catch (Exception ex)
+            {
+                return BadRequest();
+            }
+        }
+
+        /// <summary>
+        /// 回收某學校AClassOne固定分配、動態分配授權學生
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("recall-school-aclasson")]
+        public async Task<IActionResult> RecallSchoolAclassone(JsonElement request)
+        {
+            if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
+            string action = "all"; //預設回收:固定及動態均回收  sta:回收固定 dync:回收動態
+            if (request.TryGetProperty("mode", out JsonElement mode))
+            {
+                if(mode.GetString() == "sta" || mode.GetString() == "dync")
+                {
+                    action = mode.GetString();
+                }
+            }
+            int status = 0;
+            string err = "";
+            var redisClient = _azureRedis.GetRedisClient(8);
+            string keys = school_code.GetString() + ":" + "AclassOne" + ":" + "dynamic";
+            string keyd = school_code.GetString() + ":" + "AclassOne" + ":" + "dynamicIds";
+            var clientContainer = _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School");
+            var response = await clientContainer.ReadItemStreamAsync(school_code.ToString(), new PartitionKey("Product"));
+            if (response.Status == 200)
+            {
+                int total = 0; //(回傳值)可分配總數
+                int staNum = 0; //(回傳值)固定分配數
+                int dyncNum = 0; //(回傳值)動態分配數
+                List<string> staIds = new List<string>(); //(回傳值)固定學生ID列
+                List<string> dyncIds = new List<string>(); //(回傳值)動態學生ID列
+                using (Stream stream = response.ContentStream)
+                {
+                    using (StreamReader streamReader = new StreamReader(stream))
+                    {
+                        string content = streamReader.ReadToEnd();
+                        SchoolProduct schoolProduct = System.Text.Json.JsonSerializer.Deserialize<SchoolProduct>(content);
+                        if (schoolProduct.aclassone != null) //有AClassOne產品授權,schoolProduct.aclassone不會為null
+                        {
+                            total = schoolProduct.aclassone.total;
+                            staNum = schoolProduct.aclassone.used;
+                            staIds = schoolProduct.aclassone.ids;
+                            dyncNum = (int)GetSchoolDynamicAclassOneIDCount(school_code.GetString());
+                            dyncIds = GetSchoolDynamicAclassOneIDList(school_code.GetString());
+                            switch (action)
+                            {
+                                case "sta":
+                                    staIds = schoolProduct.aclassone.ids = new List<string>();
+                                    staNum = schoolProduct.aclassone.used = 0;
+                                    await clientContainer.ReplaceItemAsync<SchoolProduct>(schoolProduct, schoolProduct.id, new PartitionKey("Product"));
+                                    int updDynAclassCount = (total - dyncNum > 0) ? total - dyncNum : 0;
+                                    redisClient.StringSet(keys, updDynAclassCount);
+                                    break;
+                                case "dync": //動態回收
+                                    dyncNum = 0;
+                                    dyncIds = new List<string>();
+                                    redisClient.StringSet(keys, total - staNum);
+                                    redisClient.KeyDelete(keyd);
+                                    break;
+                                case "all": //全回收
+                                    staIds = schoolProduct.aclassone.ids = new List<string>();
+                                    staNum = schoolProduct.aclassone.used = 0;
+                                    await clientContainer.ReplaceItemAsync<SchoolProduct>(schoolProduct, schoolProduct.id, new PartitionKey("Product"));
+                                    dyncNum = 0;
+                                    dyncIds = new List<string>();
+                                    redisClient.StringSet(keys, total);
+                                    redisClient.KeyDelete(keyd);
+                                    break;
+                            }
+                            return Ok(new { status, err, total, staNum, dyncNum, staIds, dyncIds });
+                        }
+                        else
+                        {
+                            status = 1;
+                            err = "AClassOne has no authorization.";
+                            return Ok(new { status, err });
+                        }
+                    }
+                }
+            }
+            else
+            {
+                return BadRequest();
+            }
+        }
+
+        /// <summary>
+        /// 教師空間
+        /// </summary>
+        [ProducesDefaultResponseType]
+        [HttpPost("teacher-space")]
+        public async Task<IActionResult> TeacherSpace(JsonElement request)
+        {
+            // 必要檢查
+            if (!request.TryGetProperty("school_code", out JsonElement code)) return BadRequest();
+            if (!request.TryGetProperty("action", out JsonElement type)) return BadRequest();
+
+            try
+            {
+                // [變數宣告]
+                string school_code = code.ToString(); // 學校簡碼
+                string action = type.ToString();  // 功能分類
+                string queryText = ""; // 統一sql文記憶體
+
+                // response
+                string status = "0";
+                string err = "";
+                List<string> errTeachers = new List<string>();
+
+                //[學校空間]
+                var blobClient = _azureStorage.GetBlobContainerClient(school_code.ToString());
+                long? docSize = await blobClient.GetBlobsSize("doc");
+                long? videoSize = await blobClient.GetBlobsSize("video");
+                long? imageSize = await blobClient.GetBlobsSize("image");
+                long? paperSize = await blobClient.GetBlobsSize("paper");
+                long? itemSize = await blobClient.GetBlobsSize("item");
+                long? otherSize = await blobClient.GetBlobsSize("other");
+                long? studentSize = await blobClient.GetBlobsSize("student");
+                int avaliable = this.baseSpaceSize; // (G) 目前學校申請空間總數
+                int teacherSpace = 0; // 教師可設定的空間量(G)
+                int usedSpace = 0;//已被使用的空間量(G)
+
+                // 取得學校目前的總空間數(G)
+                queryText = $"SELECT p.prodCode, p.avaliable FROM c JOIN p IN c.service.product WHERE c.id = '{school_code}' AND p.prodCode = 'IPALYEIY'";
+                await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: queryText, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Product") }))
+                {
+                    using var jsoncm = await JsonDocument.ParseAsync(item.ContentStream);
+                    if (jsoncm.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        foreach (var obj in jsoncm.RootElement.GetProperty("Documents").EnumerateArray())
+                        {
+                            avaliable += obj.GetProperty("avaliable").GetInt32();
+                        }
+
+                        docSize = docSize != null ? docSize : 0;
+                        videoSize = videoSize != null ? videoSize : 0;
+                        imageSize = imageSize != null ? imageSize : 0;
+                        paperSize = paperSize != null ? paperSize : 0;
+                        itemSize = itemSize != null ? itemSize : 0;
+                        otherSize = otherSize != null ? otherSize : 0;
+                        studentSize = studentSize != null ? studentSize : 0;
+                        usedSpace = Convert.ToInt32(Math.Ceiling((double)((docSize + videoSize + imageSize + paperSize + itemSize + otherSize + studentSize) / bytes)));
+                        teacherSpace = avaliable - usedSpace;
+                    }
+                }
+
+                switch (action)
+                {
+                    case "baseSpace": // 現在的教師空間
+                        return Ok(new { 
+                            status, 
+                            avaliable = avaliable.ToString(),
+                            usedSpace = usedSpace.ToString()
+                        });
+                    case "upd": // 修改教師空間
+                        // [二次檢核]
+                        if (!request.TryGetProperty("teachers", out JsonElement list)) return BadRequest();
+                        if (list.GetArrayLength() == 0) return BadRequest();
+
+                        // 教師設定總數
+                        int setSpaceTotal = 0;
+
+                        // 教師List
+                        JsonElement.ArrayEnumerator teachers = request.GetProperty("teachers").EnumerateArray();
+                        List<string> ids = new List<string>();
+                        foreach (var obj in teachers) { 
+                            ids.Add(obj.GetProperty("id").ToString());
+                            setSpaceTotal += String.IsNullOrEmpty(obj.GetProperty("size").ToString()) ?  0 : Convert.ToInt32(obj.GetProperty("size").ToString());
+                        }
+
+                        // 檢核總數是否超過
+                        if (setSpaceTotal > teacherSpace)
+                        {
+                            status = "1"; // 要設定的空間總數大於目前學校有的
+                            err = $"設定總量({setSpaceTotal}G)超過目前空間({teacherSpace}G)";
+                            return Ok(new { status, err });
+                        }
+                        
+                        queryText = $"select * from c where c.id in ({string.Join(",", ids.Select(o => $"'{o}'"))})";
+
+                        await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: queryText, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{school_code}") }))
+                        {
+                            using var jsoncm = await JsonDocument.ParseAsync(item.ContentStream);
+                            if (jsoncm.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0 && count.GetInt16() == list.GetArrayLength())
+                            {
+
+                                List<SchoolTeacher> teachersInschool = new List<SchoolTeacher>();
+                                foreach (var obj in jsoncm.RootElement.GetProperty("Documents").EnumerateArray())
+                                {
+                                    teachersInschool.Add(obj.ToObject<SchoolTeacher>());
+                                }
+
+                                foreach (var obj in teachers)
+                                {
+                                    SchoolTeacher teacher = teachersInschool.Where(t => t.id == obj.GetProperty("id").ToString()).FirstOrDefault<SchoolTeacher>();
+                                    int orgTeacherSize = teacher.size;
+                                    teacher.size = Convert.ToInt32(obj.GetProperty("size").ToString());
+
+                                    // 修改DB裡Teacher的size
+                                    var response = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Teacher").ReadItemStreamAsync(teacher.id, new PartitionKey("Base"));
+                                    if (response.Status == 200)
+                                    {
+                                        var json = await JsonDocument.ParseAsync(response.ContentStream);
+
+                                        //軟體
+                                        Teacher teacherHimself = json.ToObject<Teacher>();
+                                        teacherHimself.size = teacherHimself.size - orgTeacherSize + teacher.size;
+
+                                        //最後一起修改
+                                        await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacherHimself, teacherHimself.id, new PartitionKey("Base"));
+                                        await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync<SchoolTeacher>(teacher, obj.GetProperty("id").ToString(), new PartitionKey($"Teacher-{school_code}"));
+                                    } 
+                                    else
+                                    {
+                                        status = "3";
+                                        err = "有老師不存在於 Teacher 的DB";
+                                    }
+                                }
+
+                            }
+                            else 
+                            {
+                                // 有不存在的的老師ID
+                                status = "2";
+                                err = "有不存在的的老師ID";
+
+                                List<string> teachersInschool = new List<string>();
+                                foreach (var obj in jsoncm.RootElement.GetProperty("Documents").EnumerateArray())
+                                {
+                                    teachersInschool.Add(obj.GetProperty("id").ToString());
+                                }
+
+                                // 差集處理
+                                errTeachers = ids.Except(teachersInschool).ToList();
+                            }
+                        }
+                        return Ok(new { status, err, errTeachers });
+                    case "retract": // 收回教師空間
+                        // [取得DB資料]
+                        queryText = "select * from c ";
+                        string record = "0";
+
+                        await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: queryText, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{school_code}") }))
+                        {
+                            using var jsoncm = await JsonDocument.ParseAsync(item.ContentStream);
+                            if (jsoncm.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                            {
+                                // 修改的筆數
+                                record = count.ToString();
+                                List<SchoolTeacher> teachersInschool = new List<SchoolTeacher>();
+                                foreach (var obj in jsoncm.RootElement.GetProperty("Documents").EnumerateArray())
+                                {
+                                    teachersInschool.Add(obj.ToObject<SchoolTeacher>());
+                                }
+
+                                foreach (SchoolTeacher teacher in teachersInschool)
+                                {
+                                    // 修改DB裡School的老師size
+                                    int teacherSizeInSchool = teacher.size;
+                                    teacher.size = 0;
+                                    
+
+                                    // 修改DB裡Teacher的size
+                                    var response = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Teacher").ReadItemStreamAsync(teacher.id, new PartitionKey("Base"));
+                                    if (response.Status == 200) 
+                                    {
+                                        var json = await JsonDocument.ParseAsync(response.ContentStream);
+
+                                        //軟體
+                                        Teacher teacherHimself = json.ToObject<Teacher>();
+                                        teacherHimself.size -= teacherSizeInSchool;
+
+                                        // 最後一起修改
+                                        await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync<SchoolTeacher>(teacher, teacher.id, new PartitionKey($"Teacher-{school_code}"));
+                                        await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacherHimself, teacherHimself.id, new PartitionKey("Base"));
+                                    }
+                                    else
+                                    {
+                                        status = "3";
+                                        err = "有老師不存在於 Teacher 的DB";
+                                    }
+                                }
+                            }
+                        }
+
+                        return Ok(new { status, record });
+                    default:
+                        return BadRequest(); // 不存在的功能BadRequest
+                }
+            }
+            catch (Exception ex)
+            {
+                return BadRequest();
+            }
+        }
     }
 }

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

@@ -54,7 +54,7 @@ namespace TEAMModelOS.Controllers
             //string status_str = (request.TryGetProperty("join_status", out JsonElement status_json)) ? status_json.ToString() : "join";
             //資料取得
             List<object> teachers = new List<object>();
-            await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name, c.classes, c.picture ,c.status, c.job, c.createTime, ARRAY_LENGTH(c.permissions) as permissionCount FROM c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{school_code}") }))
+            await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id, c.name, c.classes, c.picture ,c.status, c.job, c.createTime, ARRAY_LENGTH(c.permissions) as permissionCount, c.size FROM c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{school_code}") }))
             {
                 using var json = await JsonDocument.ParseAsync(item.ContentStream);
                 if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)

+ 2 - 1
TEAMModelOS/appsettings.Development.json

@@ -31,7 +31,8 @@
       "ScanModel": [ "TEAMModelOS" ]
     },
     "Redis": {
-      "ConnectionString": "CoreRedisCN.redis.cache.chinacloudapi.cn:6380,password=LyJWP1ORJdv+poXWofAF97lhCEQPg1wXWqvtzXGXQuE=,ssl=True,abortConnect=False,defaultDatabase=8,writeBuffer=10240,poolsize=50,prefix=habook:"
+      "ConnectionString": "CoreRedisCN.redis.cache.chinacloudapi.cn:6380,password=LyJWP1ORJdv+poXWofAF97lhCEQPg1wXWqvtzXGXQuE=,ssl=True"
+      //"ConnectionString": "CoreRedisCN.redis.cache.chinacloudapi.cn:6380,password=LyJWP1ORJdv+poXWofAF97lhCEQPg1wXWqvtzXGXQuE=,ssl=True,abortConnect=False,defaultDatabase=8,writeBuffer=10240,poolsize=50"
     },
     "ServiceBus": {
       "ConnectionString": "Endpoint=sb://teammodelos.servicebus.chinacloudapi.cn/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=Sy4h4EQ8zP+7w/lOLi1X3tGord/7ShFHimHs1vC50Dc=",