Jelajahi Sumber

Merge branch 'develop3.0-tmd' of http://106.12.23.251:10080/TEAMMODEL/TEAMModelOS into develop3.0-tmd

OnePsycho 4 tahun lalu
induk
melakukan
3641df8795
48 mengubah file dengan 4491 tambahan dan 1229 penghapusan
  1. 7 0
      TEAMModelOS/ClientApp/src/api/classroom.js
  2. 5 0
      TEAMModelOS/ClientApp/src/api/index.js
  3. 9 0
      TEAMModelOS/ClientApp/src/api/serviceDriveAuth.js
  4. 1 3
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/lesson-testpop.css
  5. 1 1
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/composePaper.vue
  6. 1 1
      TEAMModelOS/ClientApp/src/icons/index.js
  7. 2 0
      TEAMModelOS/ClientApp/src/locale/lang/en-US/index.js
  8. 12 2
      TEAMModelOS/ClientApp/src/locale/lang/en-US/schoolBaseInfo.js
  9. 8 0
      TEAMModelOS/ClientApp/src/locale/lang/en-US/seviceDriveAuth.js
  10. 2 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/index.js
  11. 11 2
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/schoolBaseInfo.js
  12. 8 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/seviceDriveAuth.js
  13. 3 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/index.js
  14. 11 2
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/schoolBaseInfo.js
  15. 8 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/serviceDriveAuth.js
  16. 2 2
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/stuAccount.js
  17. 34 1
      TEAMModelOS/ClientApp/src/mock/index.js
  18. 3 3
      TEAMModelOS/ClientApp/src/mock/serviceDriveAuth.js
  19. 3 0
      TEAMModelOS/ClientApp/src/store/module/schoolBaseInfo.js
  20. 206 20
      TEAMModelOS/ClientApp/src/store/module/serviceDriveAuth.js
  21. 6 1
      TEAMModelOS/ClientApp/src/store/module/user.js
  22. 1 1
      TEAMModelOS/ClientApp/src/view/login/Index.less
  23. 3 1
      TEAMModelOS/ClientApp/src/view/login/Index.vue
  24. 72 7
      TEAMModelOS/ClientApp/src/view/schoolmgmt/ClassroomSetting/ClassroomSetting.less
  25. 301 225
      TEAMModelOS/ClientApp/src/view/schoolmgmt/ClassroomSetting/ClassroomSetting.vue
  26. 44 9
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/Index.vue
  27. 4 3
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/AclassOneChart.vue
  28. 42 3
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/HiteachAuthList.less
  29. 246 97
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/HiteachAuthList.vue
  30. 0 35
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/ServiceList.css
  31. 2 2
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/ServiceList.less
  32. 85 37
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/ServiceList.vue
  33. 83 36
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceChart.vue
  34. 11 0
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceStatus.less
  35. 96 22
      TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceStatus.vue
  36. 334 0
      TEAMModelOS/ClientApp/src/view/student-account/AclassOneAuth.less
  37. 766 0
      TEAMModelOS/ClientApp/src/view/student-account/AclassOneAuth.vue
  38. 2 0
      TEAMModelOS/ClientApp/src/view/student-account/AddStudent.vue
  39. 223 0
      TEAMModelOS/ClientApp/src/view/student-account/AuthNumChart.vue
  40. 12 1
      TEAMModelOS/ClientApp/src/view/student-account/Index.less
  41. 881 580
      TEAMModelOS/ClientApp/src/view/student-account/Index.vue
  42. 2 0
      TEAMModelOS/ClientApp/src/view/student-account/IndexIview.less
  43. 24 2
      TEAMModelOS/Controllers/Client/HiTeachController.cs
  44. 9 1
      TEAMModelOS/Controllers/Common/ExamController.cs
  45. 481 2
      TEAMModelOS/Controllers/School/SchoolController.cs
  46. 248 126
      TEAMModelOS/Controllers/School/StudentController.cs
  47. 5 1
      TEAMModelOS/Controllers/Teacher/InitController.cs
  48. 171 0
      TEAMModelOS/Models/SchoolInfo/SchoolProduct.cs

+ 7 - 0
TEAMModelOS/ClientApp/src/api/classroom.js

@@ -0,0 +1,7 @@
+import { fetch, post } from '@/api/http'
+
+export default {
+    GetHiteachList: function (data) {        
+        return post('/school/init/get-school-product', {'school_code': data} )
+    }
+}

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

@@ -23,6 +23,9 @@ import accessToken from './accessToken'
 import studentWeb from './studentWeb'
 import regist from './regist'
 import forgetPW from './forgetPW'
+import classroom from './classroom'
+import serviceDriveAuth from './serviceDriveAuth'
+
 export default {
     accessToken,
     learnActivity,
@@ -46,6 +49,8 @@ export default {
     schoolUser,
     regist,
     forgetPW,
+    classroom,
+    serviceDriveAuth,
     // 获取登录跳转链接
     getLoginLink: function (data) {
         return post('api/login/login', data)

+ 9 - 0
TEAMModelOS/ClientApp/src/api/serviceDriveAuth.js

@@ -0,0 +1,9 @@
+import { fetch, post } from '@/api/http'
+
+
+export default {
+    getSchoolProduct: function (data) {        
+        return post('/school/init/get-school-product', {'school_code': data} )
+       
+    }
+}

+ 1 - 3
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/lesson-testpop.css

@@ -73,7 +73,6 @@
 .complete-content{
     width:35%;
     margin-top:50px;
-
 }
 .compose-box {
     background-color: #24b880;
@@ -340,8 +339,7 @@
 }
 .lesson-test-pop .checkAnswer {
     margin-top: 30px;
-    max-height: 400px;
-    overflow-y: scroll;
+    min-height: 600px;
 }
 .lesson-test-pop .checkAnswers {
     margin-top: 30px;

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

@@ -66,7 +66,7 @@
                 //    return true; // 返回 true 表示校验成功
                 //};
                 this.editor.config.placeholder = '请输入作答结果'
-                this.editor.config.height = 200
+                this.editor.config.height = 300
                 this.editor.config.showLinkImg = false;
                 this.editor.config.uploadImgShowBase64 = true; // 使用 base64 保存图片不建议使用这种,我只是图个方便
                 // editor.customConfig.uploadImgServer = '/upload'  // 上传图片到服务器

+ 1 - 1
TEAMModelOS/ClientApp/src/icons/index.js

@@ -1,3 +1,3 @@
 import Vue from 'vue'
-import Svg from '@/components/Svg'
+import Svg from '@/components/student-web/SvgIcon/index.vue'
 Vue.component('v-icon', Svg)

+ 2 - 0
TEAMModelOS/ClientApp/src/locale/lang/en-US/index.js

@@ -17,6 +17,7 @@ import regist from './regist'
 import forgotPW from './forgotPW'
 import studentWeb from './studentWeb'
 import settings from './settings'
+import seviceDriveAuth from './seviceDriveAuth'
 import elui from './elui'
 export default {
   schoolBaseInfo,
@@ -38,6 +39,7 @@ export default {
   forgotPW,
   studentWeb,
   settings,
+  seviceDriveAuth,
   test: 'test',
   elui,
   formConfigP: {

+ 12 - 2
TEAMModelOS/ClientApp/src/locale/lang/en-US/schoolBaseInfo.js

@@ -74,7 +74,17 @@ export default {
   csTips4: 'at least one item needs to be reserved! ',
   csTips5: 'upload succeeded! ',
   csTips6: 'this serial number has been bound to the classroom! ',
-  csTips7: 'Delete succese',
+  csTips7: 'Delete succese锟斤拷',
   presetClassroomName: 'preset classroom',
-  presetHeadmaster: 'no head teacher specified'
+  presetHeadmaster: 'no head teacher specified',
+  sokapp:'鑻忔牸鎷夊簳璁��',
+  remotcls:'杩滆窛鏁欏�鏈嶅姟',
+  sokdesk:'鑻忔牸鎷夊簳妗岄潰',
+  sokrpt:'鑻忔牸鎷夊簳鎶ュ憡',
+  sokvdo:'鑻忔牸鎷夊簳褰辩墖',
+  ezs:'褰曟挱绯荤粺',
+  '3222NIYD': 'ezStation 2',
+  'J223IZ6M': 'HiTeach STD',
+  '3222C6D2': 'HiTeach TBL',
+  'J223IZAM': 'HiTeach PRO'
 }

+ 8 - 0
TEAMModelOS/ClientApp/src/locale/lang/en-US/seviceDriveAuth.js

@@ -0,0 +1,8 @@
+export default {
+    //授權管理服務清單
+    "AEGMCPLY":"AClass One 週期",
+    'RYGVCPLY':'AClass One 無週期',
+    'IPALYEIY':'智慧教學服務空間',
+    '6BOPC6MD':'教與學大數據管理服務',
+    'ON6MBDOP':'智慧學校大數據管理服務'
+    }

+ 2 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/index.js

@@ -17,6 +17,7 @@ import regist from './regist'
 import forgotPW from './forgotPW'
 import studentWeb from './studentWeb'
 import settings from './settings'
+import seviceDriveAuth from './seviceDriveAuth'
 import elui from './elui'
 export default {
   schoolBaseInfo,
@@ -38,6 +39,7 @@ export default {
   forgotPW,
   studentWeb,
   settings,
+  seviceDriveAuth,
   elui,
   test: '测试',
   formConfigP: {

+ 11 - 2
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/schoolBaseInfo.js

@@ -72,6 +72,15 @@ export default {
   csTips6: '此序号已绑定到教室!',
   csTips7: '删除成功!',
   presetClassroomName: '教室',
-  presetHeadmaster: '未指定班主任'
-
+  presetHeadmaster: '未指定班主任',
+  sokapp:'苏格拉底议课',
+  remotcls:'远距教室服务',
+  sokdesk:'苏格拉底桌面',
+  sokrpt:'苏格拉底报告',
+  sokvdo:'苏格拉底影片',
+  ezs:'录播系统',
+  '3222NIYD': 'ezStation 2',
+  'J223IZ6M': 'HiTeach STD',
+  '3222C6D2': 'HiTeach TBL',
+  'J223IZAM': 'HiTeach PRO'
 }

+ 8 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/seviceDriveAuth.js

@@ -0,0 +1,8 @@
+export default {
+    //授權管理服務清單
+    "AEGMCPLY":"AClass One 週期",
+    'RYGVCPLY':'AClass One 無週期',
+    'IPALYEIY':'智慧教學服務空間',
+    '6BOPC6MD':'教與學大數據管理服務',
+    'ON6MBDOP':'智慧學校大數據管理服務'
+    }

+ 3 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/index.js

@@ -17,9 +17,11 @@ import regist from './regist'
 import forgotPW from './forgotPW'
 import studentWeb from './studentWeb'
 import settings from './settings'
+import serviceDriveAuth from './serviceDriveAuth'
 import elui from './elui'
 
 export default {
+  
   schoolBaseInfo,
   schoolMgmt,
   totalAnalysis,
@@ -39,6 +41,7 @@ export default {
   forgotPW,
   studentWeb,
   settings,
+  serviceDriveAuth, //授權管理頁面
   elui,
   test: '測試',
   formConfigP: {

+ 11 - 2
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/schoolBaseInfo.js

@@ -71,6 +71,15 @@ export default {
   csTips6: '此序號已綁定到教室!',
   csTips7: '刪除成功!',
   presetClassroomName: '教室',
-  presetHeadmaster: '未指定班主任'
-
+  presetHeadmaster: '未指定班主任',
+  sokapp:'蘇格拉底議課',
+  remotcls:'遠距教室服務',
+  sokdesk:'蘇格拉底桌面',
+  sokrpt:'蘇格拉底報告',
+  sokvdo:'蘇格拉底影片',
+  ezs:'錄播系統',
+  '3222NIYD': 'ezStation 2',
+  'J223IZ6M': 'HiTeach STD',
+  '3222C6D2': 'HiTeach TBL',
+  'J223IZAM': 'HiTeach PRO'
 }

+ 8 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/serviceDriveAuth.js

@@ -0,0 +1,8 @@
+export default {
+//授權管理服務清單
+"AEGMCPLY":"AClass One 週期",
+'RYGVCPLY':'AClass One 無週期',
+'IPALYEIY':'智慧教學服務空間',
+'6BOPC6MD':'教與學大數據管理服務',
+'ON6MBDOP':'智慧學校大數據管理服務'
+}

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

@@ -1,10 +1,10 @@
 export default {
   // table title
   seatNo: '座號',
-  account: '帳號資訊',
+  account: '校內帳號',
   stuName: '姓名',
   classroomCode: '教室編碼',
-  classroomName: '教室名稱',
+  classroomName: '年級資訊',
   period: '學段',
   grade: '學級',
   authStatus: '授權狀態',

+ 34 - 1
TEAMModelOS/ClientApp/src/mock/index.js

@@ -232,6 +232,39 @@ export default {
               'http://icons.iconarchive.com/icons/google/noto-emoji-people-face/256/10135-boy-light-skin-tone-icon.png',
               'http://icons.iconarchive.com/icons/google/noto-emoji-people-face/256/10147-adult-light-skin-tone-icon.png'
           ]
-      }
+      },
+      //Osbert-classroom的假資料
+      'hiteachSetting|20': [{
+        'id|+1': 1234567,
+        'serial': '12DLT43F-EE20-4775-93F5-6BCC',
+        'prodGroup': 'HiTeach 5',
+        'prodName|1' : ['PRO', 'TBL','STD'],
+        'deviceMax': '@natural(0, 10)', // 可綁定最大值,如只有一則回傳單一
+        'DateDate|1': [0, 1, 1602691199], //已到期,無限期或有一個結束時間
+        'aprule': {
+          'sokapp|1': true, // 蘇格拉底議課
+          'sokvdo|1': true, // 蘇格拉底報告
+          'ezs|1': true, // 錄播系統
+
+          // 目前沒有的
+          'remoteSys|1': true, // 遠距教室服務
+          'sokDestop|1': true, // 蘇格拉底桌面
+          'sokreport|1': true, // 蘇格拉底報告
+        },
+        "deviceBound": function(){
+          let max = this.deviceMax
+          let data = []
+          for(let i = 0; i< max; i++){
+            data.push({
+              'uuid': "OSBERT-COMPUTER",
+              'ip': '192.168.0.1',
+              'classId': 'hbcn0101',
+              'computer': "Microsoft Winfows NT 10.0.18883.0 | Intel(R Core(TM i7-10700 CPU @ 2.9Ghz",
+              'deveiceId': '12DLT43F-EE20-4775-93F5-6BCC'
+            })
+          }
+          return data
+        }
+      }],
   })
 }

+ 3 - 3
TEAMModelOS/ClientApp/src/mock/serviceDriveAuth.js

@@ -10,7 +10,7 @@ export default {
         isBuy: true, //是否購買
         isActive: true, //是否啟用
         authTimeStart: "2019.8.31", //服務啟用
-        authTimeEnd: "2021.8.31", //無到期日,永久授權
+        authTimeEnd: "2021.8.30", //無到期日,永久授權
         authNum: 500, //授權數量,應該只有AclassOne會有
         authNumToday: 158, //今日分配狀況
         intro: "@cparagraph(1, 3)", //產品詳細介紹
@@ -22,7 +22,7 @@ export default {
         isBuy: true,
         isActive: true,
         authTimeStart: "2019.8.31",
-        authTimeEnd: "2021.11.30",
+        authTimeEnd: "2021.5.10",
         authNum: 500,
         authNumToday: 158,
         intro: "@cparagraph(1, 3)", //產品詳細介紹
@@ -65,7 +65,7 @@ export default {
         id: "hbcn",
         code: "Product",
         serials: {
-          "auth|58": [
+          "auth|338": [
             {
               id: "117074",
               serial: "12DLT43F-EE20-4775-93F5-6BCC",

+ 3 - 0
TEAMModelOS/ClientApp/src/store/module/schoolBaseInfo.js

@@ -3,6 +3,8 @@ import JSONPath from 'jsonpath'
 export default {
     namespaced: true,
     state: {
+        // "sokapp":"蘇格拉底議課APP" "remotcls":"遠距教室服務" "sokdesk":"蘇格拉底桌面" "sokrpt":"蘇格拉底報告" "sokvdo":"蘇格拉底影片" "ezs":"ezStation錄播系統"
+        aprules:["sokapp", "remotcls", "sokdesk", "sokrpt", "sokvdo", "ezs"],
         schoolBaseInfo: {
             period: []
         },
@@ -18,6 +20,7 @@ export default {
         getGrades: state => state.srvAdr,
         getStudent: state => state.students,
         getSchoolInfo: state => state.schoolBaseInfo,
+        getAprules: state=> state.aprules
     },
     mutations: {
         setSchoolInfo(state, data) {

+ 206 - 20
TEAMModelOS/ClientApp/src/store/module/serviceDriveAuth.js

@@ -1,15 +1,47 @@
+import apiTools from "@/api";
 import serviceDriveAuth from "@/mock/serviceDriveAuth";
 //先接入假資料
 
+
+
 export default {
   namespaced: true,
   state: {
     //假設一開始未定,等待DB資料進來,從action非同步,然後操作mutation,改變State
+
+    schoolCode: "hbcn", //HBCN 學校代碼 (參考hbcn先寫死)
+
+    //接mockData 使用
     serviceList: undefined,
     hiteachAuthList: undefined,
     spaceStatus: undefined,
     serviceIntroIsOpen: [],
     hiteachListItemIsOpen: [], //存放每個開關
+
+    //接實際api使用,一開始序號為空
+
+    //第一區 服務清單
+    service: [],
+    seviceNameList: [
+      "ON6MBDOP",
+      "6BOPC6MD",
+      "IPALYEIY", //智慧教學服務空間
+      "RYGVCPLY",
+      "AEGMCPLY",
+    ],
+
+    //第二區hiteach序號資料
+    serial: [],
+    serialInfo: {
+      totalNum: 0,
+      multiAuthNum: 0, //大量授權
+      singleAuthNum: 0, //單一授權
+      couldActiveNum: 0, //可啟用裝置數
+      activeNum: 0, //已啟用裝置數
+    },
+
+    //第三區Space,由第一區的智慧教學服務空間的IPALYEIY
+    space: {},
   },
   getters: {
     getServiceIntroIsOpen: (state) => {
@@ -19,22 +51,34 @@ export default {
       return state.hiteachListItemIsOpen;
     },
     getServiceList: (state) => {
-      if (state.serviceList !== undefined) {
-        return state.serviceList;
+      if (state.service !== undefined) {
+        return state.service;
       } else {
         return {};
       }
     },
+    //取用產品服務名稱清單
+    getServiceNameList: (state) => {
+      return state.seviceNameList;
+    },
+    getHiteachAuthListInfo: (state) => {
+      return state.serialInfo;
+    },
     getHiteachAuthList: (state) => {
-      if (state.hiteachAuthList !== undefined) {
+      /*if (state.hiteachAuthList !== undefined) {
         return state.hiteachAuthList;
       } else {
         return {};
+      }*/
+      if (state.serial !== undefined) {
+        return state.serial;
+      } else {
+        return {};
       }
     },
     getSpaceStatus: (state) => {
-      if (state.spaceStatus !== undefined) {
-        return state.spaceStatus;
+      if (state.space !== undefined) {
+        return state.space;
       } else {
         return {};
       }
@@ -48,17 +92,59 @@ export default {
       state.hiteachListItemIsOpen = data;
     },
     setServiceList(state, data) {
-      state.serviceList = data;
+      //state.serviceList = data; 接mock
+      state.service = data;
     },
     setHiteachAuthList(state, data) {
-      state.hiteachAuthList = data;
+      //state.hiteachAuthList = data;  //接mock
+      state.serial = data;
+      state.serialInfo.totalNum = data.length;
+
+      //計算大量授權數
+      let multiAuthNum = 0;
+      data.forEach((item) => {
+        if (item.deviceMax > 1) {
+          multiAuthNum += 1;
+        }
+      });
+      state.serialInfo.multiAuthNum = multiAuthNum;
+
+      //計算單一授權數
+      let singleAuthNum = 0;
+      data.forEach((item) => {
+        if (item.deviceMax == 1) {
+          singleAuthNum += 1;
+        }
+      });
+      state.serialInfo.singleAuthNum = singleAuthNum;
+
+      //計算可啟用裝置數
+      let couldActiveNum = 0;
+      data.forEach((item) => {
+        if (item.expireStatus != "F") {
+          couldActiveNum += item.deviceMax;
+        }
+      });
+      state.serialInfo.couldActiveNum = couldActiveNum;
+      //計算已啟用裝置數
+      let activeNum = 0;
+      data.forEach((item) => {
+        if (item.expireStatus == "A" || item.expireStatus == "S") {
+          //console.log(item.deviceBound.length)
+          activeNum += item.deviceBound.length;
+        }
+      });
+      state.serialInfo.activeNum = activeNum;
     },
     setSpaceStatus(state, data) {
-      state.spaceStatus = data;
+      state.space = data;
     },
+
+    
   },
   actions: {
     //模擬從DB(MockData)取得所有資料 && commit to state.serviceDriveAuth
+
     getServiceListDataAsyc(context) {
       return new Promise((resolve) => {
         //先暫時模擬等待幾秒,直接取得資料
@@ -66,11 +152,12 @@ export default {
         let servicedata = serviceDriveAuth.serviceDriveAuthData.serviceList;
         let hiteachAuthdata =
           serviceDriveAuth.serviceDriveAuthData.hiteachClassroomAuth;
-       
+
         let spaceStatusData = serviceDriveAuth.serviceDriveAuthData.spaceStatus;
-        let temp = [],temp2=[];
-       
-        let hiteachlistlength=hiteachAuthdata[0].serials.auth.length
+        let temp = [],
+          temp2 = [];
+
+        let hiteachlistlength = hiteachAuthdata[0].serials.auth.length;
         //根據服務的長度生出開關,只要服務數量變動就必須重置
         for (let i = 0; i < servicedata.length; i++) {
           temp.push({
@@ -78,7 +165,6 @@ export default {
             isOpen: false,
           });
         }
-       
 
         for (let i = 0; i < hiteachlistlength; i++) {
           temp2.push({
@@ -86,16 +172,116 @@ export default {
             isOpen: false,
           });
         }
-        context.commit("setServiceIntroIsOpen", temp);
-          context.commit('setHiteachListItemIsOpen',temp2)
-          context.commit("setServiceList", servicedata);
-          context.commit("setHiteachAuthList", hiteachAuthdata);
-          context.commit("setSpaceStatus", spaceStatusData);
-       
-        
+        //context.commit("setServiceIntroIsOpen", temp);
+        //context.commit("setHiteachListItemIsOpen", temp2);
+        //context.commit("setServiceList", servicedata);
+        //context.commit("setHiteachAuthList", hiteachAuthdata);
+        //context.commit("setSpaceStatus", spaceStatusData);
 
         resolve({});
       });
     },
+
+
+
+    //
+    getSchoolProduct(context, schoolCode) {
+      return new Promise((resolve, reject) => {
+        apiTools.serviceDriveAuth.getSchoolProduct(schoolCode).then(
+          (res) => {
+            let temp = [];
+            for (let i = 0; i < res.serial.length; i++) {
+              temp.push({
+                index: i,
+                isOpen: false,
+              });
+            }
+            context.commit("setHiteachListItemIsOpen", temp);
+            console.log(res);
+            context.commit("setHiteachAuthList", res.serial);
+
+            //資料一進來,先比對一下沒有買的品項,加在表尾
+            let serviceNameList = context.getters.getServiceNameList;
+            let nowbuyList = [];
+            for (let i = 0; i < res.service.length; i++) {
+              nowbuyList.push(res.service[i].prodCode);
+            }
+            let notbuyList = serviceNameList.filter(
+              (item) => nowbuyList.includes(item) == false
+            );
+            console.log(notbuyList);
+
+            notbuyList.forEach((item) =>
+              res.service.push({
+                prodCode: item,
+                notbuy: true,
+                startDate: 0,
+                endDate: 0,
+              })
+            );
+
+            let temp2 = [];
+            for (let i = 0; i < res.service.length; i++) {
+              //資料一進來,將AclassOne排在第一個
+              if (res.service[i].prodCode == "RYGVCPLY") {
+                res.service.unshift(res.service[i]);
+                res.service.splice(i + 1, 1);
+              }
+
+              temp2.push({
+                index: i,
+                isOpen: false,
+              });
+              temp2[0].isOpen = true; //預設把AclassOne細節打開
+            }
+
+            context.commit("setServiceIntroIsOpen", temp2);
+            context.commit("setServiceList", res.service);
+
+            //提取空間資料
+            let space = res.service.filter(
+              (item) => item.prodCode == "IPALYEIY"
+            );
+
+            Object.defineProperty(space[0], "totalused", {
+              value:
+                space[0].doc +
+                space[0].image +
+                space[0].item +
+                space[0].other +
+                space[0].paper +
+                space[0].student +
+                space[0].teacher +
+                space[0].video,
+              writable: true,
+            });
+            Object.defineProperty(space[0], "unUsed", {
+              value: space[0].avaliable-space[0].totalused,
+              writable: true,
+            });
+
+            Object.defineProperty(space[0], "unUsedPercent", {
+              value: ((space[0].unUsed/space[0].avaliable)*100).toFixed(2),
+              writable: true,
+            });
+
+            console.log(space);
+            context.commit("setSpaceStatus", space[0]);
+
+            resolve({
+              code: 1,
+              message: "Get service and hiteachList successful",
+            });
+          },
+          (err) => {
+            reject({
+              code: 0,
+              data: [],
+              message: "Get service and hiteachList API error!",
+            });
+          }
+        );
+      });
+    },
   },
 };

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

@@ -585,7 +585,12 @@ export default {
             context.commit('setStudentProfile', data)
         },
         async checkSchoolProfile(context) {
-            let login_schoolCode = localStorage.getItem('login_schoolCode')
+            let school_profile = localStorage.getItem('school_profile')
+            if(school_profile){
+                context.dispatch('setSchoolProfile', JSON.parse(decodeURIComponent(school_profile,"utf-8")))
+            }
+
+            let login_schooCode = localStorage.getItem('login_schooCode')
             let id_token = localStorage.getItem('id_token')
 
             if (login_schoolCode && id_token) {

+ 1 - 1
TEAMModelOS/ClientApp/src/view/login/Index.less

@@ -42,7 +42,7 @@
                 .text{
                     margin-right: 15px;
                     color: #ffffff;
-                    font-size: ;
+                    font-size: 16px;
                 }
                 .tooltip{
                     height: 15px;

+ 3 - 1
TEAMModelOS/ClientApp/src/view/login/Index.vue

@@ -188,9 +188,11 @@
         </FormItem>
         <FormItem class="formItem" prop="id" >
           <Input element-id = "studId" v-model="studForm.id" :placeholder="$t('login.placeholder.schoolID')"/>
+          <input type="text" style="position:fixed;z-index:-10000;width:0;border:none"/>
         </FormItem>
         <FormItem class="formItem"  prop="pass">
-          <Input element-id = "studpw"  type="password" v-model="studForm.pass" :placeholder="$t('login.placeholder.schoolPsw')" >
+          <input type="password" style="position:fixed;z-index:-10000;width:0;border:none"/>
+          <Input element-id="studpw"  type="password" v-model="studForm.pass" :placeholder="$t('login.placeholder.schoolPsw')" >
             <!-- <Icon v-show="studFormEnter" @click="loginSubmit('studForm')" type="md-arrow-forward" slot="suffix" /> -->
             <Icon size="24" v-show="!loading && studFormEnter" @click="loginSubmit('studForm')" type="md-arrow-forward" slot="suffix" class="iconFrame" />
             <div v-show="loading" class="demo-spin-col" slot="suffix">

+ 72 - 7
TEAMModelOS/ClientApp/src/view/schoolmgmt/ClassroomSetting/ClassroomSetting.less

@@ -197,7 +197,7 @@
 }
 .class-list {
     width: 100%;
-    height:~"calc(100% - 95px)";
+    height:~"calc(100% - 90px)";
     padding-left: 15px;
     
 }
@@ -248,14 +248,10 @@
     }
 }
 .iconHi{
+    display: inline-block;
+    margin-right: 5px;
     width: 14px;
     height: 14px;
-    background: rgb(28, 192, 243);
-    border-radius: 16%;
-    display: inline-flex;
-    justify-content: center;
-    align-items: center;
-    margin-right: 5px;
 }
 .iconBoard {
     width: 14px;
@@ -363,15 +359,84 @@
     }
 }
 .hiteach-collapse{
+    border-bottom: 1px solid #424242;
+    position: relative;
+    cursor: pointer;
     &-main{
+        display: flex;
+        align-items: center;
+        padding: 20px 0 20px 20px;
         &:hover{
             background-image: linear-gradient(90deg, rgba(30,30,30,0) 0%, rgba(110,110,110,.2) 50%, rgba(110,110,110,.4) 100%);
         }
+        .arrowIcon{
+            position: absolute;
+            right: 5px;
+            top: 20px;
+        }
+        .proIcon{
+            width: 60px;
+            height: 60px;
+            margin-right: 30px;
+        }
+        .proCont{
+            font-size: 12px;
+            letter-spacing: 1px;
+            ul li{
+                .title{
+                    display: inline-block;
+                    margin-right: 12px;
+                    font-size: 16px;
+                    font-weight: 500;
+                    color: #bdbdbd;
+                    color:white;
+                }
+                .serialType{
+                    margin-right: 12px;
+                    padding: 2px 5px;
+                    background: #1cc0f3;
+                    border-radius: 3px;
+                    color: white;
+                }
+                .tag{
+                    display: inline-block;
+                    border: 1px solid #8d8d8d;
+                    padding: 3px 10px;
+                    color: #8d8d8d;
+                    margin-top: 5px;
+                    margin-right: 10px;
+                    border-radius: 5px;
+                    &.active{
+                        color: #1cc0f3!important;
+                        border-color: #1cc0f3!important;
+                    }
+                }
+            }
+        }
     }
     .hiteach-collapse-sub{
+        display: flex;
+        align-items: center;
+        border-top: 1px solid rgb(66, 66, 66);
+        margin-left: 20px;
+        letter-spacing: 1px;
+        font-size: 12px;
+        padding: 20px;        
         &:hover{
             background-image: linear-gradient(90deg, #2a2a2e, #293942);
         }
+        &-detail{
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            width: 100%;
+        }
+        &.linked{
+            ul li{
+                color: #888888;
+            }            
+            cursor: not-allowed;
+        }
     }
 }
 

+ 301 - 225
TEAMModelOS/ClientApp/src/view/schoolmgmt/ClassroomSetting/ClassroomSetting.vue

@@ -10,7 +10,7 @@
                         <b class="title">{{ filterPeriodName }}</b>
                         <Icon type="ios-arrow-down" style="margin-left:8px;"></Icon>
                     </span>
-                    <DropdownMenu slot="list" v-for="(item,index) in schoolBase.period" :value="item.id" :key="index">
+                    <DropdownMenu slot="list" v-for="(item,index) in periods" :value="item.id" :key="index">
                         <DropdownItem :name="item.id">{{ item.name }}</DropdownItem>
                     </DropdownMenu>
                 </Dropdown>
@@ -59,10 +59,8 @@
                                 </span>
                             </p>
                             <p class="class-type">
-                                <template v-if="item.style != 'smart'">
-                                    <div class="iconHi">
-                                        <v-icon iconClass="hi" style="color:#333;" />
-                                    </div>
+                                <template v-if="isSmart(item.id)">
+                                    <v-icon class="iconHi" iconClass="htc" />
                                     <span>
                                         <!--多语系后面统一处理-->
                                         <!--{{ $t('TEAM Model 智慧教室') }}-->
@@ -146,6 +144,9 @@
                                         </Option>
                                     </Select>
                                 </FormItem>
+                                <FormItem>
+                                    <!-- <Button @click="testSave()">TEST</Button> -->
+                                    </FormItem>
                             </Form>
                         </vuescroll>
                     </div>
@@ -159,228 +160,92 @@
                             </p>
                             <div class="class-list-filter">
                                 <div class="class-list-filter-box">
-                                    <Dropdown class="sort-dropdown" trigger="click" placement="bottom-start">
+                                    <Dropdown class="sort-dropdown" trigger="click" placement="bottom-start" @on-click="function(e){ filterHiteachVer = e }" >
                                         <span style="cursor: pointer;color:white;">
                                             {{ '顯示所有版本的Hiteach' }}
                                             <Icon type="ios-arrow-down"></Icon>
                                         </span>
                                         <DropdownMenu slot="list">
-                                            <DropdownItem name="All">{{ '顯示所有版本的Hiteach' }}</DropdownItem>
-                                            <DropdownItem name="Standard">{{ '僅顯示Hiteach Standard版本' }}</DropdownItem>
-                                            <DropdownItem name="Pro">{{ '僅顯示Hiteach Pro版本' }}</DropdownItem>
-                                            <DropdownItem name="TBL">{{ '僅顯示Hiteach TBL版本' }}</DropdownItem>
+                                            <DropdownItem name="ALL">{{ '顯示所有版本的Hiteach' }}</DropdownItem>
+                                            <DropdownItem name="J223IZ6M">{{ '僅顯示Hiteach Standard版本' }}</DropdownItem>
+                                            <DropdownItem name="J223IZAM">{{ '僅顯示Hiteach Pro版本' }}</DropdownItem>
+                                            <DropdownItem name="3222C6D2">{{ '僅顯示Hiteach TBL版本' }}</DropdownItem>
                                         </DropdownMenu>
                                     </Dropdown>
                                     <div class="dark-iview-input" style="padding-right:10px;">
-                                        <Input clearable v-model="serchCode" size="small" suffix="ios-search" @on-change="filterCode"></Input>
+                                        <Input clearable v-model="serchCode" size="small" suffix="ios-search" ></Input>
                                     </div>
                                 </div>
                             </div>
                         </div>
                         <div class="hiteach-code-wrap-list">
                             <vuescroll>
-                                <div @click="stdFlag = !stdFlag" class="hiteach-collapse" style="border-bottom: 1px solid #424242;position: relative;cursor: pointer;">
-                                    <div class="hiteach-collapse-main" style="display: flex;align-items: center;padding: 20px 0 20px 20px;">
+                                <div class="hiteach-collapse" v-for="(item, index) in showHiteachData" :key="index">
+                                    <div @click="openHiSub(index)" class="hiteach-collapse-main">
 
-                                        <Icon :type="stdFlag ? 'ios-arrow-up' : 'ios-arrow-down'" size="30" color="white" style="position: absolute;right: 5px;top: 20px;" />
+                                        <Icon class="arrowIcon" v-if="item.deviceMax > 1" :type="item.subOpen ? 'ios-arrow-up' : 'ios-arrow-down'" size="30" color="white" />
 
-                                        <v-icon style="width: 60px;height: 60px;margin-right: 30px;" iconClass="htc_std" />
-                                        <div style="font-size: 12px;letter-spacing: 1px;">
-                                            <ul>
-                                                <li>
-                                                    <h1 class="title" style="display: inline-block;margin-right: 12px;font-size: 16px;font-weight: 500;color: #bdbdbd;color:white;">HiTeach 5 STD</h1>
-                                                    <span style="margin-right: 12px;padding: 2px 5px;background: #1cc0f3;border-radius: 3px;color: white;;">大量</span>
-                                                    <span style="color: #1cc0f3;font-size:12px">2/2</span>
-                                                </li>
-                                                <li><span style="color: #1cc0f3;">3222IAVN-FJ4Y-F5EG-S3OI-4P1A</span></li>
-                                                <li>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底議課</Tag>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底報告</Tag>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底桌面</Tag>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底影片</Tag>
-                                                    <Tag type="border" color="#1cc0f3">遠距教室服務</Tag>
-                                                    <Tag type="border" color="#1cc0f3">錄播系統</Tag>
-                                                </li>
-                                                <li><span style="color: #8d8d8d;">序號到期日: </span>無到期日</li>
-                                            </ul>
-                                        </div>
-                                    </div>
-                                    <div v-if="stdFlag">
-                                        <div class="hiteach-collapse-sub gradient" style="display: flex;align-items: center;border-top: 1px solid rgb(66, 66, 66);margin-left: 20px;letter-spacing: 1px;font-size: 12px;padding: 20px;">
-                                            <Checkbox style="margin-right: 70px;" v-model="test1" disabled></Checkbox>
-                                            <div style="display: flex;justify-content: space-between;align-items: center;width: 100%;">
-                                                <ul>
-                                                    <!-- 被關聯要變色 -->
-                                                    <li style="color: #888888;">
-                                                        <h3>USER-PCCA062992</h3>
-                                                    </li>
-                                                    <li style="color: #888888;">
-                                                        <span>192.168.256 | 84FJID3LKSD88 </span>
-                                                    </li>
-                                                    <li style="color: #888888;">
-                                                        <span>Microsoft Winfows NT 10.0.18883.0 | Intel(R Core(TM i7-10700 CPU @ 2.9Ghz))</span>
-                                                    </li>
-                                                </ul>
-                                                <span style="color: #1cc0f3;padding: 0 15px;">
-                                                    已被關聯
-                                                </span>
-                                            </div>
-                                        </div>
-                                        <div class="hiteach-collapse-sub gradient" style="display: flex;align-items: center;border-top: 1px solid rgb(66, 66, 66);margin-left: 20px;letter-spacing: 1px;font-size: 12px;padding: 20px;">
-                                            <Checkbox style="margin-right: 70px;"></Checkbox>
-                                            <div style="display: flex;justify-content: space-between;align-items: center;width: 100%;">
-                                                <ul>
-                                                    <li>
-                                                        <h3>USER-PCCA062992</h3>
-                                                    </li>
-                                                    <li>
-                                                        <span>192.168.256 | 84FJID3LKSD88 </span>
-                                                    </li>
-                                                    <li>
-                                                        <span>Microsoft Winfows NT 10.0.18883.0 | Intel(R Core(TM i7-10700 CPU @ 2.9Ghz))</span>
-                                                    </li>
-                                                </ul>
-                                                <span v-if="false" style="color: #1cc0f3;padding: 0 15px;">
-                                                    已被關聯
-                                                </span>
-                                            </div>
-                                        </div>
-                                    </div>
-                                </div>
-                                <div class="hiteach-collapse" style="border-bottom: 1px solid #424242;position: relative;">
-                                    <div class="hiteach-collapse-main" style="display: flex;align-items: center;padding: 20px 0 20px 20px;">
+                                        <v-icon v-if="item.prodCode == 'J223IZ6M'" class="proIcon" iconClass="htc_std" />
+                                        <v-icon v-else-if="item.prodCode == '3222C6D2'" class="proIcon" iconClass="htc_tbl" />
+                                        <v-icon v-else-if="item.prodCode == 'J223IZAM'" class="proIcon" iconClass="htc_pro" />
 
-                                        <Icon @click="tblFlag = !tblFlag" :type="tblFlag ? 'ios-arrow-up' : 'ios-arrow-down'" size="30" color="white" style="position: absolute;right: 5px;top: 20px;" />
 
-                                        <v-icon style="width: 60px;height: 60px;margin-right: 30px;" iconClass="htc_tbl" />
-                                        <div style="font-size: 12px;letter-spacing: 1px;">
+                                        <div class="proCont">
                                             <ul>
                                                 <li>
-                                                    <h1 class="title" style="display: inline-block;margin-right: 12px;font-size: 16px;font-weight: 500;color: #bdbdbd;color:white;">HiTeach 5 STD</h1>
-                                                    <span style="margin-right: 12px;padding: 2px 5px;background: #1cc0f3;border-radius: 3px;color: white;;">大量</span>
-                                                    <span style="color: #1cc0f3;font-size:12px">2/2</span>
+                                                    <h1 class="title">
+                                                        {{ $t('schoolBaseInfo.' + item.prodCode)}}
+                                                    </h1>
+                                                    <span class="serialType">
+                                                        {{ item.deviceMax == 1 ? $t('單一') : $t('大量')}}
+                                                    </span>
+                                                    <span style="color: #1cc0f3;font-size:12px">
+                                                        <!-- 要增加被關聯了幾個 -->
+                                                        {{ item.deviceBound ? item.linkCount + ' / '+item.deviceMax : '-- / -- '}}
+                                                    </span>
                                                 </li>
-                                                <li><span style="color: #1cc0f3;">3222IAVN-FJ4Y-F5EG-S3OI-4P1A</span></li>
-                                                <li>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底議課</Tag>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底報告</Tag>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底桌面</Tag>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底影片</Tag>
-                                                    <Tag type="border" color="#1cc0f3">遠距教室服務</Tag>
-                                                    <Tag type="border" color="#1cc0f3">錄播系統</Tag>
+                                                <li><span style="color: #1cc0f3;">{{ item.serial }}</span></li>
+                                                <li style="display: block;">
+                                                    <div v-for="(aprule, index) in aprules" :key="index" class="tag" :class="{active: item.aprule[aprule]}">{{ $t('schoolBaseInfo.'+ aprule)}}</div>
                                                 </li>
-                                                <li><span style="color: #8d8d8d;">序號到期日: </span>無到期日</li>
+                                                <li><span style="color: #8d8d8d;">{{$t('序號到期日')}}: </span>{{item.endDate | timeFmt}}</li>
                                             </ul>
                                         </div>
                                     </div>
-                                    <div v-if="tblFlag">
-                                        <div class="hiteach-collapse-sub gradient" style="display: flex;align-items: center;border-top: 1px solid rgb(66, 66, 66);margin-left: 20px;letter-spacing: 1px;font-size: 12px;padding: 20px;">
-                                            <Checkbox style="margin-right: 70px;" v-model="test1" disabled></Checkbox>
-                                            <div style="display: flex;justify-content: space-between;align-items: center;width: 100%;">
-                                                <ul>
-                                                    <!-- 被關聯要變色 -->
-                                                    <li style="color: #888888;">
-                                                        <h3>USER-PCCA062992</h3>
-                                                    </li>
-                                                    <li style="color: #888888;">
-                                                        <span>192.168.256 | 84FJID3LKSD88 </span>
-                                                    </li>
-                                                    <li style="color: #888888;">
-                                                        <span>Microsoft Winfows NT 10.0.18883.0 | Intel(R Core(TM i7-10700 CPU @ 2.9Ghz))</span>
-                                                    </li>
-                                                </ul>
-                                                <span style="color: #1cc0f3;padding: 0 15px;">
-                                                    已被關聯
-                                                </span>
-                                            </div>
-                                        </div>
-                                        <div class="hiteach-collapse-sub gradient" style="display: flex;align-items: center;border-top: 1px solid rgb(66, 66, 66);margin-left: 20px;letter-spacing: 1px;font-size: 12px;padding: 20px;">
-                                            <Checkbox style="margin-right: 70px;"></Checkbox>
-                                            <div style="display: flex;justify-content: space-between;align-items: center;width: 100%;">
-                                                <ul>
-                                                    <li>
-                                                        <h3>USER-PCCA062992</h3>
-                                                    </li>
-                                                    <li>
-                                                        <span>192.168.256 | 84FJID3LKSD88 </span>
-                                                    </li>
-                                                    <li>
-                                                        <span>Microsoft Winfows NT 10.0.18883.0 | Intel(R Core(TM i7-10700 CPU @ 2.9Ghz))</span>
-                                                    </li>
-                                                </ul>
-                                                <span v-if="false" style="color: #1cc0f3;padding: 0 15px;">
-                                                    已被關聯
-                                                </span>
-                                            </div>
-                                        </div>
-                                    </div>
-                                </div>
-                                <div class="hiteach-collapse" style="border-bottom: 1px solid #424242;position: relative;">
-                                    <div class="hiteach-collapse-main" style="display: flex;align-items: center;padding: 20px 0 20px 20px;">
-
-                                        <Icon @click="proFlag = !proFlag" :type="proFlag ? 'ios-arrow-up' : 'ios-arrow-down'" size="30" color="white" style="position: absolute;right: 5px;top: 20px;" />
+                                    <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.deviceId, (dbItem.classId && classroomListShow[curClassIndex].id != dbItem.classId))">
+                                                <!-- 被關聯 disabled -->
+                                                <Checkbox :id="'checkbox-' + dbItem.deviceId" @on-change="watchUpdate" style="margin-right: 70px;" v-model="dbItem.hiteachLink" :disabled="dbItem.classId != '' && dbItem.classId != null && classroomListShow[curClassIndex].id != dbItem.classId" ></Checkbox>
 
-                                        <v-icon style="width: 60px;height: 60px;margin-right: 30px;" iconClass="htc_pro" />
-                                        <div style="font-size: 12px;letter-spacing: 1px;">
-                                            <ul>
-                                                <li>
-                                                    <h1 class="title" style="display: inline-block;margin-right: 12px;font-size: 16px;font-weight: 500;color: #bdbdbd;color:white;">HiTeach 5 STD</h1>
-                                                    <span style="margin-right: 12px;padding: 2px 5px;background: #1cc0f3;border-radius: 3px;color: white;;">大量</span>
-                                                    <span style="color: #1cc0f3;font-size:12px">2/2</span>
-                                                </li>
-                                                <li><span style="color: #1cc0f3;">3222IAVN-FJ4Y-F5EG-S3OI-4P1A</span></li>
-                                                <li>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底議課</Tag>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底報告</Tag>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底桌面</Tag>
-                                                    <Tag type="border" color="#8d8d8d">蘇格拉底影片</Tag>
-                                                    <Tag type="border" color="#1cc0f3">遠距教室服務</Tag>
-                                                    <Tag type="border" color="#1cc0f3">錄播系統</Tag>
-                                                </li>
-                                                <li><span style="color: #8d8d8d;">序號到期日: </span>無到期日</li>
-                                            </ul>
-                                        </div>
-                                    </div>
-                                    <div v-if="proFlag">
-                                        <div class="hiteach-collapse-sub gradient" style="display: flex;align-items: center;border-top: 1px solid rgb(66, 66, 66);margin-left: 20px;letter-spacing: 1px;font-size: 12px;padding: 20px;">
-                                            <Checkbox style="margin-right: 70px;" v-model="test1" disabled></Checkbox>
-                                            <div style="display: flex;justify-content: space-between;align-items: center;width: 100%;">
-                                                <ul>
-                                                    <!-- 被關聯要變色 -->
-                                                    <li style="color: #888888;">
-                                                        <h3>USER-PCCA062992</h3>
-                                                    </li>
-                                                    <li style="color: #888888;">
-                                                        <span>192.168.256 | 84FJID3LKSD88 </span>
-                                                    </li>
-                                                    <li style="color: #888888;">
-                                                        <span>Microsoft Winfows NT 10.0.18883.0 | Intel(R Core(TM i7-10700 CPU @ 2.9Ghz))</span>
-                                                    </li>
-                                                </ul>
-                                                <span style="color: #1cc0f3;padding: 0 15px;">
-                                                    已被關聯
-                                                </span>
+                                                <div class="hiteach-collapse-sub-detail" >
+                                                    <ul>
+                                                        <li>
+                                                            <h3>{{ dbItem.pcname }}</h3>
+                                                        </li>
+                                                        <li>
+                                                            <span>{{ dbItem.ip }} {{ dbItem.uuid ? ' | '+ dbItem.uuid : dbItem.uuid2 ? ' | ' + dbItem.uuid2 : ''}} </span>
+                                                        </li>
+                                                        <li>
+                                                            <span>
+                                                                {{ dbItem.os }}
+                                                            </span>
+                                                        </li>
+                                                    </ul>                                                    
+                                                    <span v-if="dbItem.classId && classroomListShow[curClassIndex].id != dbItem.classId" style="color: #1cc0f3;padding: 0 15px;">
+                                                        已被關聯
+                                                    </span>
+                                                </div>
                                             </div>
-                                        </div>
-                                        <div class="hiteach-collapse-sub gradient" style="display: flex;align-items: center;border-top: 1px solid rgb(66, 66, 66);margin-left: 20px;letter-spacing: 1px;font-size: 12px;padding: 20px;">
-                                            <Checkbox style="margin-right: 70px;"></Checkbox>
-                                            <div style="display: flex;justify-content: space-between;align-items: center;width: 100%;">
-                                                <ul>
-                                                    <li>
-                                                        <h3>USER-PCCA062992</h3>
-                                                    </li>
-                                                    <li>
-                                                        <span>192.168.256 | 84FJID3LKSD88 </span>
-                                                    </li>
-                                                    <li>
-                                                        <span>Microsoft Winfows NT 10.0.18883.0 | Intel(R Core(TM i7-10700 CPU @ 2.9Ghz))</span>
-                                                    </li>
-                                                </ul>
-                                                <span v-if="false" style="color: #1cc0f3;padding: 0 15px;">
-                                                    已被關聯
-                                                </span>
+                                        </template>
+                                        <template v-else>
+                                            <div class="hiteach-collapse-sub gradient" style="display: flex;align-items: center;border-top: 1px solid rgb(66, 66, 66);margin-left: 20px;letter-spacing: 1px;font-size: 12px;padding: 20px;">
+                                                <span style="margin-right: 70px;" ></span>
+                                                <div style="display: flex;justify-content: space-between;align-items: center;width: 100%;color: #8d8d8d">
+                                                    {{ '此序號尚未啟用' }}
+                                                </div>
                                             </div>
-                                        </div>
+                                        </template>
                                     </div>
                                 </div>
                                 <!-- <ul>
@@ -457,6 +322,7 @@
     import '@/icons/svg/htc_std.svg'
     import '@/icons/svg/htc_tbl.svg'
     import '@/icons/svg/board.svg'
+    import { mapGetters } from 'vuex'
     export default {
         data() {
             // 验证只能是字母和数字
@@ -508,17 +374,16 @@
                         align: 'center'
                     }
                 ],
+                orgHiteachData: [],
+                hiteachData: [],
                 schoolBase: {
                     period:[]
                 },
-                tableHeight:600,
                 stuLoading:false,
-                tblFlag: false,
-                stdFlag: false,
-                proFlag: false,                
-                test1: true,
                 orderBy: 'id',
+                filterHiteachVer: 'ALL',
                 updateBefore:'',
+                updateBeforebyHiteach:'',
                 filterPeriod: undefined,
                 editStatus: true,//可切换编辑状态
                 noStatus: false,
@@ -654,12 +519,37 @@
                 },
             }
         },
+        filters: {
+            timeFmt: function (val) {
+                if(val == 0 || val == 1){
+                    return val == 0 ? '已到期' : '無限期'
+                } else {
+                    if(val.toString().length < 13) val = val*1000
+                    let d = new Date(val),
+                    month = '' + (d.getMonth() + 1),
+                    day = '' + d.getDate(),
+                    year = d.getFullYear();
+
+                    if (month.length < 2) 
+                        month = '0' + month;
+                    if (day.length < 2) 
+                        day = '0' + day;
+
+                    return [year, month, day].join('/');
+                }
+            }
+        },
         computed: {
+            ...mapGetters({
+                periods: 'user/getPeriods', // 學制s
+                aprules: 'schoolBaseInfo/getAprules',          
+            }),            
             filterPeriodName: function(){
+                let data = this.periods
                 let pId = this.filterPeriod
                 let name = ''
-                if(pId){
-                    let temp = this.$store.state.user.schoolProfile.school_base.period.filter( item => {
+                if(pId !== ''){
+                    let temp = data.filter( item => {
                         return pId == item.id
                     })
                     if(temp.length >0 ) name = temp[0].name
@@ -679,6 +569,38 @@
                     break;
                 }
             },
+            showHiteachData: function(){
+                let data = this.hiteachData
+                let filterText = this.filterHiteachVer
+                if( Array.isArray(data) && data.length > 0 && filterText != 'ALL'){
+                    data = data.filter( item => {
+                        return item.prodCode == filterText
+                    })
+                }
+
+                if(this.serchCode !=''){
+                    let serchCode = this.serchCode.toUpperCase()
+                    data = data.filter((item, index) => {
+                        let verName = this.$t('schoolBaseInfo.' + item.prodCode)
+                        let pcNames = false;
+
+                        if(Array.isArray(item.deviceBound) && item.deviceBound.length > 0) {
+                            pcNames = item.deviceBound.some( db => {
+                                if(db.pcname) {
+                                    return db.pcname.toUpperCase().indexOf(serchCode) != -1
+                                } else {
+                                    return false
+                                }
+                                
+                            })
+                        }
+
+                        return item.serial.toUpperCase().indexOf(serchCode) != -1 || verName.toUpperCase().indexOf(serchCode) != -1 || pcNames
+                    })
+                }
+
+                return data
+            },
             students() {
                 if (this.currentTabIndex == '2') {
                     if (this.classroomListShow[this.curClassIndex] && this.classroomListShow[this.curClassIndex].students) {
@@ -706,6 +628,9 @@
             }
         },
         methods: {
+            showtest(val){
+                console.log(val, 'showtest')
+            },
             dropdownStates(flag){
                 if(!flag) this.filterByPeriod()
             },
@@ -801,7 +726,7 @@
             },
             confirmAdd() {
                 this.hiTeachs.push(this.hiTeachItem)
-                this.filterCode()
+                // this.filterCode()
                 this.hiTeachItem = {
                     code: '',
                     single: undefined,
@@ -880,13 +805,13 @@
                 this.drawSchoolPlan('imgUrl')
                 this.initIcon()
             },
-            filterCode() {
-                if (this.serchCode == '') {
-                    this.hiTeachsShow = [...this.hiTeachs]
-                } else {
-                    this.hiTeachsShow = this.hiTeachs.filter(item => item.code.indexOf(this.serchCode) != -1)
-                }
-            },
+            // filterCode() {
+            //     if (this.serchCode == '') {
+            //         this.hiTeachsShow = [...this.hiTeachs]
+            //     } else {
+            //         this.hiTeachsShow = this.hiTeachs.filter(item => item.code.indexOf(this.serchCode) != -1)
+            //     }
+            // },
             
             drawText(text, x, y) {
                 this.schoolPlan = document.getElementById('school-plan')
@@ -1078,7 +1003,12 @@
                                         this.classroomListShow[this.curClassIndex].option = 'insert'
                                         this.$Message.error(res.v)
                                     } else {
-                                        this.$Message.success(this.$t('schoolBaseInfo.csTips3'))
+
+                                        // 修改Hiteach
+                                        
+
+
+                                        this.$Message.success(this.$t('schoolBaseInfo.csTips3'))                                        
                                         this.updated = false
                                         if (option == 'insert') {
                                             this.classroomListShow[this.curClassIndex].code = 'Class-'+ this.classroomListShow[this.curClassIndex].code
@@ -1116,10 +1046,9 @@
                             this.filterClassname()
 
                             // 預設搜尋給第一個
-                            if (res.school_base.period && res.school_base.period.length) {
-                                this.filterPeriod = res.school_base.period[0].id
-                                this.filterByPeriod()
-                            }
+                            // this.filterPeriod = res.school_base.period[0].id
+                            if(this.periods) this.filterPeriod = this.periods[0].id
+                            this.filterByPeriod()
                         }
                     },
                     (err) => {
@@ -1168,8 +1097,26 @@
                                         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.hiteachData = orgHiData
+                                this.orgHiteachData = JSON.stringify(orgHiData)
+
                                 this.classroomList.splice(originIndex, 1)
                                 this.classroomListShow.splice(index, 1)
+
+
                                 this.$Message.success(this.$t('schoolBaseInfo.csTips7'))
                                 this.updated = false
                             }
@@ -1209,6 +1156,7 @@
                                     this.$set(this.classroomListShow, this.curClassIndex, JSON.parse(this.updateBefore))
                                     this.curClassIndex = index
                                     this.updateBefore = JSON.stringify(this.classroomListShow[this.curClassIndex])
+                                    this.hiteachData = JSON.parse(this.orgHiteachData)
                                 }
                             }
                         }
@@ -1216,6 +1164,7 @@
                     } else {
                         this.curClassIndex = index
                         this.updateBefore = JSON.stringify(this.classroomListShow[this.curClassIndex])
+                        this.hiteachData = JSON.parse(this.orgHiteachData)
                     }
                     if (this.currentTabIndex == 1) {
                         this.drawSchoolPlan('')
@@ -1258,7 +1207,8 @@
                             }
                             this.hiTeachs[i].using = 1
                             this.classroomListShow[this.curClassIndex].sn = this.hiTeachsShow[index].code
-                            this.classroomListShow[this.curClassIndex].style = 'smart'
+                            // smart 由右邊的Hiteach List 判斷
+                            // this.classroomListShow[this.curClassIndex].style = 'smart'
                             this.updated = true
                         }
                     } else {
@@ -1281,10 +1231,11 @@
                         }
                         this.hiTeachs[i].using++
                         this.classroomListShow[this.curClassIndex].sn = this.hiTeachsShow[index].code
-                        this.classroomListShow[this.curClassIndex].style = 'smart'
+                        // smart 由右邊的Hiteach List 判斷
+                        // this.classroomListShow[this.curClassIndex].style = 'smart'
                         this.updated = true
                     }
-                    this.filterCode()
+                    // this.filterCode()
                 }
             },
             initData() {
@@ -1336,12 +1287,137 @@
                 } else {
                     this.$Message.warning('您暂无次操作权限!')
                 }
+            },
+            initHiteachData: async function () {
+                let temp;
+                let linkedClassIds = []
+                await this.getHiteachList().then( res=>{
+                    temp = res.map( item => {
+                        // 子項目開關
+                        item.subOpen = false
+                        if(item.deviceMax == 1) item.subOpen = true
+
+                        // 已被關聯數量
+                        let linkCount = 0
+
+                        // 關聯開關
+                        item.deviceBound.map( db=> {
+                            db.hiteachLink = false
+                            if(db.classId) {
+                                db.hiteachLink = true
+                                linkedClassIds.push(db.classId)
+                                linkCount++;
+                            }
+                        })
+
+                        item.linkCount = linkCount
+
+                        return item
+                    })
+                })
+                this.orgHiteachData = JSON.stringify(temp)
+                this.hiteachData = temp
+
+                if(this.classroomList.length > 0){
+                    this.classroomList.forEach(item => {
+                        item.style = "normal"
+                        if(linkedClassIds.indexOf(item.id)){
+                            item.style = "smart"
+                        }
+                    })
+                }
+            },
+            getHiteachList: async function() {
+                let data = []
+                await this.$api.classroom.GetHiteachList(this.$store.state.user.schoolCode).then(
+                    (res) => {
+                        data = res.serial
+                    },
+                    (err) => {
+                        this.$Message.error('API error!')
+                    }
+                )
+                return data
+            },
+            openHiSub(index){
+                if(this.showHiteachData.length > 0 && this.showHiteachData[index].deviceMax > 1){
+                    this.showHiteachData[index].subOpen = !this.showHiteachData[index].subOpen
+                }
+            },
+            isSmart: function(classId){
+                let style = false
+                if(this.orgHiteachData != ''){
+                    let orgHiData = JSON.parse(this.orgHiteachData)
+                    let exist = orgHiData.some( item => {
+                        let dbexist;
+                        if(Array.isArray(item.deviceBound) && item.deviceBound.length > 0) {
+                            dbexist = item.deviceBound.some(dbItem => {
+                                return dbItem.classId == classId
+                            })                            
+                        }
+                        return dbexist
+                    })
+                    style = exist
+                }
+                return style
+            },
+            pushCheckbox(deviceId, disabled){
+                if(!disabled){
+                    document.getElementById('checkbox-' + deviceId).click()
+                }
+            },
+            testSave(){ // 保留這個Fuction 等API來再用
+
+                // 準備saveData 給API
+                let saveData = {
+                    classId : this.classroomListShow[this.curClassIndex].id, // 被綁定的教室id
+                    linkList: []
+                }
+
+                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({
+                                    id: item.id, // 產品id
+                                    deviceId: db.deviceId // 裝置id
+                                })
+                            }
+                        })
+                    }
+                })
+
+                // API callback 後
+                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[tempIndex] = temp
+                    }
+                })
+
+                this.hiteachData = orgHiData
+                this.orgHiteachData = JSON.stringify(orgHiData)
+
             }
         },
         mounted() {
             this.filterClassname()
-            this.filterCode()
-            
+            // this.filterCode()
+            this.initData()
+            this.initHiteachData()
             this.tableHeight = document.getElementById('class-list').clientHeight + 45
         },
         beforeRouteLeave(to, from, next) {
@@ -1369,7 +1445,7 @@
             if (this.$access.can('admin.*|classroom-upd')) this.editStatus = this.noStatus
             this.getClassroom()
             this.$store.dispatch('teachers/getTeacherList').then(res => { })
-        }
+        }        
     }
 </script>
 <style lang="less" scoped>

+ 44 - 9
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/Index.vue

@@ -4,15 +4,16 @@
 
 <template>
   <div id="serviceDriveAuth">
+    <Loading v-if="isloading == true"></Loading>
     <Row>
       <i-col :xs="24" :sm="24" :md="24" :lg="7">
-        <ServiceList :serviceList="serviceList" />
+        <ServiceList :serviceList="serviceList" :windowWidth='windowWidth' :windowHeight='windowHeight'/> 
       </i-col>
        <i-col :xs="24" :sm="24" :md="24" :lg="11">
-        <HiteachAuthList :hiteachAuthList="hiteachAuthList" />
+        <HiteachAuthList :hiteachAuthList="hiteachAuthList" :windowWidth='windowWidth' :windowHeight='windowHeight'/>
       </i-col>
       <i-col :xs="24" :sm="24" :md="24" :lg="6">
-        <SpaceStatus :spaceStatus="spaceStatus" />
+        <SpaceStatus :spaceStatus="spaceStatus" :windowWidth='windowWidth' :windowHeight='windowHeight' :isloading='isloading'/>
       </i-col>
     </Row>
   </div>
@@ -25,7 +26,8 @@ import PersonalPhoto from "@/components/public/personalPhoto/Index.vue";
 import ServiceList from "./SubComponents/ServiceList.vue";
 import Spacebar from "./SubComponents/Spacebar.vue";
 import HiteachAuthList from "./SubComponents/HiteachAuthList.vue";
-import SpaceStatus from "./SubComponents/SpaceStatus.vue" 
+import SpaceStatus from "./SubComponents/SpaceStatus.vue" ;
+import Loading from '@/common/Loading.vue';
 export default {
   name: "serviceDriveAuth",
   components: {
@@ -33,24 +35,43 @@ export default {
     ServiceList,
     Spacebar,
     HiteachAuthList,
-    SpaceStatus
+    SpaceStatus,
+    Loading,
+  
   },
   watch: {},
 
   data() {
-    return {};
+    return {
+      isloading: true,
+      windowWidth: window.innerWidth,
+      windowHeight: window.innerHeight,
+    };
   },
 
   computed: {
     ...mapGetters({
       serviceList: "serviceDriveAuth/getServiceList",
       hiteachAuthList:"serviceDriveAuth/getHiteachAuthList",
-       spaceStatus:"serviceDriveAuth/getSpaceStatus"
+      spaceStatus:"serviceDriveAuth/getSpaceStatus"
     }),
   },
 
+
+
   methods: {
-    getDataAsyc() {
+    getSchoolProduct(schoolCode){
+      this.$store.dispatch("serviceDriveAuth/getSchoolProduct",schoolCode).then(
+        (res) => {
+          this.isloading=false
+          this.$Message.success("取得API資料");
+        },
+        (err) => {
+          this.$Message.error("取得資料失敗");
+        }
+      );
+    },
+    /*getDataAsyc() {
       this.$store.dispatch("serviceDriveAuth/getServiceListDataAsyc").then(
         (res) => {
           this.$Message.success("取得Mock資料");
@@ -59,11 +80,25 @@ export default {
           this.$Message.error("取得資料失敗");
         }
       );
+    },*/
+     onResize() {
+      this.windowWidth = window.innerWidth;
+      this.windowHeight = window.innerHeight;
     },
   },
   mounted() {
-    this.getDataAsyc();
+    //console.log(this.$store.state.user.schoolCode)
+   
+    this.getSchoolProduct(this.$store.state.user.schoolCode);
+    //this.getDataAsyc();
+     this.$nextTick(() => {
+      window.addEventListener("resize", this.onResize);
+    });
   },
+  beforeDestroy() {
+    window.removeEventListener("resize", this.onResize);
+  },
+
 };
 </script>
 <style lang="less" >

+ 4 - 3
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/AclassOneChart.vue

@@ -5,6 +5,7 @@
 <script>
 export default {
   name: "AclassOneChart",
+  props:['less','used','avaliable'],
   data() {
     return {
       e: event,
@@ -12,9 +13,9 @@ export default {
 
       mockdata: {
         pieNumData: [
-          { value: 74, name: "固定分配數" },
-          { value: 84, name: "動態分配數" },
-          { value: 500-74-84, name: "剩餘分配數" },
+          { value: this.used, name: "固定分配數" },
+          { value: this.less, name: "動態分配數" },
+          { value:this.avaliable-this.less-this.used, name: "剩餘分配數" },
         ],
       },
     };

+ 42 - 3
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/HiteachAuthList.less

@@ -1,4 +1,8 @@
 .hiteachAuth-list {
+  .no-data-text{
+    text-align: center;
+    padding:40px;
+  }
   .ivu-select-dropdown {
     border-radius: 4px !important;
     width: auto;
@@ -49,6 +53,18 @@
     padding: 20px;
     padding-bottom: 10px;
     padding-left: 30px;
+    .device-max {
+      position: relative;
+      top: 0px;
+      left: 0px;
+      margin-right:4px;
+      display: inline-block;
+      font-size: 8px !important;
+      font-weight: bolder;
+      padding: 2px 5px;
+      background-color: #1cc0f3;
+      border-radius: 3px;
+    }
     .nomal-text {
       font-size: 10px;
       color: #8d8d8d;
@@ -58,6 +74,15 @@
       margin-top: -5px;
       font-weight: 500;
     }
+    .info-num-small{
+      font-size: 30px;
+      display: inline-block;
+      font-weight: 500;
+      position: relative;
+      line-height: 25px;
+      left:15px;
+      top: 6px;
+    }
   }
 
   .action-btn {
@@ -79,9 +104,9 @@
    
   }
   .hiteach-list {
-    height: 76.5vh;
-    padding-bottom: 50px;
-    overflow: scroll;
+   
+    padding-bottom: 80px;
+    overflow: auto;
     border-top: 1px solid rgba(98, 97, 101, 0.4);
     .hiteach-item {
       padding: 23px 0px;
@@ -219,6 +244,20 @@
           border-radius: 4px !important;
         }
       }
+      .remove-pc{
+        animation: 2s removeOut;
+      
+        @keyframes removeOut {
+          0%{
+            transform: translate(0px);
+            
+          }
+          100%{
+            transform: translate(130px);
+            opacity: 0;
+          }
+        }
+      }
       .pclist-empty {
         padding: 10px 50px;
         color: #1cc0f3;

+ 246 - 97
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/HiteachAuthList.vue

@@ -1,31 +1,66 @@
 <template>
   <div class="hiteachAuth-list">
+    <!-- 新增知识点弹窗 -->
+    <Modal
+      id="hiteachAuth-modal"
+      v-model="isOpenRemove"
+      :title="'確定解除授權'"
+      ref="pointRef"
+    >
+      <p>當前您所選中的PC【{{ currentConfirmRemovePC }}】,確定解除授權?</p>
+      <p>解除授權後,可在教室中的電腦再次進行綁定授權。</p>
+
+      <div slot="footer">
+        <Button type="text" @click="isOpenRemove = false">取消</Button>
+        <Button type="primary" @click="removeUserPC(currentConfirmRemovePC)"
+          >確定</Button
+        >
+      </div>
+    </Modal>
+    <Loading v-if="isloading == true"></Loading>
     <div class="service-list">
       <div class="service-listTitle">智慧教室授權管理</div>
       <Row>
-        <i-col :xs="24" :sm="24" :md="24" :lg="16">
+        <i-col :xs="24" :sm="24" :md="24" :lg="19">
           <Row>
-            <i-col :xs="24" :sm="24" :md="24" :lg="8">
+            <i-col :xs="24" :sm="24" :md="24" :lg="13">
               <div class="hiteach-info">
                 <p class="nomal-text">Hiteach序號數</p>
-                <p class="info-num">{{hiteachListItemIsOpenVuex.length}}</p>
+                <Row>
+                  <i-col :xs="24" :sm="24" :md="24" :lg="12">
+                    <p class="device-max">大量</p>
+                    &nbsp;<span class="nomal-text">授權</span>
+                    <p class="info-num-small">{{ serialInfo.multiAuthNum }}</p>
+                  </i-col>
+                  <i-col :xs="24" :sm="24" :md="24" :lg="12">
+                    <p class="device-max">單一</p>
+                    &nbsp;<span class="nomal-text">授權</span>
+                    <p class="info-num-small">{{ serialInfo.singleAuthNum }}</p>
+                  </i-col>
+                </Row>
+
+                <!--p class="info-num">{{ serialInfo.totalNum }}</p-->
               </div>
             </i-col>
-            <i-col :xs="24" :sm="24" :md="24" :lg="8">
+            <i-col :xs="24" :sm="24" :md="24" :lg="5">
               <div class="hiteach-info">
-                <p class="nomal-text">可啟用Hiteach序號數</p>
-                <p class="info-num">{{(hiteachListItemIsOpenVuex.length*225/18).toFixed(0)}}</p>
+                <p class="nomal-text">可啟用裝置數</p>
+                <p class="info-num">
+                  {{ serialInfo.couldActiveNum }}
+                </p>
               </div>
             </i-col>
-            <i-col :xs="24" :sm="24" :md="24" :lg="8">
+            <i-col :xs="24" :sm="24" :md="24" :lg="6">
               <div class="hiteach-info">
-                <p class="nomal-text">已啟用Hiteach序號數</p>
-                <p class="info-num">{{(hiteachListItemIsOpenVuex.length*25/8).toFixed(0)}}</p>
+                <p class="nomal-text">已啟用裝置數</p>
+                <p class="info-num">
+                  {{ serialInfo.activeNum }}
+                </p>
               </div>
             </i-col>
           </Row></i-col
         >
-        <i-col :xs="24" :sm="24" :md="24" :lg="8">
+        <i-col :xs="24" :sm="24" :md="24" :lg="5">
           <div class="action-btn" @click="gotoHiteachMgmt()">
             管理Hiteach教室
           </div>
@@ -34,42 +69,53 @@
     </div>
     <div class="hiteach-info-border" />
     <div class="hiteach-type-filter">
+       <!--篩選選單-->
       <Dropdown @on-click="filterList">
         <a class="title" href="javascript:void(0)">
-          <span v-if="filterType == '1'">顯示全部</span>
-          <span v-if="filterType == '2'">僅顯示HiTeach STD序號</span>
-          <span v-if="filterType == '3'">僅顯示HiTeach PRO序號</span>
-          <span v-if="filterType == '4'">僅顯示HiTeach TBL序號</span>
-          <span v-if="filterType == '5'">僅顯示已完成綁定的序號</span>
-          <span v-if="filterType == '6'">僅顯示已到期序號</span>
+          <span
+            v-for="item in filterListItem"
+            v-show="filterType == item.name"
+            :key="item.name"
+            >{{ item.text }}</span
+          >
+
           <span><Icon type="ios-arrow-down"></Icon></span>
         </a>
         <DropdownMenu slot="list">
-          <DropdownItem name="1">顯示全部</DropdownItem>
-          <DropdownItem name="2">僅顯示HiTeach STD序號</DropdownItem>
-          <DropdownItem name="3">僅顯示HiTeach PRO序號</DropdownItem>
-          <DropdownItem name="4">僅顯示HiTeach TBL序號</DropdownItem>
-          <DropdownItem name="5">僅顯示已完成綁定的序號</DropdownItem>
-          <DropdownItem name="6">僅顯示已到期序號</DropdownItem>
+          <DropdownItem
+            v-for="item in filterListItem"
+            :name="`${item.name}`"
+            :key="item.name"
+            >{{ item.text }}</DropdownItem
+          >
         </DropdownMenu>
       </Dropdown>
     </div>
-    <div
-      class="hiteach-list"
-      v-for="(item, index) in hiteachAuthList"
-      :key="index"
-    >
+    <div class="hiteach-list" :style="{ height: windowHeight - 250 + 'px' }">
+      <!--無資料時-->
+       <div
+        v-if="
+         serialInfo.multiAuthNum==0&& serialInfo.singleAuthNum==0
+        "
+        class="no-data-text"
+      >
+        <img src="../../../assets/icon/no_data.png" width="120" />
+        <p style="color: #808080">暂无数据</p>
+      </div>
+
+
       <div
         class="hiteach-item"
-        v-for="(i, index) in item.serials.auth"
-        :key="index + 'a'"
+        v-for="(i, index) in hiteachAuthList"
+        :key="i.id"
         v-show="filterCondition(i)"
       >
         <div
           class="detail-btn"
           @click="setIntroOpen(index)"
-          v-if="i.endDate != 1 && i.deviceBound.length != 0"
+          v-if="i.endDate != 1 && i.deviceBound.length != 0 && i.deviceMax != 1"
         >
+          <!--只要是單一就直接展開無需開關控件-->
           <Icon
             v-show="hiteachListItemIsOpen[index].isOpen == false"
             type="ios-arrow-down"
@@ -83,102 +129,103 @@
           <i-col :xs="24" :sm="24" :md="24" :lg="3">
             <div class="hicon-wrap">
               <v-icon
-                v-if="i.prodName === 'STD'"
+                v-if="i.prodCode === 'J223IZ6M'" 
                 iconClass="hiteach_std"
               /><v-icon
-                v-if="i.prodName === 'PRO'"
+                v-if="i.prodCode === 'J223IZAM'"
                 iconClass="hiteach_pro"
-              /><v-icon v-if="i.prodName === 'TBL'" iconClass="hiteach_tbl" />
+              /><v-icon v-if="i.prodCode === '3222C6D2'" iconClass="hiteach_tbl" />
             </div>
           </i-col>
           <i-col :xs="24" :sm="24" :md="24" :lg="21">
             <p class="title">
-              {{ i.prodGroup }} {{ i.prodName }}
+            
+              <span v-if="i.prodCode === 'J223IZ6M'" >HiTeach STD </span>
+              <span v-if="i.prodCode === '3222C6D2'" >HiTeach TBL </span>
+              <span v-if="i.prodCode === 'J223IZAM'" >HiTeach PRO </span>
               <span class="device-max">{{
                 i.deviceMax == 1 ? "單一" : "大量"
               }}</span>
             </p>
-            <p class="serial" :class="{ 'overtime-text': i.endDate == 1 }">
+            <p
+              class="serial"
+              :class="{ 'overtime-text': i.expireStatus == 'F' }"
+            >
               {{ i.serial }}
             </p>
+
             <p class="app-group">
               <span
+                v-for="(item, index) in aprules"
+                :key="index"
                 class="more-app"
-                :class="{ 'bought-app': i.aprule.sokapp == true }"
-                >蘇格拉底議課</span
-              >
-              <span
-                class="more-app"
-                :class="{ 'bought-app': i.aprule.sokrepor == true }"
-                >蘇格拉底報告</span
-              >
-              <span
-                class="more-app"
-                :class="{ 'bought-app': i.aprule.sokDesktop == true }"
-                >蘇格拉底桌面</span
-              >
-              <span
-                class="more-app"
-                :class="{ 'bought-app': i.aprule.sokvdo == true }"
-                >蘇格拉底影片</span
-              >
-              <span
-                class="more-app"
-                :class="{ 'bought-app': i.aprule.remoteSys == true }"
-                >遠距教室服務</span
-              >
-              <span
-                class="more-app"
-                :class="{ 'bought-app': i.aprule.ezs == true }"
-                >錄播系統</span
+                :class="{
+                  'bought-app': i.aprule.hasOwnProperty(item) == true,
+                }"
+                >{{ $t("schoolBaseInfo." + item) }}</span
               >
             </p>
             <Row>
-              <i-col :xs="24" :sm="24" :md="24" :lg="6"
-                ><p class="nomal-text">
-                  序號到期日:<span class="strong-text" v-if="i.endDate != 1">{{
-                    i.endDate == 0 ? "無到期日" : converTime(i.endDate)
-                  }}</span>
-                  <span class="strong-text" v-if="i.endDate == 1">已到期</span>
-                </p></i-col
-              >
-              <i-col :xs="24" :sm="24" :md="24" :lg="18"
-                ><p class="nomal-text " v-if="i.deviceMax != 1" style="margin-left:4%">
-                  序號使用狀況:<span class="strong-text" v-if="i.endDate != 1"
-                    >{{
-                      i.deviceBound.length > i.deviceMax
-                        ? i.deviceMax
-                        : i.deviceBound.length
-                    }}
-                    / {{ i.deviceMax }} ({{
+              <i-col :xs="24" :sm="24" :md="24" :lg="7">
+                <p class="nomal-text">
+                  序號到期日:<span
+                    class="strong-text"
+                    v-if="i.expireStatus == 'A' || i.expireStatus === ''"
+                    >{{ i.endDate == 0 ? "無到期日" : converTime(i.endDate) }}
+                  </span>
+                  <!--span  class="strong-text" v-if="i.expireStatus === ''">(未啟用)</span-->
+                  <span class="strong-text" v-if="i.expireStatus == 'F'"
+                    >已到期</span
+                  >
+                </p>
+              </i-col>
+              <i-col :xs="24" :sm="24" :md="24" :lg="17"
+                ><p class="nomal-text" style="margin-left: 4%">
+                  序號使用狀況:<span class="strong-text"
+                    >{{ i.deviceBound.length }} / {{ i.deviceMax }} ({{
                       (i.deviceBound.length > i.deviceMax
                         ? 100
                         : (i.deviceBound.length / i.deviceMax) * 100
                       ).toFixed(0) + "%"
                     }})</span
                   >
-                  <span class="strong-text" v-if="i.endDate == 1"
-                    >0 / {{ i.deviceMax }} (0%)</span
-                  >
                 </p></i-col
               >
             </Row>
           </i-col>
         </Row>
+        <!--只要是單一就直接展開無需開關控件-->
         <ul
           class="pclist"
-          v-if="i.endDate != 1 && hiteachListItemIsOpen[index].isOpen == true"
+          v-if="
+            (i.endDate != 1 && hiteachListItemIsOpen[index].isOpen == true) ||
+            i.deviceMax == 1
+          "
         >
           <li
+            :id="`PC${pc.uuid != null ? pc.uuid : pc.uuid2}`"
             v-for="(pc, index) in i.deviceBound"
             :key="index + 'pc'"
             v-show="i.deviceMax != 1 ? index < i.deviceMax : index < 1"
             class="pclist-item"
           >
-            <p class="pc-title">UserPC-{{ pc.uuid }}</p>
-            <p class="nomal-text">{{ pc.deviceIp }} | {{ pc.deviceMac }}</p>
-            <p class="nomal-text">{{ pc.computer }}</p>
-            <div class="action-btn"><Icon type="md-close" /> 解除授權</div>
+            <p class="pc-title">
+              {{ pc.pcname == null ? "未命名機台" : pc.pcname }}
+            </p>
+            <p class="nomal-text">
+              {{ pc.ip }} | {{ pc.uuid != null ? pc.uuid : pc.uuid2 }}
+            </p>
+            <p class="nomal-text">
+              {{ pc.os }}&nbsp;{{ pc.cpu }}&nbsp;{{
+                (pc.ram / 1000).toFixed(1)
+              }}G RAM
+            </p>
+            <div
+              class="action-btn"
+              @click="comfirmRemovePC(pc.uuid != null ? pc.uuid : pc.uuid2)"
+            >
+              <Icon type="md-close" /> 解除授權
+            </div>
           </li>
           <!--div v-if="i.deviceBound==0" class='pclist-empty' >尚未綁定任何機台</div-->
         </ul>
@@ -191,14 +238,48 @@
 import "@/icons/svg/hiteach_std.svg";
 import "@/icons/svg/hiteach_pro.svg";
 import "@/icons/svg/hiteach_tbl.svg";
+import Loading from "@/common/Loading.vue";
 import { mapGetters } from "vuex";
 export default {
-  props: ["hiteachAuthList"],
+  props: ["hiteachAuthList", "windowHeight", "windowWidth"],
   name: "HiteachAuthList",
+  components: {
+    Loading,
+  },
   data() {
     return {
+      filterListItem: [
+        {
+          name: 1,
+          text: "顯示全部",
+        },
+        {
+          name: 2,
+          text: "僅顯示HiTeach STD序號",
+        },
+        {
+          name: 3,
+          text: "僅顯示HiTeach PRO序號",
+        },
+        {
+          name: 4,
+          text: "僅顯示HiTeach TBL序號",
+        },
+        {
+          name: 5,
+          text: "僅顯示已完成綁定的序號",
+        },
+        {
+          name: 6,
+          text: "僅顯示已到期序號",
+        },
+      ],
+      tempRemovePCs: [], //暫存解綁的UserPC的Mac
       hiteachListItemIsOpen: {},
       filterType: "1",
+      currentConfirmRemovePC: "", //暫存讓使用者確認的UserPC
+      isOpenRemove: false,
+      isloading: false,
     };
   },
   created() {
@@ -206,43 +287,83 @@ export default {
   },
   computed: {
     ...mapGetters({
+      serialInfo: "serviceDriveAuth/getHiteachAuthListInfo",
+      aprules: "schoolBaseInfo/getAprules", //讀取周邊產品列表
       hiteachListItemIsOpenVuex: "serviceDriveAuth/getHiteachListItemIsOpen",
     }),
   },
   watch: {
     hiteachAuthList: function (value) {
       //根據服務的長度生出開關,只要服務數量變動就必須重置
+     
       let temp = [];
-      let hiteachlistlength = value[0].serials.auth.length;
-      for (let i = 0; i < hiteachlistlength; i++) {
+
+      for (let i = 0; i < value.length; i++) {
         temp.push({
           index: i,
           isOpen: false,
         });
       }
-      console.log(temp);
+      console.log(value, temp);
       this.hiteachListItemIsOpen = temp;
     },
   },
   methods: {
+    //先讓使用者確定是否解除授權
+    comfirmRemovePC(mac) {
+      this.isOpenRemove = true;
+      this.currentConfirmRemovePC = mac;
+    },
+    removeUserPC(mac) {
+      //假設回傳deviceMac,進行解綁動作
+      this.isOpenRemove = false;
+      if (this.tempRemovePCs.includes(mac) == false) {
+        this.tempRemovePCs.push(mac);
+      }
+      //console.log(this.tempRemovePCs);
+      document.getElementById(`PC${mac}`).classList.add("remove-pc");
+      setTimeout(() => {
+        document.getElementById(`PC${mac}`).style.display = "none";
+
+        this.$Message.success("已成功解除授權!");
+      }, 1800);
+      this.isloading = true;
+      setTimeout(() => {
+        this.getDataAsyc();
+        this.isloading = false;
+      }, 3000);
+    },
     gotoHiteachMgmt() {
       this.$router.push("/home/classroom");
     },
+    getDataAsyc() {
+      this.$store.dispatch("serviceDriveAuth/getServiceListDataAsyc").then(
+        (res) => {
+          this.$Message.success("取得Mock資料");
+        },
+        (err) => {
+          this.$Message.error("取得資料失敗");
+        }
+      );
+    },
     filterCondition(item) {
       switch (this.filterType) {
         case "1":
           return true;
 
         case "2":
-          return item.prodName === "STD";
+          return item.prodCode === 'J223IZ6M'
+          //return item.prodName === "STD";
         case "3":
-          return item.prodName === "PRO";
+          return item.prodCode === 'J223IZAM'
+          //return item.prodName === "PRO";
         case "4":
-          return item.prodName === "TBL";
+           return item.prodCode === '3222C6D2'
+          //return item.prodName === "TBL";
         case "5":
-          return item.deviceBound.length != 0 && item.endDate != 1;
+          return item.deviceBound.length != 0;
         case "6":
-          return item.endDate == 1;
+          return item.expireStatus === "F";
 
         default:
           return true;
@@ -271,7 +392,7 @@ export default {
       this.hiteachListItemIsOpen[index].isOpen = !this.hiteachListItemIsOpen[
         index
       ].isOpen;
-      console.log(index, this.hiteachListItemIsOpen);
+      //console.log(index, this.hiteachListItemIsOpen);
     },
   },
 };
@@ -279,4 +400,32 @@ export default {
 
 <style lang='less'>
 @import "./HiteachAuthList.less";
+
+#hiteachAuth-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>

+ 0 - 35
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/ServiceList.css

@@ -1,35 +0,0 @@
-.service-list .service-listTitle {
-  padding: 14px 0px;
-  color: #e2e2e3;
-  font-size: 14px;
-  border-bottom: 1px solid #464646;
-  margin-left: 20px;
-}
-.service-list .listTitle-noMargin {
-  margin-left: 0px;
-  padding-left: 20px ;
-}
-.service-list .service-search {
-  position: relative;
-  margin-top: -60px;
-  margin-bottom: 10px;
-  padding: 20px 0px;
-}
-.service-list .service-search .action-btn-icon {
-  cursor: pointer;
-  font-size: 20px;
-  position: absolute;
-  top: 25px;
-  right: 10px;
-}
-.service-list .service-search .dark-iview-input {
-  position: absolute;
-  right: 10px;
-  width: 50%;
-}
-.service-list .serviceList-wrap {
-  padding-left: 20px;
-}
-.service-list .serviceList-wrap .service-item .service-name {
-  font-size: 20px;
-}

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

@@ -32,9 +32,9 @@
 
   .serviceList-wrap {
     background-color: #232227;
-    height: 87.5vh;
+    //height: 90vh;
     overflow: auto;
-   
+    padding-bottom:20px;
     .service-item {
       background-color: #2b2a2f;
       border: 1px solid rgba(98, 97, 101, 0.4);

+ 85 - 37
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/ServiceList.vue

@@ -1,7 +1,10 @@
 <template>
   <div class="service-list">
     <div class="service-listTitle listTitle-noMargin">服務授權列表</div>
-    <div class="serviceList-wrap">
+    <div
+      class="serviceList-wrap"
+      :style="{ height: windowHeight - 116 + 'px' }"
+    >
       <div
         class="service-item"
         v-for="(item, index) in serviceList"
@@ -17,67 +20,87 @@
             type="ios-arrow-up"
           />
         </div>
-
-        <p class="service-name">{{ item.name }}</p>
-        <p class="isbuy" v-if="item.isBuy == true">已購買</p>
-        <p class="unbuy" v-else>未購買</p>
-        <p class="nomal-text">
+        <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="isbuy" v-else>已購買</p>
+       
+        <p class="nomal-text" >
           服務啟用 / 到期日:
-          <span class="strong-text" v-if="item.isBuy == true"
-            >{{ item.authTimeStart }} - {{ item.authTimeEnd }}</span
+          <span class="strong-text" v-if="item.startDate != 0"
+            >{{ converTime(item.startDate) }} -
+            {{ converTime(item.endDate) }}</span
           >
-          <span class="strong-text" v-else>--</span>
+          <span class="strong-text" v-if="item.startDate == 0">--</span>
         </p>
-        <div>
-          <Row>
+        <div v-if="item.hasOwnProperty('notbuy')!=true">
+          <Row class="time-bar-wrap">
             <i-col :xs="24" :sm="24" :md="24" :lg="16">
-              <div class="time-bar" v-if="item.isBuy == true"></div>
+              <div class="time-bar" v-if="item.startDate != 0"></div>
 
               <div
                 class="remain-bar"
-                v-if="item.isBuy == true"
+                v-if="item.startDate != 0"
                 :style="{
                   width:
                     (
-                      (dateRemain(today(), item.authTimeEnd) /
-                        dateRemain(item.authTimeStart, item.authTimeEnd)) *
+                      (dateRemain(today(), converTime(item.endDate)) /
+                        dateRemain(
+                          converTime(item.startDate),
+                          converTime(item.endDate)
+                        )) *
                       100
                     ).toFixed(2) + '%',
                   'background-color': colorPercent(
-                    item.authTimeStart,
-                    item.authTimeEnd
+                    converTime(item.startDate),
+                    converTime(item.endDate)
                   )
                     ? '#ad3435'
                     : '#1cc0f3',
                 }"
               ></div>
             </i-col>
-            <i-col :xs="24" :sm="24" :md="24" :lg="8" v-if="item.isBuy == true">
+            <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(item.authTimeStart, item.authTimeEnd)
+                  color: colorPercent(
+                    converTime(item.startDate),
+                    converTime(item.endDate)
+                  )
                     ? '#ad3435'
                     : '#1cc0f3',
                 }"
-                >{{ dateRemain(today(), item.authTimeEnd) }}</span
+                >{{ dateRemain(today(), converTime(item.endDate)) }}</span
               ><span class="remainDay">天</span>
             </i-col>
           </Row>
         </div>
         <!--給AclassOne-->
-        <div class="detailinfo-block" v-if="index == 0 && serviceIntroIsOpen[index].isOpen == true">
+        <div
+          class="detailinfo-block"
+          v-if="index == 0 && serviceIntroIsOpen[index].isOpen == true"
+        >
           <p class="nomal-text auth-num">
-            授權數量:<span class="strong-text">{{ item.authNum }}</span>
+            授權數量:<span class="strong-text">{{ item.avaliable }}</span>
           </p>
           <Row>
             <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.fixedNum }} ({{
-                    ((item.fixedNum / item.authNum) * 100).toFixed(1)
+                  >{{ item.used }} ({{
+                    ((item.used / (item.avaliable)) * 100).toFixed(1)
                   }}%)</span
                 >
               </p>
@@ -85,8 +108,8 @@
               <div class="title-rec"></div>
               <p class="nomal-text title-withRec">
                 動態分配數:<span class="strong-text"
-                  >{{ item.dynamicNum }} ({{
-                    ((item.dynamicNum / item.authNum) * 100).toFixed(1)
+                  >{{ item.less }} ({{
+                    ((item.less / (item.avaliable)) * 100).toFixed(1)
                   }}%)</span
                 >
               </p></i-col
@@ -96,26 +119,31 @@
                 總使用率:<span class="strong-text">
                   {{
                     (
-                      ((item.dynamicNum + item.fixedNum) / item.authNum) *
+                      ((item.less+item.used) / (item.avaliable)) *
                       100
                     ).toFixed(1)
                   }}%</span
                 >
               </p></i-col
             >
-            <i-col :xs="24" :sm="24" :md="24" :lg="7">  <AclassOneChart /></i-col>
+            <i-col :xs="24" :sm="24" :md="24" :lg="7">
+              <AclassOneChart :less='item.less' :used='item.used' :avalible='item.avaliable'
+            /></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">詳細服務內容:</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">{{ item.intro }}</p></i-col
+              <p class="intro-text">實際行銷簡介文本待提供</p></i-col
             >
           </Row>
         </div>
@@ -126,27 +154,30 @@
 
 <script>
 import router from "../../../router";
-import AclassOneChart from './AclassOneChart'
+import AclassOneChart from "./AclassOneChart";
 import { mapGetters } from "vuex";
 export default {
-  props: ["serviceList"],
+  props: ["serviceList", "windowWidth", "windowHeight"],
   name: "ServiceList",
   data() {
     return {
       serviceIntroIsOpen: {},
+      oriIndex: "",
     };
   },
-  components:{
-    AclassOneChart
+  components: {
+    AclassOneChart,
   },
   computed: {
     ...mapGetters({
       serviceIntroIsOpenVuex: "serviceDriveAuth/getServiceIntroIsOpen",
+      serviceNameList: "serviceDriveAuth/getServiceNameList",
     }),
   },
   created() {
     this.serviceIntroIsOpen = this.serviceIntroIsOpenVuex;
   },
+
   watch: {
     serviceList: function () {
       //根據服務的長度生出開關,只要服務數量變動就必須重置
@@ -157,12 +188,29 @@ export default {
           index: i,
           isOpen: false,
         });
+        temp[0].isOpen=true //預設把AclassOne細節打開
       }
       this.serviceIntroIsOpen = temp;
     },
   },
 
   methods: {
+    converTime: function (time) {
+      //time: Unix timestamp(秒)
+      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;
+    },
     gotoStudentManagent() {
       this.$router.push("/home/studentAccount");
     },
@@ -174,7 +222,7 @@ export default {
     setIntroOpen(index) {
       this.serviceIntroIsOpen[index].isOpen = !this.serviceIntroIsOpen[index]
         .isOpen;
-      console.log(index, this.serviceIntroIsOpen);
+      //console.log(index, this.serviceIntroIsOpen);
     },
     today() {
       var today = new Date();

+ 83 - 36
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceChart.vue

@@ -1,4 +1,4 @@
-<!--課堂參與人次觀察之環形圖-->
+
 <template>
   <div id="space-chart"></div>
 </template>
@@ -6,56 +6,88 @@
 <script>
 export default {
   name: "SpaceChart",
+  props: ["spaceStatus", "remainDays"],
   data() {
     return {
-      currentColor:"#fff",
+      currentColor: "#fff",
       e: event,
       index: 0,
       mockdata: {
-        pieNumData: [
-          { value: (573 * 0.03).toFixed(0), name: "文件", days: 66 },
-          { value: (573 * 0.07).toFixed(0), name: "影片", days: 66 },
-          { value: (573 * 0.16).toFixed(0), name: "圖片", days: 66 },
-          { value: (573 * 0.04).toFixed(0), name: "題目與試卷", days: 26 },
-          { value: (573 * 0.1).toFixed(0), name: "其他", days: 66 },
-          { value: (573 * 0.25).toFixed(0), name: "學生使用", days: 66 },
-          { value: (573 * 0.25).toFixed(0), name: "已分配至教師", days: 166 },
-          { value: 1010 - 573, name: "未使用" , days: 166},
-        ],
+        pieNumData: [],
       },
     };
   },
   mounted() {
-    this.drawLine(this.mockdata.pieNumData);
-    
+    //this.drawLine(this.mockdata.pieNumData);
   },
   created() {
     this.$emit("StudentSourceInfo", this.mockdata.pieNumData);
   },
-  watch:{
-    currentColor:function(val){
-
-      //console.log(document.getElementById('color-title'))
-       }
- 
+  watch: {
+    spaceStatus: function (value) {
+      (this.mockdata.pieNumData = [
+        { value: value.doc, name: "文件", days: 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,
+        },
+        { value: value.other, name: "其他", days: this.remainDays },
+        {
+          value: value.student,
+          name: "學生使用",
+          days: this.remainDays,
+        },
+        {
+          value: value.teacher,
+          name: "已分配至教師",
+          days: this.remainDays,
+        },
+        {
+          value: value.unUsed,
+          name: "未使用",
+          days: this.remainDays,
+        },
+      ]),
+        this.drawLine(this.mockdata.pieNumData);
+    },
   },
   methods: {
+    byteToGibiByte(n) {
+      //統一轉成GB 2的30次方
+      return (n / Math.pow(2, 30)).toFixed(2);
+    },
+
     drawLine(pieNumData) {
       //基于准备好的dom,初始化echarts实例
-      let _this=this;
+      let _this = this;
       let myChart = this.$echarts.init(document.getElementById("space-chart")); //绘制图表
       myChart.setOption({
         tooltip: {
           trigger: "item",
+          backgroundColor: "rgba(50,50,50,0.9)",
+          borderColor: "#8D8D8D",
+          borderWidth: 1,
           formatter: function (v) {
-           _this._data.currentColor=v.color
+            if (v.color != "#48474c") _this._data.currentColor = v.color;
+            else _this._data.currentColor = "white";
 
             return `
             <div class='chart-toolTip'> 
-             <p :style="{'color':${v.color}" >${v.data.name}空間</p>
-             <p class='title'>${v.data.name}空間數:<span class='value'>${v.value} G</span></p>
-             <p class='title'>${v.data.name}空間比率:<span class='value'>${v.percent} %</span></p>
-             <p class='title'>剩餘天數:<span class='value'>${v.data.days} days</span> </p> </div> 
+             <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)).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> 
             `;
           },
         },
@@ -161,9 +193,16 @@ export default {
         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,
@@ -187,12 +226,20 @@ export default {
       });
       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);
     },
   },
 };
@@ -204,21 +251,21 @@ export default {
   height: 250px;
   margin-top: -20px;
   margin-bottom: 20px;
-  margin-left:2%;
-  .chart-toolTip{
-    outline:1px solid rgba(255, 255, 255, 0.5);
-    padding:10px;
-    .title{
-     
+  margin-left: 2%;
+  .chart-toolTip {
+    padding: 10px;
+    #color-title {
+      font-weight: bolder;
+    }
+    .title {
       font-size: 10px;
-      color:gray;
+      color: gray;
       font-weight: bolder;
     }
-    .value{
+    .value {
       font-weight: bolder;
-      color:white
+      color: white;
     }
-
   }
 }
 </style>

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

@@ -1,5 +1,13 @@
 .space-status {
   border-left: 1px solid rgba(98, 97, 101, 0.4);
+  //height: 98.5vh;
+  overflow: auto;
+  //padding-bottom:50px;
+  .no-data-text {
+    text-align:center;
+    padding-top:10%;
+    
+ }
   .service-list {
     position: relative;
     .action-btn {
@@ -80,6 +88,9 @@
       font-size: 10px;
     }
     .buy-item {
+      &:last-child{
+        border-bottom:none
+      }
       .tmd-icon {
         font-size: 30px;
         position: relative;

+ 96 - 22
TEAMModelOS/ClientApp/src/view/serviceDriveAuth/SubComponents/SpaceStatus.vue

@@ -1,48 +1,60 @@
 <template>
-  <div class="space-status">
+  <div class="space-status" :style="{ height: windowHeight - 68 + 'px' }">
     <div class="service-list">
       <div class="service-listTitle">智慧教學服務空間狀態</div>
       <div class="action-btn" @click="gotoTeacherManagent()">
         <Icon class="share-icon" type="md-share" /> 分配教學空間
       </div>
     </div>
-    <Row class="main-wrap">
+    <div
+      v-if="isloading == true||spaceStatus==undefined"
+      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' }"
+    >
       <i-col :xs="24" :sm="24" :md="24" :lg="12">
         <p class="nomal-title">空間總數</p>
         <p class="info-text">
-          <span class="info-num">{{
-            numberWithCommas(spaceStatus.avaliable)
-          }}</span>
-          GB
+          <span class="info-num">{{ totalavaliable }}</span>
+          {{ totalavaliableText }}
         </p>
       </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">{{ numberWithCommas(spaceStatus.used) }}</span>
-          GB
+          <span class="info-num">{{ totalused }}</span>
+          {{ totalusedText }}
         </p>
       </i-col>
 
       <i-col :xs="24" :sm="24" :md="24" :lg="24">
         <div class="chart-wrap">
           <p class="nomal-title">空間使用狀況</p>
-          <SpaceChart />
+          <SpaceChart :spaceStatus="spaceStatus" :remainDays="remainDays" />
           <!--放置餅圖剩下的空間狀態-->
           <div class="remail-data">
-            <p class='title'>未使用空間數</p>
-            <p class='num'>{{1010 - 573}}<span class='small-text'> GB</span></p>
-            <p class='title'>未使用空間比率</p>
-             <p class='num'>45.9<span class='small-text'> %</span></p>
+            <p class="title">未使用空間數</p>
+            <p class="num">
+              {{ unUsed }}<span class="small-text"> {{ unUsedText }}</span>
+            </p>
+            <p class="title">未使用空間比率</p>
+            <p class="num">
+              {{ spaceStatus.unUsedPercent }}<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"
-            >{{ converTime(spaceStatus.lastEndDay) }} (
-            {{ dateRemain(today(), converTime(spaceStatus.lastEndDay)) + "天" }}
-            )</span
+            >{{ converTime(spaceStatus.endDate) }} ({{ remainDays }} 天)</span
           >
         </p>
 
@@ -51,13 +63,26 @@
         </p>
       </i-col>
     </Row>
-    <div class="service-list buy-list">
+    <div
+      class="service-list buy-list"
+      :style="{ visibility: isloading == true ? 'hidden' : 'visible' }"
+    >
       <div class="service-listTitle">購買記錄</div>
+
       <div class="list-wrap">
+        <!--無資料時-->
+        <div
+          v-if="spaceStatus.hasOwnProperty('history') == false"
+          class="no-data-text"
+        >
+          <img src="../../../assets/icon/no_data.png" width="120" />
+          <p style="color: #808080">暂无数据</p>
+        </div>
+        <!--有資料時-->
         <div
           class="buy-item"
-          v-for="(i, index) in spaceStatus.order"
-          :key="index"
+          v-for="(i, orderId) in spaceStatus.history"
+          :key="orderId"
         >
           <Row>
             <i-col :xs="24" :sm="24" :md="24" :lg="3"
@@ -65,14 +90,14 @@
             ></i-col>
             <i-col :xs="24" :sm="24" :md="24" :lg="21"
               ><p class="title">
-                醍摩豆{{ spaceStatus.prodName }}
+                醍摩豆智慧教學服務空間
                 {{ numberWithCommas(i.number) }} {{ i.unit }}
               </p>
               <Row s>
                 <i-col :xs="24" :sm="24" :md="24" :lg="12"
                   ><p class="nomal-text">
                     訂單日期:<span class="strong-text">{{
-                      converTime(i.createDate)
+                      converTime(i.orderDate)
                     }}</span>
                   </p></i-col
                 >
@@ -97,12 +122,61 @@
 import SpaceChart from "./SpaceChart.vue";
 import "@/icons/svg/tmd.svg";
 export default {
-  props: ["spaceStatus"],
+  props: ["spaceStatus", "windowHeight", "windowWidth", "isloading"],
   name: "SpaceStatus",
   components: {
     SpaceChart,
   },
+  data() {
+    return {
+      remainDays: 0,
+      totalavaliable: 0,
+      totalavaliableText: "",
+      totalused: 0,
+      totalusedText: "",
+      unUsed: 0,
+      unUsedText: "",
+    };
+  },
+  watch: {
+    spaceStatus: function (value) {
+      if (value !== undefined) {
+        this.remainDays = this.dateRemain(
+          this.today(),
+          this.converTime(value.endDate)
+        );
+        this.totalavaliable = this.finalFormatNum(value.avaliable);
+        this.totalavaliableText = this.finalFormatText(value.avaliable);
+        this.totalused = this.finalFormatNum(value.totalused);
+        this.totalusedText = this.finalFormatText(value.totalused);
+        this.unUsed = this.finalFormatNum(value.unUsed);
+        this.unUsedText = this.finalFormatText(value.unUsed);
+      }
+    },
+  },
   methods: {
+    finalFormatNum(byte) {
+      return this.numberWithCommas(this.formatBytes(byte)).substr(
+        0,
+        this.numberWithCommas(this.formatBytes(byte)).length - 2
+      );
+    },
+    finalFormatText(byte) {
+      return this.numberWithCommas(this.formatBytes(byte)).substr(
+        this.numberWithCommas(this.formatBytes(byte)).length - 2,
+        this.numberWithCommas(this.formatBytes(byte)).length - 1
+      );
+    },
+
+    formatBytes(bytes, decimals = 2) {
+      if (bytes === 0) return "0 Bytes";
+      const k = 1024;
+      const dm = decimals < 0 ? 0 : decimals;
+      const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
+      const i = Math.floor(Math.log(bytes) / Math.log(k));
+      return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
+    },
+
     gotoTeacherManagent() {
       this.$router.push("/home/teachermgmt");
     },

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

@@ -0,0 +1,334 @@
+.aclassone-auth {
+.dark-iview-select{
+  margin:0 10px;
+  position: relative;
+  
+  width: 180px;
+   .ivu-select-selection{
+    outline: none !important;
+
+    background-color: #333237 !important;
+    border:#4a4a4d 1px solid !important ;
+    &:active,&:hover,&:focus{
+      outline: none !important;
+      border:#909096 1px solid !important ;
+     }
+    
+   }
+  
+}
+.input-num{
+  text-align: center;
+   color: #c0c0c0;
+   font-weight: 700;
+   font-size: 14px;
+   outline: none;
+   background-color: #333237;
+   width: 50px;
+   border-radius: 5px;
+   padding:4px 3px;
+   margin:0 5px;
+   top:-4px;
+   position: relative;
+   border:#4a4a4d 1px solid !important ;
+   &:focus{
+    border:#909096 1px solid !important ;
+   }
+    
+}  
+
+@keyframes slideIn {
+  0% {
+    right: 0%;
+  }
+
+  100% {
+    right: -50%;
+  }
+}
+
+@keyframes slideout {
+  0% {
+    right: 0%;
+  }
+
+  100% {
+    right: -30%;
+  }
+}
+@keyframes fadeIn{
+  0%{
+      opacity: 0;
+  }
+  100%{
+      opacity: 1;
+  }
+
+}
+  position: absolute;
+  background-color: #2b2a2f;
+  border-left: 1px solid #434247;
+  width: 50%;
+  height: 100vh;
+  top: -45px;
+  right: -50% !important;
+  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: 18%;
+    top: 9px;
+    font-size: 24px;
+    cursor: pointer;
+
+    &:hover {
+      color: #1cc0f3;
+    }
+  }
+
+  .dashboard-title {
+    font-size: 18px;
+    padding-top: 28px;
+    padding-left: 15px;
+    color: #f2f2f3;
+    font-weight: 600;
+    letter-spacing: 2px;
+  }
+
+  .dashboard-note {
+    font-size: 10px;
+    padding-left: 15px;
+    color: #797979;
+  }
+
+  .dashboard {
+    padding-right: 25%;
+    padding-top: 36px;
+
+    .info-num {
+      text-align: center;
+      font-size: 28px;
+      font-weight: 500;
+      letter-spacing: 2px;
+      color: #c0c0c0;
+    }
+
+    .info-text {
+      font-size: 12px;
+      text-align: center;
+      margin-top: -4px;
+      margin-bottom: 36px !important;
+      color: #797979;
+    }
+  }
+
+  .tab-btn {
+    font-size: 12px;
+    color: #797979;
+    margin-right: 50px;
+    margin-left: 25px;
+    padding: 7px 0px;
+    cursor: pointer;
+
+    &:hover {
+      color: lighten(#797979, 20%);
+    }
+  }
+
+  .tab-now {
+    color: #f2f2f3;
+    font-weight: 500;
+    border-bottom: 2px solid #1cc0f3;
+  }
+
+  .tabContent-wrap {
+    border-top: 1px solid #434247;
+    border-bottom: 1px solid #434247;
+    margin-top: 15px;
+    padding-right: 25%;
+    margin-left: 10px;
+    animation: fadeIn 0.5s !important;
+
+    .chart-title {
+      font-size: 16px;
+      font-weight: 500;
+
+      text-align: center;
+      margin-top: 65px;
+      color: #777777;
+
+      .chart-totalnum {
+        color: #1cc0f3;
+      }
+    }
+
+    .chart-label {
+      margin-top: 65px;
+      margin-bottom: 40px;
+    }
+
+    .chart-item {
+      margin-bottom: 28px;
+
+      .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;
+      }
+    }
+
+    .chart-note {
+      position: relative;
+      color: #797979;
+      font-size: 10px;
+      text-align: justify;
+      padding-bottom: 10px;
+      padding-left: 5px;
+
+      .list-icon {
+        position: absolute;
+        left: -10px;
+        bottom: 25px;
+      }
+    }
+  }
+    .option-group{
+      padding:15px;
+      animation: fadeIn 0.5s !important;
+      .small-title{
+        font-size: 10px;
+        font-weight: 400;
+        color: #797979;
+        margin-bottom: 25px;
+      }
+      
+      .assign-btn{
+        
+        cursor: pointer;
+        position: absolute;
+        line-height: 20px;
+       
+        right:-50px;
+        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
+        }
+       
+      }
+      
+      .option-text{
+        color: #797979;
+        font-size: 13px;
+        font-weight: 500;
+        line-height: 24px;
+      }
+      .option-item{
+       height: 48px;
+      }
+      .option-item:nth-child(3){
+        margin-top:-4px;
+      }
+    }
+  .unAssign-btn{
+    cursor: pointer;
+    animation: fadeIn 0.7s !important;
+    position: absolute;
+    bottom: 135px;
+    border-radius: 5px;
+    margin: 20px;
+    width: 75%;
+    margin-left: 3%;
+    padding: 10px 12px;
+    font-size: 16px;
+    background-color: #616161;
+    color: white;
+    text-align: center;
+    font-weight: 700;
+
+    &:hover {
+      background-color: darken( #616161, 10%);
+    }
+  }
+  
+  .save-btn {
+    cursor: pointer;
+    animation: fadeIn 0.7s !important;
+    position: absolute;
+    bottom: 80px;
+    border-radius: 5px;
+    margin: 20px;
+    width: 75%;
+    margin-left: 3%;
+    padding: 10px 12px;
+    font-size: 16px;
+    background-color: #1cc0f3;
+    color: white;
+    text-align: center;
+    font-weight: 700;
+    
+    &:hover {
+      background-color: darken(#1cc0f3, 10%);
+    }
+  }
+  .buy-btn{
+    
+      width: 62% !important;
+      margin-left: 10%;
+    
+  }
+}
+
+
+.closeShow {
+  animation: slideout 0.7s !important;
+  right: 30%;
+}
+
+.light-btn{
+  color: #ffffff !important;
+  transition: 0.2s;
+  background-color: #1cc0f3 !important
+}
+.disabled-btn{
+  background-color: #333333 !important;
+  color: #a0a0a0 !important;
+  &:hover {
+    color: #a0a0a0 !important;
+    background-color: #333333 !important;
+  }
+  cursor:not-allowed !important
+}

+ 766 - 0
TEAMModelOS/ClientApp/src/view/student-account/AclassOneAuth.vue

@@ -0,0 +1,766 @@
+<template>
+  <div class="aclassone-auth" :class="{ closeShow: closefromBtn == true }">
+    <!--收回授權弹窗 -->
+    <Modal
+      class="aclassone-auth-modal"
+      v-model="isOpenRemove"
+      :title="'確定收回所有授權'"
+    >
+      <p>收回授權後使用數會直接設置為0,如欲給予授權可針對目標對象進行套用。</p>
+
+      <div slot="footer">
+        <Button type="text" @click="isOpenRemove = false">取消</Button>
+        <Button type="primary" @click="resetAll">確定</Button>
+      </div>
+    </Modal>
+    <div class="title">
+      服務授權管理<span @click="close()"
+        ><v-icon class="close-icon" iconClass="close"
+      /></span>
+    </div>
+    <div class="dashboard-title">AClassONE智慧學伴服務授權</div>
+    <p class="dashboard-note">
+      賦予持有該服務授權的學生TEAM Modal ID使用AClassONE智慧學伴App的權限
+    </p>
+    <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>
+        </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>
+        </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>
+        </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
+    >
+    <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>
+          </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] }"
+            />
+            <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>
+      <!--授權管理-->
+      <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="2"
+            ><div
+              class="assign-btn"
+              style="margin-top: -2px"
+              @click="assignType(1)"
+              :class="{ 'light-btn': currentAssignType == 1 }"
+            >
+              套用
+            </div></i-col
+          >
+        </Row>
+        <Row class="option-item">
+          <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"
+              />項
+            </p></i-col
+          >
+          <i-col :xs="24" :sm="12" :md="12" :lg="2"
+            ><div
+              class="assign-btn"
+              style="margin-top: 2px"
+              @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 == '',
+              }"
+            >
+              套用
+            </div></i-col
+          >
+        </Row>
+        <Row class="option-item">
+          <i-col :xs="24" :sm="12" :md="12" :lg="22"
+            ><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
+              >所有項目
+            </p></i-col
+          >
+          <i-col :xs="24" :sm="12" :md="12" :lg="2"
+            ><div
+              class="assign-btn"
+              style="margin-top: 2px"
+              @click="assignType(3)"
+              :class="{
+                'light-btn': currentAssignType == 3,
+                'disabled-btn': currentFilter != 0 || searchPeriod == '',
+              }"
+            >
+              套用
+            </div></i-col
+          >
+        </Row>
+        <Row class="option-item">
+          <i-col :xs="24" :sm="12" :md="12" :lg="22"
+            ><p class="option-text">
+              套用至
+
+              <!-- 學級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
+          >
+          <i-col :xs="24" :sm="12" :md="12" :lg="2"
+            ><div
+              class="assign-btn"
+              style="margin-top: 2px"
+              @click="assignType(4)"
+              :class="{
+                'light-btn': currentAssignType == 4,
+                'disabled-btn': currentFilter != 1 || searchGrade == '',
+              }"
+            >
+              套用
+            </div></i-col
+          >
+        </Row>
+        <Row class="option-item">
+          <i-col :xs="24" :sm="12" :md="12" :lg="22"
+            ><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
+                >
+                <Option value="noclass">未关联班级</Option> </Select
+              >所有項目
+            </p></i-col
+          >
+          <i-col :xs="24" :sm="12" :md="12" :lg="2"
+            ><div
+              class="assign-btn"
+              style="margin-top: 2px"
+              @click="assignType(5)"
+              :class="{
+                'light-btn': currentAssignType == 5,
+                'disabled-btn': currentFilter != 2 || searchClassroom == '',
+              }"
+            >
+              套用
+            </div></i-col
+          >
+        </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
+          >
+          <i-col :xs="24" :sm="12" :md="12" :lg="2"
+            ><div
+              class="assign-btn"
+              style="margin-top: -4px"
+              @click="assignType(6)"
+              :class="{
+                'light-btn': currentAssignType == 6,
+                'disabled-btn': students.length > totalAuth,
+              }"
+            >
+              套用
+            </div></i-col
+          >
+        </Row>
+      </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>
+    <div v-if="currentTab == 1" class="save-btn" @click="saveAuth()">
+      保存所有變更
+    </div>
+  </div>
+</template>
+
+<script>
+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"],
+  components: {
+    AuthNumChart,
+    CountTo,
+  },
+  data() {
+    return {
+      isOpenRemove: false,
+      searchPeriod: "",
+      searchGrade: "",
+      searchClassroom: "",
+      inputSelectMin: "",
+      inputSelectMax: "",
+      currentTab: 0,
+      clickTab: false,
+
+      totalAuth: 356,
+      usedAuth: 128,
+      currentAssignType: 0, //存放目前套用的類目 0為選,1當前選中,2選中並指定長度,3學制,4學籍,5班級,6全校
+      currentAssignNum: 0, //存放目前所套用所選之數量
+      currentFilter: -1, //當前所選中的篩選器
+
+      tableShowData: [],
+      tableFilterData: [],
+      tableColumns: [],
+      basicCount: 99,
+      pointNum: 0,
+      pieNumData: [
+        { value: 104, name: "固定分配授權數" },
+        { value: 24, name: "今日動態取用數" },
+        { value: 228, name: "今日可用授權數" },
+      ],
+
+      color: ["#eb974e", "#fb62bb", "#00f492"],
+    };
+  },
+  
+  computed: {
+    ...mapGetters({
+      periods: "user/getPeriods", // 學制s
+      grades: "user/getGrades", // 年級
+      classes: "user/getClasses", // 教室ID
+      //students: "schoolBaseInfo/getStudent", // 學生List
+    }),
+    filterGrades: function () {
+      var data = this.grades;
+      if (this.searchPeriod) {
+        let periodId = this.searchPeriod;
+        data = data.filter(function (item) {
+          return item.periodId == periodId;
+        });
+        return data;
+      } else {
+        return [];
+      }
+    },
+    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;
+      } else {
+        return [];
+      }
+    },
+  },
+  watch: {
+    //只要勾選有變動就預設套用第一項
+
+  
+    selected: function (value) {
+      this.currentAssignType = 1;
+      //this.checkSelectIsAuth();
+
+      this.currentAssignNum = this.selected.length;
+    },
+  },
+  methods: {
+    ...mapMutations({
+      setStudents: "schoolBaseInfo/setStudents",
+    }),
+    //Louise Mock 加入授權數,實際應從vuex讀取db api變動之值
+    saveAuth() {
+      console.log();
+      let finalAdd = 0;
+      if (this.currentAssignType == 0) {
+        this.$Message.warning("請先選擇套用,再按保存");
+      }
+
+      //情況1:純勾選
+      if (this.currentAssignType == 1) {
+        this.selected.forEach((item) => {
+          if (this.getMockAuthStatus(item.id) == false) {
+            this.setStudentTarget(item.id);
+            finalAdd++;
+          }
+        });
+
+        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++;
+          }
+        });
+
+        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++;
+          }
+        });
+        if (finalAdd <= this.totalAuth - this.usedAuth) {
+          this.usedAuth = this.usedAuth + finalAdd;
+          this.$Message.success("保存成功,本次新增" + finalAdd + "個");
+          this.$emit("updateAuth");
+        } 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];
+    },
+    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++;
+        }
+      });
+      this.currentAssignNum = finalAdd;
+    },
+
+    assignType(type) {
+      this.currentAssignType = type;
+      switch (type) {
+        case 0:
+          this.currentFilter = -1;
+          this.currentAssignNum = 0;
+          break;
+        case 1:
+          this.currentFilter = -1;
+          this.searchPeriod = "";
+          this.searchGrade = "";
+          this.searchClassroom = "";
+          if (this.selected.length == 0) {
+            this.$Message.warning("請先勾選欲授權之學生 !");
+          }
+          this.currentAssignNum = this.selected.length;
+          //this.checkSelectIsAuth()
+
+          break;
+        case 2:
+          this.currentFilter = -1;
+          this.searchPeriod = "";
+          this.searchGrade = "";
+          this.searchClassroom = "";
+          if (this.selected.length == 0) {
+            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;
+          } else {
+            this.currentAssignNum =
+              parseInt(this.inputSelectMax) -
+              (parseInt(this.inputSelectMin) - 1);
+          }
+
+          break;
+        case 3:
+          if (this.currentFilter == 0) {
+            this.currentAssignNum = this.tableFilterData.length;
+            if (this.tableFilterData.length == 0) {
+              this.$Message.warning("目前所選學制人數為 0 人!");
+            }
+          } else {
+            this.$Message.warning("請先選學制!");
+            this.currentAssignNum = 0;
+          }
+          break;
+        case 4:
+          if (this.currentFilter == 1) {
+            this.currentAssignNum = this.tableFilterData.length;
+            if (this.tableFilterData.length == 0) {
+              this.$Message.warning("目前所選學級人數為 0 人!");
+            }
+          } else {
+            this.$Message.warning("請先選學級!");
+            this.currentAssignNum = 0;
+          }
+          break;
+        case 5:
+          if (this.currentFilter == 2) {
+            this.currentAssignNum = this.tableFilterData.length;
+            if (this.tableFilterData.length == 0) {
+              this.$Message.warning("目前所選班級人數為 0 人!");
+            }
+          } else {
+            this.$Message.warning("請先選班級!");
+            this.currentAssignNum = 0;
+          }
+          break;
+
+        case 6:
+          {
+            this.currentFilter = -1;
+            this.searchPeriod = "";
+            this.searchGrade = "";
+            this.searchClassroom = "";
+            if (this.students.length > this.totalAuth) {
+              this.$Message.warning("全校人數超過所購買授權數,無法使用");
+              this.currentAssignNum = 0;
+            } else {
+              this.currentAssignNum = this.students.length;
+            }
+          }
+          break;
+      }
+    },
+    cleanData: function (type) {
+      if (type == 0) {
+        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 = "";
+      }
+      if (type == 2) {
+        this.currentFilter = 2;
+        this.currentAssignNum = 0;
+        this.currentAssignType = 0;
+        this.searchPeriod = "";
+        this.searchGrade = "";
+      }
+      //只要選中選擇器,其他兩個就清空
+    },
+    setCurrentTab(page) {
+      this.currentTab = page;
+      if (this.currentTab == 0) {
+        this.clickTab = true;
+      } else this.clickTab = false;
+    },
+    close() {
+      this.clickTab = false;
+      this.currentAssignType = 0;
+      this.currentAssignNum = 0;
+      this.$emit("closeAuth", {
+        action: 0,
+      });
+    },
+    /**根据学段、年级、班级搜索 */
+    filterData: function () {
+      let data = this.students;
+
+      // 筛选没有关联班级的学生
+      if (this.searchClassroom == "noclass") {
+        if (this.searchClassroom) {
+          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;
+      }
+
+      // 帳號資訊搜尋
+      if (this.searchText) {
+        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;
+        });
+      }
+
+      // 年級篩選
+      if (this.searchGrade) {
+        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;
+        });
+      }
+
+      this.tableFilterData = data;
+      this.pointNum = this.basicCount;
+      this.tableShowData = data.slice(0, this.basicCount);
+
+      console.log("tableFilterData");
+      console.log(this.tableFilterData);
+    },
+  },
+};
+</script>
+
+<style lang="less">
+@import "./AclassOneAuth.less";
+.aclassone-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>

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

@@ -75,9 +75,11 @@
                 <Input v-model="studentInfo.id" :placeholder="$t('stuAccount.accountHolder')" @on-change="setPassword(same)">
                 <!-- <span slot="prepend">{{schoolCode}}#</span> 前方 -->
                 </Input>
+                <input type="text" style="position:fixed;z-index:-10000;width:0;border:none"/>
             </FormItem>
             <!-- 密碼 -->
             <FormItem :label="$t('stuAccount.passwordInfo')" prop="pw">
+                <input type="password" style="position:fixed;z-index:-10000;width:0;border:none"/>
                 <Input v-model="studentInfo.pw" type="password" password :placeholder="$t('stuAccount.passwordHolder')"></Input>
                 <Checkbox v-model="same" @on-change="setPassword">{{$t('stuAccount.isSame')}}</Checkbox>
             </FormItem>

+ 223 - 0
TEAMModelOS/ClientApp/src/view/student-account/AuthNumChart.vue

@@ -0,0 +1,223 @@
+<template>
+  <div id="authNum-chart"></div>
+</template>
+
+<script>
+export default {
+  name: "AuthNumChart",
+  props: ["clickTab", "dbPieNumData"],
+  data() {
+    return {
+      currentColor: "#fff",
+      e: event,
+      index: 0,
+     
+    };
+  },
+
+  mounted() {
+    console.log(this.dbPieNumData);
+    //console.log( this.clickTab)
+    setTimeout(
+      () => {
+        this.drawLine(this.dbPieNumData);
+      },
+      this.clickTab == false ? 2700 : 0 //等側邊介面就定位再繪圖,如果是切Tab則直接繪製
+    );
+  },
+ 
+  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";
+
+            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",
+
+              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,
+          },
+        ],
+      });
+
+      // 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">
+#authNum-chart {
+  position: relative;
+  height: 170px;
+  margin-top: 20px;
+  margin-bottom: 40px;
+  z-index: 2;
+  .chart-toolTip {
+    padding: 10px;
+    #color-title {
+      font-weight: bolder;
+    }
+    .title {
+      font-size: 10px;
+      color: gray;
+      font-weight: bolder;
+    }
+    .value {
+      font-weight: bolder;
+      color: white;
+    }
+  }
+}
+</style>

+ 12 - 1
TEAMModelOS/ClientApp/src/view/student-account/Index.less

@@ -7,6 +7,8 @@
 @primary-fontSize:14px;
 @second-fontSize:16px;
 
+
+
 .sc {
     &-container {
         width: 100%;
@@ -38,7 +40,16 @@
             line-height: 45px;
             height: 45px;
             width: 42%;
-
+            animation: fadeIn 1.5s ;
+            @keyframes fadeIn{
+                0%{
+                    opacity: 0;
+                }
+                100%{
+                    opacity: 1;
+                }
+            
+            }
             ul {
                 list-style: none;
                 color: white;

File diff ditekan karena terlalu besar
+ 881 - 580
TEAMModelOS/ClientApp/src/view/student-account/Index.vue


+ 2 - 0
TEAMModelOS/ClientApp/src/view/student-account/IndexIview.less

@@ -58,6 +58,8 @@
     background: none !important;
 }
 
+
+
 .sc-content .ivu-table-header {
 /*    background-color: #222222;*/
 }

+ 24 - 2
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>();
@@ -204,9 +204,29 @@ namespace TEAMModelOS.Controllers.Client
                         }
                     }
                 }
+                else //無此學校資料
+                {
+                    return BadRequest();
+                }
 
                 //該老師排定的學校課程
-                List<object> courses = new List<object>();
+                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}") }))
+                {
+                    var jsonsc = await JsonDocument.ParseAsync(item.ContentStream);
+                    if (jsonsc.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        foreach (var obj in jsonsc.RootElement.GetProperty("Documents").EnumerateArray())
+                        {
+                            string courseIdNow = obj.GetProperty("id").ToString();
+                            object subjectNow = obj.GetProperty("subject");
+                            schoolCoursesDic.Add(courseIdNow, subjectNow);
+                        }
+                    }
+                }
+
+                List<object> courses = new List<object>(); //老師被安排的課程列表
                 var query = $"SELECT c.id, c.name, c.teacher, cc.course, c.scope FROM c JOIN cc IN c.courses JOIN cct IN cc.teachers WHERE cct.id = '{id}'";
                 await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"CourseManagement-{school_code}") }))
                 {
@@ -234,7 +254,9 @@ namespace TEAMModelOS.Controllers.Client
                                 courseExtobj.name = courseNow.GetProperty("name").ToString();
                                 courseExtobj.scope = courseNow.GetProperty("scope").ToString();
                                 courseExtobj.classes = new List<object>();
+                                courseExtobj.subject = schoolCoursesDic[courseIdNow];
                                 courseExtobj.classes.Add(classExtobj);
+                                courseExtobj.subject = schoolCoursesDic[courseIdNow];
                                 courses.Add(courseExtobj);
                             }
                             else

+ 9 - 1
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -35,14 +35,16 @@ namespace TEAMModelOS.Controllers
         private readonly AzureServiceBusFactory _serviceBus;
         private readonly DingDing _dingDing;
         private readonly Option _option;
+        public readonly AzureStorageFactory _azureStorage;
 
-        public ExamController(AzureCosmosFactory azureCosmos, AzureServiceBusFactory serviceBus, SnowflakeId snowflakeId, DingDing dingDing, IOptionsSnapshot<Option> option)
+        public ExamController(AzureCosmosFactory azureCosmos, AzureServiceBusFactory serviceBus, SnowflakeId snowflakeId, DingDing dingDing, IOptionsSnapshot<Option> option, AzureStorageFactory azureStorage)
         {
             _azureCosmos = azureCosmos;
             _serviceBus = serviceBus;
             _snowflakeId = snowflakeId;
             _dingDing = dingDing;
             _option = option?.Value;
+            _azureStorage = azureStorage;
         }
 
         /// <summary>
@@ -340,6 +342,12 @@ namespace TEAMModelOS.Controllers
                     }
                     for (int i = 0; i < ans.Count; i++)
                     {
+                        if(standard[i].Count == 0 && ans[i].Count > 0)
+                        {
+                            string blob = await _azureStorage.UploadFileByContainer("hbcn", ans[i].ToJsonString(), "exam", studentId+"-"+ result.examId + "-"+ result.subjectId + ".json");
+                            result.studentAnswers[index][i].Add(blob);
+                            continue;
+                        }
                         result.studentAnswers[index][i] = ans[i];
                         //算分处理
                         if (standard[i].Count > 0)

+ 481 - 2
TEAMModelOS/Controllers/School/SchoolController.cs

@@ -14,6 +14,12 @@ using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Options;
 using System.IO;
 using System.Dynamic;
+using TEAMModelOS.SDK.Context.Configuration;
+using System.Net.Http;
+using System.Net;
+using Newtonsoft.Json;
+using System.Linq;
+using TEAMModelOS.Models.SchoolInfo;
 
 namespace TEAMModelOS.Controllers
 {
@@ -26,13 +32,15 @@ namespace TEAMModelOS.Controllers
     {
 
         public AzureCosmosFactory _azureCosmos;
+        private readonly AzureStorageFactory _azureStorage;
         private readonly DingDing _dingDing;
         private readonly Option _option;
-        public SchoolController(AzureCosmosFactory azureCosmos, DingDing dingDing, IOptionsSnapshot<Option> option)
+        public SchoolController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, DingDing dingDing, IOptionsSnapshot<Option> option)
         {
             _azureCosmos = azureCosmos;
+            _azureStorage = azureStorage;
             _dingDing = dingDing;
-            _option = option?.Value; 
+            _option = option?.Value;
         }
         /// <summary>
         /// 保存或更新学校
@@ -219,5 +227,476 @@ namespace TEAMModelOS.Controllers
             }
             return Ok(new { periods, grades, classes });
         }
+
+        /// <summary>
+        /// 取得某學校產品資料 
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("get-school-product")]
+        public async Task<IActionResult> GetSchoolProductInfo(JsonElement request)
+        {
+            if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
+
+            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<deviceBoundRich> uuidList = new List<deviceBoundRich>(); //要向CoreService詢問deviceID及硬體資訊的UUID列表
+            var response = await client.GetContainer("TEAMModelOSTemp", "School").ReadItemStreamAsync(school_code.ToString(), new PartitionKey("Product"));
+            if (response.Status == 200)
+            {
+                var json = await JsonDocument.ParseAsync(response.ContentStream);
+                //軟體
+                if (json.RootElement.TryGetProperty("serial", out JsonElement serialJobj))
+                {
+                    foreach (var serialInfo in serialJobj.EnumerateArray())
+                    {
+                        serial.Add(serialInfo.ToObject<SerialInfoBaseWithdeviceBoundExt>());
+                        serialInfo.TryGetProperty("deviceBound", out JsonElement deviceBoundJobj);
+                        foreach (var deviceBoundRow in deviceBoundJobj.EnumerateArray())
+                        {
+                            deviceBoundRich uuidExtobj = new deviceBoundRich();
+                            uuidExtobj.serial = (!string.IsNullOrWhiteSpace(Convert.ToString(serialInfo.GetProperty("serial")))) ? Convert.ToString(serialInfo.GetProperty("serial")) : null;
+                            uuidExtobj.uuid = (!string.IsNullOrWhiteSpace(Convert.ToString(deviceBoundRow.GetProperty("uuid")))) ? Convert.ToString(deviceBoundRow.GetProperty("uuid")) : null;
+                            uuidExtobj.uuid2 = (!string.IsNullOrWhiteSpace(Convert.ToString(deviceBoundRow.GetProperty("uuid2")))) ? Convert.ToString(deviceBoundRow.GetProperty("uuid2")) : null;
+                            uuidExtobj.deviceId = (!string.IsNullOrWhiteSpace(Convert.ToString(deviceBoundRow.GetProperty("deviceId")))) ? Convert.ToString(deviceBoundRow.GetProperty("deviceId")) : null;
+                            uuidExtobj.classId = (!string.IsNullOrWhiteSpace(Convert.ToString(deviceBoundRow.GetProperty("classId")))) ? Convert.ToString(deviceBoundRow.GetProperty("classId")) : null;
+                            uuidList.Add(uuidExtobj);
+                        }
+                    }
+                }
+                //取得DeviceInfo From Core [先做假的,之後再對接]
+                List<object> serialResult = new List<object>();
+                List<CoreUuid> coreUuidList = (List<CoreUuid>)GetDeviceFromCore(uuidList);
+                foreach (SerialInfoBaseWithdeviceBoundExt serialRow in serial)
+                {
+                    var deviceBoundArray = new List<deviceBoundExt>();
+                    List<CoreUuid> coreUuid = coreUuidList
+                           .Where((CoreUuid x) => x.serial == serialRow.serial)
+                           .ToList();
+                    foreach (CoreUuid deviceRow in coreUuid)
+                    {
+                        //前端顯示用
+                        deviceBoundRich deviceBoundRow = uuidList.Where(u => u.serial == deviceRow.serial && u.uuid == deviceRow.uuid && u.uuid2 == deviceRow.uuid2).FirstOrDefault();
+                        deviceBoundExt deviceBoundExt = new deviceBoundExt();
+                        deviceBoundExt.uuid = deviceBoundRow.uuid;
+                        deviceBoundExt.uuid2 = deviceBoundRow.uuid2;
+                        deviceBoundExt.classId = deviceBoundRow.classId;
+                        deviceBoundExt.deviceId = deviceRow.deviceId;
+                        deviceBoundExt.os = deviceRow.os;
+                        deviceBoundExt.deviceId = deviceRow.deviceId;
+                        deviceBoundExt.ip = deviceRow.ip;
+                        deviceBoundExt.cpu = deviceRow.cpu;
+                        deviceBoundExt.pcname = deviceRow.pcname;
+                        deviceBoundExt.ram = deviceRow.ram;
+                        deviceBoundArray.Add(deviceBoundExt);
+                        //DB更新用
+                        deviceBound serialDeviceBoundRow = serialRow.deviceBound.Where(d => d.uuid == deviceBoundRow.uuid && d.uuid2 == deviceBoundRow.uuid2).FirstOrDefault();
+                        if (serialDeviceBoundRow.deviceId != deviceRow.deviceId)
+                        {
+                            deviceBoundRich deviceBoundUpdRow = new deviceBoundRich();
+                            deviceBoundUpdRow = deviceBoundRow;
+                            deviceBoundUpdRow.deviceId = deviceRow.deviceId;
+                            serialDeviceUpdList.Add(deviceBoundUpdRow);
+                        }
+                    }
+                    serialRow.deviceBound = deviceBoundArray;
+                }
+
+
+
+                //服務
+                if (json.RootElement.TryGetProperty("service", out JsonElement serviceJobj))
+                {
+                    //取得active的主週期、所有主週期歷史
+                    List<ServiceMainPeriod> activeMainPeriod = new List<ServiceMainPeriod>();
+                    List<ServiceMainPeriod> historyMainPeriod = new List<ServiceMainPeriod>();
+                    //ServiceMainPeriod activeMainPeriod = new ServiceMainPeriod();
+                    if (serviceJobj.TryGetProperty("mainperiod", out JsonElement mainperiodJobj))
+                    {
+                        foreach (var mainperiodRow in mainperiodJobj.EnumerateArray())
+                        {
+                            historyMainPeriod.Add(mainperiodRow.ToObject<ServiceMainPeriod>());
+                            if (mainperiodRow.GetProperty("active").GetBoolean())
+                            {
+                                activeMainPeriod.Add(mainperiodRow.ToObject<ServiceMainPeriod>());
+                            }
+                        }
+                    }
+
+                    //取得active主週期的副週期
+                    List<ServicePeriod> activePeriodOfMain = new List<ServicePeriod>();
+                    List<ServicePeriod> historyPeriodOfMain = new List<ServicePeriod>();
+                    if (serviceJobj.TryGetProperty("period", out JsonElement periodJobj))
+                    {
+                        foreach (var periodRow in periodJobj.EnumerateArray())
+                        {
+                            historyPeriodOfMain.Add(periodRow.ToObject<ServicePeriod>());
+                            foreach (ServiceMainPeriod mainPeriodRow in activeMainPeriod)
+                            {
+                                if (periodRow.GetProperty("mainPeriodId").ToString() == mainPeriodRow.mainPeriodId)
+                                {
+                                    activePeriodOfMain.Add(periodRow.ToObject<ServicePeriod>());
+                                }
+                            }
+                        }
+                    }
+                    //計算各產品時間區域
+                    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))
+                            {
+                                switch (serviceProductResultRow.prodCode)
+                                {
+                                    case "RYGVCPLY": //AClassOne買斷、週期
+                                    case "AEGMCPLY":
+                                        ServiceProductAclassoneResult serviceProductAclassoneResult = new ServiceProductAclassoneResult();
+                                        serviceProductAclassoneResult.prodCode = serviceProductResultRow.prodCode;
+                                        serviceProductAclassoneResult.noperiod = serviceProductResultRow.noperiod;
+                                        serviceProductAclassoneResult.serviceType = serviceProductResultRow.serviceType;
+                                        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.used = (aclassoneJobj.TryGetProperty("used", out JsonElement usedJobj)) ? usedJobj.GetInt32() : 0;
+                                            serviceProductAclassoneResult.less = (aclassoneJobj.TryGetProperty("less", out JsonElement lessJobj)) ? lessJobj.GetInt32() : 0;
+                                        }
+                                        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}") }))
+                                        {
+                                            var jsonts = await JsonDocument.ParseAsync(item.ContentStream);
+                                            foreach (var obj in jsonts.RootElement.GetProperty("Documents").EnumerateArray())
+                                            {
+                                                teacherBlobSize = obj.GetProperty("size").GetInt64() * 1073741824;//G換算成bytes
+                                            }
+                                        }
+                                        //欄位取得
+                                        dynamic serviceSpaceProductInfo = new ExpandoObject();
+                                        serviceSpaceProductInfo.prodCode = serviceProductResultRow.prodCode;
+                                        serviceSpaceProductInfo.noperiod = serviceProductResultRow.noperiod;
+                                        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.history = CalServiceProductOrderHistory(historyMainPeriod, historyPeriodOfMain, serviceProductRow.ToObject<ServiceProduct>()); ////購買紀錄
+                                        service.Add(serviceSpaceProductInfo);
+                                        break;
+                                    default:
+                                        service.Add(serviceProductResultRow);
+                                        break;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                //硬體
+                if (json.RootElement.TryGetProperty("hard", out JsonElement hardJobj))
+                {
+                    hard.Add(hardJobj.ToObject<object>());
+                }
+
+                //更新DB
+                if (serialDeviceUpdList.Count > 0)
+                {
+                    SchoolProduct schoolProductItem = json.ToObject<SchoolProduct>();
+                    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("TEAMModelOSTemp", "School").ReplaceItemAsync<SchoolProduct>(schoolProductItem, schoolProductItem.id, new PartitionKey("Product"));
+                }
+
+            }
+
+            return Ok(new { serial, service, hard });
+        }
+
+        /// <summary>
+        /// 取得CoreDevice資訊
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        private List<CoreUuid> GetDeviceFromCore(List<deviceBoundRich> uuidList)
+        {
+            List<CoreUuid> list = new List<CoreUuid>();
+            try
+            {
+                //string url = BaseConfigModel.Configuration["HaBookAuth:CoreId:userinfo"];
+                //HttpClient client = new HttpClient();
+                //var content = new StringContent(JsonConvert.SerializeObject(uuidList), Encoding.UTF8, "application/json");
+                //HttpResponseMessage responseMessage = await client.PostAsync(url, content);
+                //if (responseMessage.StatusCode == HttpStatusCode.OK)
+                //{
+                //    string responseBody = responseMessage.Content.ReadAsStringAsync().Result;
+                //    return Ok(responseBody);
+                //}
+                //else
+                //{
+                //    return BadRequest();
+                //}
+
+                CoreUuid CoreUuid1 = new CoreUuid();
+                CoreUuid1.serial = "3222IAVN-H4RS-VF5L-YSCJ-ZXYW";
+                CoreUuid1.uuid = "3C970E5CD649";
+                CoreUuid1.uuid2 = "E006E6CE1D28";
+                CoreUuid1.deviceId = "2d776529-ac7b-4197-b981-3ae3370fe686";
+                CoreUuid1.ip = "171.216.152.131";
+                CoreUuid1.pcname = "ONLY-PC";
+                CoreUuid1.os = "Microsoft Windows NT 6.1.7601 Service Pack 1";
+                CoreUuid1.cpu = "Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz";
+                CoreUuid1.ram = 3287;
+                list.Add(CoreUuid1);
+                CoreUuid CoreUuid2 = new CoreUuid();
+                CoreUuid2.serial = "3222IAVN-8MKL-9N6F-C14T-QK39";
+                CoreUuid2.uuid = "000C2909298F";
+                CoreUuid2.uuid2 = null;
+                CoreUuid2.deviceId = "a5b21a8d-b204-40f4-b28b-c7df34cda0a9";
+                CoreUuid2.ip = "182.148.142.235";
+                CoreUuid2.pcname = null;
+                CoreUuid2.os = "Microsoft Windows NT 6.1.7601 Service Pack 1";
+                CoreUuid2.cpu = "Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz";
+                CoreUuid2.ram = 6143;
+                list.Add(CoreUuid2);
+                CoreUuid CoreUuid3 = new CoreUuid();
+                CoreUuid3.serial = "12DLT43F-BTXK-W65T-9P6J-DVAR";
+                CoreUuid3.uuid = null;
+                CoreUuid3.uuid2 = "A483E742B0FF";
+                CoreUuid3.deviceId = "8acb91e0-b5c7-4224-8e3e-5e165b80093b";
+                CoreUuid3.ip = "110.185.29.13";
+                CoreUuid3.pcname = "DESKTOP-D4OMD85";
+                CoreUuid3.os = "Microsoft Windows NT 10.0.18362.0";
+                CoreUuid3.cpu = "Intel(R) Core(TM) i5-8257U CPU @ 1.40GHz";
+                CoreUuid3.ram = 8036;
+                list.Add(CoreUuid3);
+                CoreUuid CoreUuid4 = new CoreUuid();
+                CoreUuid4.serial = "12DLT43F-TOGN-OGVR-MYVR-6PSA";
+                CoreUuid4.uuid = "94C691231A5F";
+                CoreUuid4.uuid2 = null;
+                CoreUuid4.deviceId = "a4c0bf19-db01-4f3c-8d1e-f3074476349f";
+                CoreUuid4.ip = "118.114.15.247";
+                CoreUuid4.pcname = "PC-201909100955";
+                CoreUuid4.os = "Microsoft Windows NT 10.0.14393.0";
+                CoreUuid4.cpu = "Intel(R) Core(TM) i5-7400 CPU @ 3.00GHz";
+                CoreUuid4.ram = 16291;
+                list.Add(CoreUuid4);
+                CoreUuid CoreUuid5 = new CoreUuid();
+                CoreUuid5.serial = "12DLT43F-TOGN-OGVR-MYVR-6PSA";
+                CoreUuid5.uuid = "D8CB8A780144";
+                CoreUuid5.uuid2 = null;
+                CoreUuid5.deviceId = "699c8847-f9cd-4f8d-9799-2f5555e46e34";
+                CoreUuid5.ip = "183.220.92.210";
+                CoreUuid5.pcname = "USER-20180716GZ";
+                CoreUuid5.os = "Microsoft Windows NT 10.0.17134.0";
+                CoreUuid5.cpu = "Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz";
+                CoreUuid5.ram = 16287;
+                list.Add(CoreUuid5);
+                CoreUuid CoreUuid6 = new CoreUuid();
+                CoreUuid6.serial = "12DLT43F-TOGN-OGVR-MYVR-6PSA";
+                CoreUuid6.uuid = null;
+                CoreUuid6.uuid2 = "C49DED0BB2A9";
+                CoreUuid6.deviceId = "44af0c54-7927-4a26-8503-9dcb62272e95";
+                CoreUuid6.ip = "118.114.15.247";
+                CoreUuid6.pcname = "DESKTOP-S4JOQCB";
+                CoreUuid6.os = "Microsoft Windows NT 10.0.18362.0";
+                CoreUuid6.cpu = "Intel(R) Core(TM) i5-7300U CPU @ 2.60GHz";
+                CoreUuid6.ram = 4021;
+                list.Add(CoreUuid6);
+
+                return list;
+            }
+            catch (Exception ex)
+            {
+                return list;
+            }
+        }
+
+        /// <summary>
+        /// 計算各產品的初始結束時間及可用數
+        /// </summary>
+        /// <param name="mainPeriodList">active的主周期</param>
+        /// <param name="periodList">附屬周期</param>
+        /// <param name="serviceProduct">產品資訊</param>
+        /// <returns></returns>
+        private ServiceProductResult CalServiceProductAuth(List<ServiceMainPeriod> mainPeriodList, List<ServicePeriod> periodList, ServiceProduct serviceProduct)
+        {
+            periodZone periodZoneResult = new periodZone();
+            List<periodZone> periodZone = new List<periodZone>();
+            //篩選出產品有的副週期
+            List<string> existPeriodIdList = new List<string>();
+            if (serviceProduct.auth.Count > 0)
+            {
+                foreach (ServiceProductAuth serviceProductAuthRow in serviceProduct.auth)
+                {
+                    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) {
+
+                //篩選出主週期(複數active主週期對策) ※最終篩出唯一一組 篩選原則:(1)由此產品有的副週期ID篩出 (2)若還有複數筆,則期間長者勝出
+                List<string> existMainPeriodIdList = new List<string>();
+                foreach (ServicePeriod periodRow in periodOrder)
+                {
+                    if (!existMainPeriodIdList.Contains(periodRow.mainPeriodId))
+                    {
+                        existMainPeriodIdList.Add(periodRow.mainPeriodId);
+                    }
+                }
+                ServiceMainPeriod mainPeriod = mainPeriodList.Where(w => existMainPeriodIdList.Contains(w.mainPeriodId)).OrderByDescending(w => w.endDate - w.startDate).First();
+                List<ServicePeriod> periods = periodOrder.Where(p => p.mainPeriodId == mainPeriod.mainPeriodId).ToList();
+
+                //combine period,compare to mainPeriod start, end
+                periodZone = combinePeriodZone(periods);
+                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))
+            {
+                serviceProductResult.prodCode = serviceProduct.prodCode;
+                serviceProductResult.noperiod = serviceProduct.noperiod;
+                serviceProductResult.serviceType = serviceProduct.serviceType;
+                serviceProductResult.startDate = periodZoneResult.startDate;
+                serviceProductResult.endDate = periodZoneResult.endDate;
+                serviceProductResult.avaliable = serviceProduct.avaliable;
+            }
+
+            return serviceProductResult;
+        }
+
+        /// <summary>
+        /// 計算各產品的購買歷史紀錄
+        /// </summary>
+        /// <param name="mainPeriodList">所有的主周期</param>
+        /// <param name="periodList">所有附屬周期</param>
+        /// <param name="serviceProduct">產品資訊</param>
+        /// <returns></returns>
+        private List<ServiceProductAuthHistoryStartEnd> CalServiceProductOrderHistory(List<ServiceMainPeriod> mainPeriodList, List<ServicePeriod> periodList, ServiceProduct serviceProduct)
+        {
+            List<ServiceProductAuthHistoryStartEnd> serviceProductOrderStartEndList = new List<ServiceProductAuthHistoryStartEnd>();
+
+            //GROUP BY orderId
+            if (serviceProduct.auth.Count > 0)
+            {
+                List<ServiceProductAuthHistory> serviceProductOrderList = new List<ServiceProductAuthHistory>();
+                foreach (ServiceProductAuth serviceProductAuthRow in serviceProduct.auth)
+                {
+                    ServiceProductAuthHistory existServiceProductAuthHistory = serviceProductOrderList.Where(a => a.orderId == serviceProductAuthRow.orderId).FirstOrDefault();
+                    if(existServiceProductAuthHistory != null)
+                    {
+                        if(!existServiceProductAuthHistory.periodIdList.Contains(serviceProductAuthRow.periodId))
+                        {
+                            existServiceProductAuthHistory.periodIdList.Add(serviceProductAuthRow.periodId);
+                        }
+                    }
+                    else
+                    {
+                        ServiceProductAuthHistory serviceProductAuthHistoryRow = new ServiceProductAuthHistory();
+                        serviceProductAuthHistoryRow.orderId = serviceProductAuthRow.orderId;
+                        int y = Int32.Parse(serviceProductAuthRow.orderId.Substring(0, 4));
+                        int m = Int32.Parse(serviceProductAuthRow.orderId.Substring(4, 2));
+                        int d = Int32.Parse(serviceProductAuthRow.orderId.Substring(6, 2));
+                        serviceProductAuthHistoryRow.orderDate = new DateTimeOffset(y, m, d, 0, 0, 0, TimeSpan.Zero).ToUnixTimeSeconds();
+                        serviceProductAuthHistoryRow.number = serviceProductAuthRow.number;
+                        serviceProductAuthHistoryRow.unit = serviceProductAuthRow.unit;
+                        serviceProductAuthHistoryRow.periodIdList = new List<string>();
+                        serviceProductAuthHistoryRow.periodIdList.Add(serviceProductAuthRow.periodId);
+                        serviceProductOrderList.Add(serviceProductAuthHistoryRow);
+                    }
+                }
+                
+                foreach (ServiceProductAuthHistory serviceProductOrderRow in serviceProductOrderList)
+                {
+                    ServiceProductAuthHistoryStartEnd ServiceProductAuthHistoryStartEndRow = new ServiceProductAuthHistoryStartEnd();
+                    //combine period
+                    List<ServicePeriod> periodOrder = periodList.Where((ServicePeriod x) => serviceProductOrderRow.periodIdList.Contains(x.periodId)).OrderBy(p => Int32.Parse(p.periodId)).ToList();
+                    List<periodZone> periodZone = combinePeriodZone(periodOrder);
+                    ServiceProductAuthHistoryStartEndRow.startDate = periodZone[0].startDate;
+                    ServiceProductAuthHistoryStartEndRow.endDate = periodZone[0].endDate;
+                    ServiceProductAuthHistoryStartEndRow.orderId = serviceProductOrderRow.orderId;
+                    ServiceProductAuthHistoryStartEndRow.orderDate = serviceProductOrderRow.orderDate;
+                    ServiceProductAuthHistoryStartEndRow.number = serviceProductOrderRow.number;
+                    ServiceProductAuthHistoryStartEndRow.unit = serviceProductOrderRow.unit;
+                    serviceProductOrderStartEndList.Add(ServiceProductAuthHistoryStartEndRow);
+                }
+            }
+            //return
+            return serviceProductOrderStartEndList;
+        }
+
+        //子週期合併開始結束時間
+        private List<periodZone> combinePeriodZone(List<ServicePeriod> periods)
+        {
+            List<periodZone> periodZone = new List<periodZone>();
+            foreach (ServicePeriod periodRow in periods)
+            {
+                if (periodZone.Count == 0)
+                {
+                    periodZone periodZoneNew = new periodZone();
+                    periodZoneNew.startDate = periodRow.startDate;
+                    periodZoneNew.endDate = periodRow.endDate;
+                    periodZone.Add(periodZoneNew);
+                }
+                else
+                {
+                    bool zoneExistFlg = false;
+                    for (int i = 0; i < periodZone.Count; i++) //考慮到副週期可能斷成數個block,整合後取第一個
+                    {
+                        if (periodRow.startDate <= periodZone[i].endDate + 10 && periodZone[i].endDate + 10 < periodRow.endDate)
+                        {
+                            periodZone[i].endDate = periodRow.endDate;
+                            zoneExistFlg = true;
+                        }
+                        else if (periodZone[i].startDate <= periodRow.endDate + 10 && periodRow.endDate + 10 < periodZone[i].endDate)
+                        {
+                            periodZone[i].startDate = periodRow.startDate;
+                            zoneExistFlg = true;
+                        }
+                    }
+                    if (!zoneExistFlg)
+                    {
+                        periodZone periodZoneNew = new periodZone();
+                        periodZoneNew.startDate = periodRow.startDate;
+                        periodZoneNew.endDate = periodRow.endDate;
+                        periodZone.Add(periodZoneNew);
+                    }
+                }
+            }
+            return periodZone;
+        }
     }
 }

+ 248 - 126
TEAMModelOS/Controllers/School/StudentController.cs

@@ -344,7 +344,7 @@ namespace TEAMModelOS.Controllers
                 using var memoryStream = new MemoryStream();
                 using var writer = new Utf8JsonWriter(memoryStream);
                 writer.WriteStartObject();
-                writer.WriteString("pk", "Class");
+                writer.WriteString("pk", "Classroom");
                 writer.WriteString("code", $"Class-{schoolId}");
                 writer.WriteString("id", classId);
                 writer.WriteNull("x");
@@ -362,6 +362,8 @@ namespace TEAMModelOS.Controllers
                     else writer.WriteString("name", name);
                     if (string.IsNullOrWhiteSpace(no)) writer.WriteNull("no");
                     else writer.WriteString("no", no);
+                    writer.WriteNull("groupId");
+                    writer.WriteNull("groupName");
                     writer.WriteEndObject();
                     ids.Add(id);
                 }
@@ -425,7 +427,7 @@ namespace TEAMModelOS.Controllers
             {   //TODO : 進階查詢選項調整 
                 //以學校學生角度去抓資料
                 List<(string id, string name, string pic, string year)> listStudent = new List<(string id, string name, string pic, string year)>();
-                string queryText = $"SELECT c.id, c.name, c.picture, c.year FROM c WHERE c.pk = 'Base'";
+                string queryText = $"SELECT c.id, c.name, c.picture, c.year FROM c WHERE c.code = 'Base-{schoolId}'";
 
                 //如果有選擇ClassId的話,則先取得該教室內的學生。
                 List<string> searchId = new List<string>();
@@ -567,7 +569,7 @@ namespace TEAMModelOS.Controllers
             {   //TODO : 進階查詢選項調整 
                 //以學校學生角度去抓資料
                 List<(string id, string name, string pic, string year)> listStudent = new List<(string id, string name, string pic, string year)>();
-                string queryText = $"SELECT c.id, c.name, c.picture, c.year FROM c WHERE c.pk = 'Base'";
+                string queryText = $"SELECT c.id, c.name, c.picture, c.year FROM c WHERE c.code = 'Base-{schoolId}'";
 
                 //回傳用ContinuationToken
                 string continuationToken = string.Empty;
@@ -736,8 +738,8 @@ namespace TEAMModelOS.Controllers
         {
             try
             {
-                string queryText = $"SELECT c.id, c.students FROM c WHERE c.pk = 'Class'";
-                if (!string.IsNullOrWhiteSpace(classId)) queryText += $" AND c.id = '{classId}'";
+                string queryText = $"SELECT c.id, c.students FROM c WHERE c = c";
+                //if (!string.IsNullOrWhiteSpace(classId)) queryText += $" AND c.id = '{classId}'";
                 Dictionary<string, JsonElement> listStudent = new Dictionary<string, JsonElement>();
 
                 await foreach (Response item in _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School")
@@ -942,18 +944,22 @@ namespace TEAMModelOS.Controllers
 
                 foreach (var importItem in classStudents)
                 {
+                    List<(string id, string name, string no, string groupId, string groupName)> retStudents
+                        = new List<(string id, string name, string no, string groupId, string groupName)>();
+                    retStudents.AddRange(importItem.Value.Select(o => (o.id, o.name, o.no, (string)null, (string)null)).ToList());
+
                     //檢查輸入資料的id及no是否有重複
-                    var duplicateId = importItem.Value.GroupBy(o => o.id).Where(o => o.Count() > 1).Select(o => o.Key).ToList();
+                    var duplicateId = retStudents.GroupBy(o => o.id).Where(o => o.Count() > 1).Select(o => o.Key).ToList();
                     foreach (var id in duplicateId)
                     {
-                        importItem.Value.RemoveAll(o => o.id.Equals(id));
+                        retStudents.RemoveAll(o => o.id.Equals(id));
                         existId.Add(id);
                     }
 
-                    var duplicateNo = importItem.Value.GroupBy(o => o.no).Where(o => o.Count() > 1).Select(o => o.Key).ToList();
+                    var duplicateNo = retStudents.GroupBy(o => o.no).Where(o => o.Count() > 1).Select(o => o.Key).ToList();
                     foreach (var no in duplicateNo)
                     {
-                        importItem.Value.RemoveAll(o => o.no.Equals(no));
+                        retStudents.RemoveAll(o => o.no.Equals(no));
                         existNo.Add(no);
                     }
 
@@ -989,7 +995,8 @@ namespace TEAMModelOS.Controllers
                             if (existStudents.GetArrayLength() != 0)
                             {
                                 //若學生數量不為0,代表裡面已有資料,故要檢查是否有重複
-                                List<(string id, string name, string no)> tmpStuds = new List<(string id, string name, string no)>();
+                                List<(string id, string name, string no, string groupId, string groupName)> tmpStuds
+                                    = new List<(string id, string name, string no, string groupId, string groupName)>();
                                 //取得雲端教室的名單,並檢查id及座號是否重複
                                 var studs = existStudents.EnumerateArray();
 
@@ -1000,8 +1007,10 @@ namespace TEAMModelOS.Controllers
                                     string id = stud.GetProperty("id").GetString();
                                     string no = stud.GetProperty("no").GetString();
                                     string name = stud.GetProperty("name").GetString();
+                                    string groupId = stud.GetProperty("groupId").GetString();
+                                    string groupName = stud.GetProperty("groupName").GetString();
 
-                                    tmpStuds.Add((id, name, no));
+                                    tmpStuds.Add((id, name, no, groupId, groupName));
                                 }
 
                                 //先檢查ID及座號是否重複,重複的則記錄在duplicate內
@@ -1012,27 +1021,26 @@ namespace TEAMModelOS.Controllers
                                 //var duplicate = importItem.Value.Where(
                                 //    a => tmpStuds.Exists(t => a.no.Contains(t.no))
                                 //    || tmpStuds.Exists(t => a.id.Contains(t.id))).ToList();
-                                var duplicate = importItem.Value.Where(
+                                var duplicate = retStudents.Where(
                                     a => tmpStuds.Any(p => (p.id == a.id) || (p.no == a.no))).Select(o => o);
 
-                                foreach (var (id, name, no) in duplicate)
+                                foreach (var item in duplicate)
                                 {
-                                    importItem.Value.RemoveAll(o => o.id.Equals(id) || o.no.Equals(no));
+                                    retStudents.RemoveAll(o => o.id.Equals(item.id) || o.no.Equals(item.no));
                                 }
 
                                 //將現有資料加回去
-                                importItem.Value.AddRange(tmpStuds);
+                                retStudents.AddRange(tmpStuds);
                             }
 
                             foreach (var testDataElement in existClassData.RootElement.EnumerateObject())
                             {
                                 if (!testDataElement.Name.Equals("students", StringComparison.Ordinal)) testDataElement.WriteTo(writer);
                             }
-
                         }
                         else if (response.Status == 404)
                         {
-                            writer.WriteString("pk", "Class");
+                            writer.WriteString("pk", "Classroom");
                             writer.WriteString("code", $"Class-{schoolId}");
                             writer.WriteString("id", importItem.Key);
                             writer.WriteNull("x");
@@ -1058,12 +1066,20 @@ namespace TEAMModelOS.Controllers
                         writer.WritePropertyName("students");
                         writer.WriteStartArray();
 
-                        foreach (var (id, name, no) in importItem.Value)
+                        //進行no排序
+                        retStudents = retStudents.OrderBy(o => string.IsNullOrWhiteSpace(o.no)).ThenBy(o => string.IsNullOrEmpty(o.no) ? 0 : int.Parse(o.no)).ToList();
+                        foreach (var item in retStudents)
                         {
                             writer.WriteStartObject();
-                            writer.WriteString("id", id);
-                            writer.WriteString("name", name);
-                            writer.WriteString("no", no);
+                            writer.WriteString("id", item.id);
+                            if (string.IsNullOrWhiteSpace(item.name)) writer.WriteNull("name");
+                            else writer.WriteString("name", item.name);
+                            if (string.IsNullOrWhiteSpace(item.no)) writer.WriteNull("no");
+                            else writer.WriteString("no", item.no);
+                            if (string.IsNullOrWhiteSpace(item.groupId)) writer.WriteNull("groupId");
+                            else writer.WriteString("groupId", item.groupId);
+                            if (string.IsNullOrWhiteSpace(item.groupName)) writer.WriteNull("groupName");
+                            else writer.WriteString("groupName", item.groupName);
                             writer.WriteEndObject();
                         }
 
@@ -1077,7 +1093,7 @@ namespace TEAMModelOS.Controllers
                                         .GetContainer("TEAMModelOS", "School")
                                         .UpsertItemStreamAsync(memoryStream, new PartitionKey($"Class-{schoolId}"));
 
-                        retClassStud.Add(classId, (periodId, gradeId, importItem.Value.Select(o => o.id).ToList()));
+                        retClassStud.Add(classId, (periodId, gradeId, retStudents.Select(o => o.id).ToList()));
                     }
                 }
                 return (retClassStud, existId, existNo);
@@ -1103,7 +1119,7 @@ namespace TEAMModelOS.Controllers
             {
                 if (!(classIds == null || classIds.Count == 0))
                 {
-                    string queryText = $"SELECT * FROM c WHERE c.pk = 'Class' AND c.id IN ({string.Join(",", classIds.Select(o => $"'{o}'"))})";
+                    string queryText = $"SELECT * FROM c WHERE c.code = 'Class-{schoolId}' AND c.id IN ({string.Join(",", classIds.Select(o => $"'{o}'"))})";
 
                     Dictionary<string, JsonElement> dicClassInfo = new Dictionary<string, JsonElement>();
 
@@ -1215,7 +1231,7 @@ namespace TEAMModelOS.Controllers
                 {
                     CosmosContainer cosmosContainer = _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Student");
                     //查學生的基本資料
-                    string queryText = $"SELECT * FROM c WHERE c.pk = 'Base' AND c.id IN ({string.Join(",", studentsInfos.Select(o => $"'{o.Key}'"))})";
+                    string queryText = $"SELECT * FROM c WHERE c.id IN ({string.Join(",", studentsInfos.Select(o => $"'{o.Key}'"))})";
 
                     List<JsonElement> listStudent = new List<JsonElement>();
 
@@ -1243,9 +1259,6 @@ namespace TEAMModelOS.Controllers
                                 {
                                     switch (true)
                                     {
-                                        //case bool _ when element.Name.Equals("name", StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(studentsInfos[id].name):
-                                        //    writer.WriteString("name", studentsInfos[id].name);
-                                        //    break;
                                         case bool _ when element.Name.Equals("name", StringComparison.Ordinal):
                                             if (string.IsNullOrWhiteSpace(studentsInfos[id].name))
                                             {
@@ -1266,9 +1279,6 @@ namespace TEAMModelOS.Controllers
                                                 upPwDone = true;
                                             }
                                             break;
-                                        //case bool _ when element.Name.Equals("gender", StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(studentsInfos[id].gender):
-                                        //    writer.WriteString("gender", studentsInfos[id].gender);
-                                        //    break;
                                         case bool _ when element.Name.Equals("gender", StringComparison.Ordinal):
                                             if (string.IsNullOrWhiteSpace(studentsInfos[id].gender))
                                             {
@@ -1280,9 +1290,6 @@ namespace TEAMModelOS.Controllers
                                                 writer.WriteString("gender", studentsInfos[id].gender);
                                             }
                                             break;
-                                        //case bool _ when element.Name.Equals("year", StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(studentsInfos[id].year):
-                                        //    writer.WriteString("year", studentsInfos[id].year);
-                                        //    break;
                                         case bool _ when element.Name.Equals("year", StringComparison.Ordinal):
                                             if (string.IsNullOrWhiteSpace(studentsInfos[id].year))
                                             {
@@ -1363,127 +1370,196 @@ namespace TEAMModelOS.Controllers
         {
             try
             {
-                //輸出用資料格式
+                //最後輸出給前端資料
                 var retStudentsClassInfo = new Dictionary<string, (string classId, string periodId, string gradeId, string name, string no)>();
+                /**
+                整理輸入的資料
+                var studentsClassInfo = new Dictionary<string, (string classId, string name, string no)>();
+                while (students.MoveNext())
+                {
+                    JsonElement student = students.Current;
+                    if (student.TryGetProperty("id", out var tmpId))
+                    {
+                        if (!string.IsNullOrWhiteSpace(tmpId.GetString()))
+                        {
+                            string classId = string.Empty, className = string.Empty, no = string.Empty, name = string.Empty;
 
-                //整理輸入的資料
-                //var studentsClassInfo = new Dictionary<string, (string classId, string name, string no)>();
-                //while (students.MoveNext())
-                //{
-                //    JsonElement student = students.Current;
-                //    if (student.TryGetProperty("id", out var tmpId))
-                //    {
-                //        if (!string.IsNullOrWhiteSpace(tmpId.GetString()))
-                //        {
-                //            string classId = string.Empty, className = string.Empty, no = string.Empty, name = string.Empty;
-
-                //            if (student.TryGetProperty("name", out var tmpName)) name = tmpName.GetString();
-                //            if (student.TryGetProperty("classId", out var tmpClassId)) classId = tmpClassId.GetString();
-                //            if (student.TryGetProperty("no", out var tmpNo)) no = tmpNo.GetString();
-
-                //            studentsClassInfo.Add(tmpId.GetString(), (classId, name, no));
-                //        }
-                //    }
-                //}
+                            if (student.TryGetProperty("name", out var tmpName)) name = tmpName.GetString();
+                            if (student.TryGetProperty("classId", out var tmpClassId)) classId = tmpClassId.GetString();
+                            if (student.TryGetProperty("no", out var tmpNo)) no = tmpNo.GetString();
+
+                            studentsClassInfo.Add(tmpId.GetString(), (classId, name, no));
+                        }
+                    }
+                }
+                */
                 if (students.Count == 0) return retStudentsClassInfo;
 
+                var tmpStuds = new Dictionary<string, (string name, string year, string pic, string gender, string mail, string mobile, string classId, string no)>(students);
+
                 //透過id查找已加入的教室
-                var classInfo = await getClassInfoUseStudent(schoolId, students.Select(o => o.Key).ToList());
+                var classInfos = await getClassInfoUseStudent(schoolId, students.Select(o => o.Key).ToList());
 
                 //如果有查到,代表該學生已經加入過某間教室(Class)
-                if (classInfo.Count != 0)
+                if (classInfos.Count != 0)
                 {
-                    foreach (var item in classInfo)
+                    foreach (var classInfo in classInfos)
                     {
                         //教室資訊的id
-                        if (item.TryGetProperty("id", out var tmpClassId))
+                        if (classInfo.TryGetProperty("id", out var tmpClassId))
                         {
                             string classId = tmpClassId.GetString();
                             string periodId = string.Empty, gradeId = string.Empty;
 
-                            if (item.TryGetProperty("periodId", out var tmpPeriodId)) periodId = tmpPeriodId.GetString();
-                            if (item.TryGetProperty("gradeId", out var tmpGradeId)) gradeId = tmpGradeId.GetString();
-
-                            using var memoryStream = new MemoryStream();
-                            using var writer = new Utf8JsonWriter(memoryStream);
-                            writer.WriteStartObject();
-                            foreach (var element in item.EnumerateObject())
-                            {
-                                if (!(element.Name.Equals("students", StringComparison.Ordinal) || element.Name.StartsWith("_")))
-                                {
-                                    element.WriteTo(writer);
-                                }
-                            }
-                            writer.WritePropertyName("students");
-                            writer.WriteStartArray();
-
-                            //查詢學生欄位
-                            var Documents = item.GetProperty("students").EnumerateArray();
+                            if (classInfo.TryGetProperty("periodId", out var tmpPeriodId)) periodId = tmpPeriodId.GetString();
+                            if (classInfo.TryGetProperty("gradeId", out var tmpGradeId)) gradeId = tmpGradeId.GetString();
+                            
+                            //用以紀錄教室內已存在的學生
+                            List<(string id, string name, string no, string groupId, string groupName)> existStudents 
+                                = new List<(string id, string name, string no, string groupId, string groupName)>();
+                            var Documents = classInfo.GetProperty("students").EnumerateArray();
                             while (Documents.MoveNext())
                             {
                                 JsonElement Document = Documents.Current;
-                                string studId = string.Empty, no = string.Empty, name = string.Empty;
+                                string studId = null, no = null, name = null, groupId = null, groupName = null;
 
-                                if (Document.TryGetProperty("name", out var tmpName)) name = tmpName.GetString();
                                 if (Document.TryGetProperty("id", out var tmpId)) studId = tmpId.GetString();
+                                if (Document.TryGetProperty("name", out var tmpName)) name = tmpName.GetString();
                                 if (Document.TryGetProperty("no", out var tmpNo)) no = tmpNo.GetString();
+                                if (Document.TryGetProperty("groupId", out var tmpgroupId)) groupId = tmpgroupId.GetString();
+                                if (Document.TryGetProperty("groupName", out var tmpgroupName)) groupName = tmpgroupName.GetString();
 
                                 //檢查輸入資料內學生要加入的教室是否一致
                                 if (students.ContainsKey(studId))
                                 {
-                                    //如果是相同的教室id
                                     if (students[studId].classId.Equals(classId, StringComparison.Ordinal))
                                     {
-                                        retStudentsClassInfo.Add(studId, (classId, periodId, gradeId, name, no));
-
-                                        //座號及姓名檢查,如果不相同則進行更新
+                                        //座號及姓名檢查,如果現有資料與欲更新資料不同則進行更新
                                         if (
-                                            students[studId].no.Equals(no, StringComparison.Ordinal)
-                                            && students[studId].name.Equals(name, StringComparison.Ordinal)
+                                            !(students[studId].no.Equals(no, StringComparison.Ordinal)
+                                            && students[studId].name.Equals(name, StringComparison.Ordinal))
                                             )
                                         {
-                                            writer.WriteStartObject();
-                                            writer.WriteString("id", studId);
-                                            if (string.IsNullOrWhiteSpace(name)) writer.WriteNull("name");
-                                            else writer.WriteString("name", name);
-                                            if (string.IsNullOrWhiteSpace(no)) writer.WriteNull("no");
-                                            else writer.WriteString("no", no);
-                                            writer.WriteEndObject();
+                                            name = students[studId].name;
+                                            no = students[studId].no;
+                                            //retStudentsClassInfo.Add(studId, (classId, periodId, gradeId, name, no));
                                         }
-                                        else
-                                        {
-                                            writer.WriteStartObject();
-                                            writer.WriteString("id", studId);
-                                            if (string.IsNullOrWhiteSpace(students[studId].name)) writer.WriteNull("name");
-                                            else writer.WriteString("name", students[studId].name);
-                                            if (string.IsNullOrWhiteSpace(students[studId].no)) writer.WriteNull("no");
-                                            else writer.WriteString("no", students[studId].no);
-                                            writer.WriteEndObject();
-                                            //更新輸出結果的資料
-                                            retStudentsClassInfo[studId] = (classId, periodId, gradeId, students[studId].name, students[studId].no);
-                                        }
-                                        //將已處理好的學生從字典裡移除
-                                        students.Remove(studId);
+                                        tmpStuds.Remove(studId);
+                                    }
+                                    else
+                                    {
+                                        //retStudentsClassInfo.Add(studId, (students[studId].classId, null, null, name, no));
+                                        tmpStuds[studId] = (tmpStuds[studId].name, tmpStuds[studId].year, tmpStuds[studId].pic, tmpStuds[studId].gender, tmpStuds[studId].mail, tmpStuds[studId].mobile, tmpStuds[studId].classId, no);
+                                        continue;
                                     }
-                                    //不是相同教室id,則要移除該學生的資訊,不寫入
-                                    else continue;
                                 }
-                                //寫入原本已存在的學生資訊
-                                else
+                                retStudentsClassInfo.Add(studId, (classId, periodId, gradeId, name, no));
+                                existStudents.Add((studId, name, no, groupId, groupName));
+                            }
+                            existStudents = existStudents.OrderBy(o => string.IsNullOrWhiteSpace(o.no)).ThenBy(o => string.IsNullOrEmpty(o.no) ? 0 : int.Parse(o.no)).ToList();
+                            
+                            using var memoryStream = new MemoryStream();
+                            using var writer = new Utf8JsonWriter(memoryStream);
+
+                            writer.WriteStartObject();
+                            //將非students的欄位資料寫回去
+                            foreach (var element in classInfo.EnumerateObject())
+                            {
+                                if (!(element.Name.Equals("students", StringComparison.Ordinal) || element.Name.StartsWith("_")))
                                 {
-                                    writer.WriteStartObject();
-                                    writer.WriteString("id", studId);
-                                    if (string.IsNullOrWhiteSpace(name)) writer.WriteNull("name");
-                                    else writer.WriteString("name", name);
-                                    if (string.IsNullOrWhiteSpace(no)) writer.WriteNull("no");
-                                    else writer.WriteString("no", no);
-                                    writer.WriteEndObject();
+                                    element.WriteTo(writer);
                                 }
                             }
+
+                            writer.WritePropertyName("students");
+                            writer.WriteStartArray();
+
+                            foreach (var item in existStudents)
+                            {
+                                writer.WriteStartObject();
+                                writer.WriteString("id", item.id);
+                                if (string.IsNullOrWhiteSpace(item.name)) writer.WriteNull("name");
+                                else writer.WriteString("name", item.name);
+                                if (string.IsNullOrWhiteSpace(item.no)) writer.WriteNull("no");
+                                else writer.WriteString("no", item.no);
+                                if (string.IsNullOrWhiteSpace(item.groupId)) writer.WriteNull("groupId");
+                                else writer.WriteString("groupId", item.groupId);
+                                if (string.IsNullOrWhiteSpace(item.groupName)) writer.WriteNull("groupName");
+                                else writer.WriteString("groupName", item.groupName);
+                                writer.WriteEndObject();
+                            }
+
                             writer.WriteEndArray();
                             writer.WriteEndObject();
                             writer.Flush();
 
+                            /**
+                            //while (Documents.MoveNext())
+                            //{
+                            //    JsonElement Document = Documents.Current;
+                            //    string studId = string.Empty, no = string.Empty, name = string.Empty;
+
+                            //    if (Document.TryGetProperty("name", out var tmpName)) name = tmpName.GetString();
+                            //    if (Document.TryGetProperty("id", out var tmpId)) studId = tmpId.GetString();
+                            //    if (Document.TryGetProperty("no", out var tmpNo)) no = tmpNo.GetString();
+
+                            //    //檢查輸入資料內學生要加入的教室是否一致
+                            //    if (students.ContainsKey(studId))
+                            //    {
+                            //        //如果是相同的教室id
+                            //        if (students[studId].classId.Equals(classId, StringComparison.Ordinal))
+                            //        {
+                            //            retStudentsClassInfo.Add(studId, (classId, periodId, gradeId, name, no));
+
+                            //            //座號及姓名檢查,如果不相同則進行更新
+                            //            if (
+                            //                students[studId].no.Equals(no, StringComparison.Ordinal)
+                            //                && students[studId].name.Equals(name, StringComparison.Ordinal)
+                            //                )
+                            //            {
+                            //                writer.WriteStartObject();
+                            //                writer.WriteString("id", studId);
+                            //                if (string.IsNullOrWhiteSpace(name)) writer.WriteNull("name");
+                            //                else writer.WriteString("name", name);
+                            //                if (string.IsNullOrWhiteSpace(no)) writer.WriteNull("no");
+                            //                else writer.WriteString("no", no);
+                            //                writer.WriteEndObject();
+                            //            }
+                            //            else
+                            //            {
+                            //                writer.WriteStartObject();
+                            //                writer.WriteString("id", studId);
+                            //                if (string.IsNullOrWhiteSpace(students[studId].name)) writer.WriteNull("name");
+                            //                else writer.WriteString("name", students[studId].name);
+                            //                if (string.IsNullOrWhiteSpace(students[studId].no)) writer.WriteNull("no");
+                            //                else writer.WriteString("no", students[studId].no);
+                            //                writer.WriteEndObject();
+                            //                //更新輸出結果的資料
+                            //                retStudentsClassInfo[studId] = (classId, periodId, gradeId, students[studId].name, students[studId].no);
+                            //            }
+                            //            //將已處理好的學生從字典裡移除
+                            //            students.Remove(studId);
+                            //        }
+                            //        //不是相同教室id,則要移除該學生的資訊,不寫入
+                            //        else continue;
+                            //    }
+                            //    //寫入原本已存在的學生資訊
+                            //    else
+                            //    {
+                            //        writer.WriteStartObject();
+                            //        writer.WriteString("id", studId);
+                            //        if (string.IsNullOrWhiteSpace(name)) writer.WriteNull("name");
+                            //        else writer.WriteString("name", name);
+                            //        if (string.IsNullOrWhiteSpace(no)) writer.WriteNull("no");
+                            //        else writer.WriteString("no", no);
+                            //        writer.WriteEndObject();
+                            //    }
+                            //}
+                            //writer.WriteEndArray();
+                            //writer.WriteEndObject();
+                            //writer.Flush();
+                            */
+
                             try
                             {
                                 var ret = await _azureCosmos
@@ -1503,26 +1579,55 @@ namespace TEAMModelOS.Controllers
                     }
                 }
                 //始終會加入新教室,除非只是單純的換座號或是姓名
-                if (classInfo.Count == 0 || students.Count != 0) //透過學生id查找教室,但沒找到任何已加入的教室
+                if (classInfos.Count == 0 || tmpStuds.Count != 0) //透過學生id查找教室,但沒找到任何已加入的教室
                 {
                     //使用classId來查詢教室資訊
-                    var classInfos = await getClassInfoUseId(schoolId, students.Select(o => o.Value.classId).ToList());
-                    if (classInfos.Count != 0)
+                    var dicClassInfo = await getClassInfoUseId(schoolId, tmpStuds.Select(o => o.Value.classId).ToList());
+                    if (dicClassInfo.Count != 0)
                     {
-                        foreach (var item in classInfos)
+                        foreach (var classInfo in dicClassInfo)
                         {
-                            string classId = item.Key;
+                            string classId = classInfo.Key;
                             string periodId = string.Empty, gradeId = string.Empty;
 
-                            if (item.Value.TryGetProperty("periodId", out var tmpPeriodId)) periodId = tmpPeriodId.GetString();
-                            if (item.Value.TryGetProperty("gradeId", out var tmpGradeId)) gradeId = tmpGradeId.GetString();
+                            if (classInfo.Value.TryGetProperty("periodId", out var tmpPeriodId)) periodId = tmpPeriodId.GetString();
+                            if (classInfo.Value.TryGetProperty("gradeId", out var tmpGradeId)) gradeId = tmpGradeId.GetString();
+
+                            //用以紀錄教室內已存在的學生
+                            List<(string id, string name, string no, string groupId, string groupName)> existStudents
+                                = new List<(string id, string name, string no, string groupId, string groupName)>();
+                            var Documents = classInfo.Value.GetProperty("students").EnumerateArray();
+                            while (Documents.MoveNext())
+                            {
+                                JsonElement Document = Documents.Current;
+                                string studId = null, no = null, name = null, groupId = null, groupName = null;
+
+                                if (Document.TryGetProperty("id", out var tmpId)) studId = tmpId.GetString();
+                                if (Document.TryGetProperty("name", out var tmpName)) name = tmpName.GetString();
+                                if (Document.TryGetProperty("no", out var tmpNo)) no = tmpNo.GetString();
+                                if (Document.TryGetProperty("groupId", out var tmpgroupId)) groupId = tmpgroupId.GetString();
+                                if (Document.TryGetProperty("groupName", out var tmpgroupName)) groupName = tmpgroupName.GetString();
+                                existStudents.Add((studId, name, no, groupId, groupName));
+                            }
+
+                            //如果classId相同,則將該學生加入現有的學生清單內。
+                            foreach (var item in tmpStuds)
+                            {
+                                if (item.Value.classId.Equals(classId, StringComparison.Ordinal))
+                                {
+                                    existStudents.Add((item.Key, item.Value.name, item.Value.no, null, null));
+                                    retStudentsClassInfo.Add(item.Key, (classId, periodId, gradeId, item.Value.name, item.Value.no));
+                                }
+                            }
+                            existStudents = existStudents.OrderBy(o => string.IsNullOrWhiteSpace(o.no)).ThenBy(o => string.IsNullOrEmpty(o.no) ? 0 : int.Parse(o.no)).ToList();
+
 
                             #region 組JSON
                             using var memoryStream = new MemoryStream();
                             using var writer = new Utf8JsonWriter(memoryStream);
                             writer.WriteStartObject();
                             //寫入除了students及開頭為"_"的數據資料
-                            foreach (var element in item.Value.EnumerateObject())
+                            foreach (var element in classInfo.Value.EnumerateObject())
                             {
                                 if (!(element.Name.Equals("students", StringComparison.Ordinal) || element.Name.StartsWith("_")))
                                 {
@@ -1532,7 +1637,23 @@ namespace TEAMModelOS.Controllers
                             writer.WritePropertyName("students");
                             writer.WriteStartArray();
 
-                            //將原本已存在的學生寫回去
+                            foreach (var item in existStudents)
+                            {
+                                writer.WriteStartObject();
+                                writer.WriteString("id", item.id);
+                                if (string.IsNullOrWhiteSpace(item.name)) writer.WriteNull("name");
+                                else writer.WriteString("name", item.name);
+                                if (string.IsNullOrWhiteSpace(item.no)) writer.WriteNull("no");
+                                else writer.WriteString("no", item.no);
+                                if (string.IsNullOrWhiteSpace(item.groupId)) writer.WriteNull("groupId");
+                                else writer.WriteString("groupId", item.groupId);
+                                if (string.IsNullOrWhiteSpace(item.groupName)) writer.WriteNull("groupName");
+                                else writer.WriteString("groupName", item.groupName);
+                                writer.WriteEndObject();
+                            }
+
+                            /**
+                            將原本已存在的學生寫回去
                             var Documents = item.Value.GetProperty("students").EnumerateArray();
                             while (Documents.MoveNext())
                             {
@@ -1552,7 +1673,7 @@ namespace TEAMModelOS.Controllers
                                 writer.WriteEndObject();
                             }
 
-                            //將欲加入的學生寫入該教室名單內
+                            將欲加入的學生寫入該教室名單內
                             var studList = students
                                 .Where(o => o.Value.classId.Equals(classId, StringComparison.Ordinal))
                                 .Select(o => new { id = o.Key, o.Value.name, o.Value.no }).ToList();
@@ -1568,6 +1689,7 @@ namespace TEAMModelOS.Controllers
 
                                 retStudentsClassInfo.Add(stud.id, (classId, periodId, gradeId, stud.name, stud.no));
                             }
+                            */
 
                             writer.WriteEndArray();
                             writer.WriteEndObject();
@@ -1689,7 +1811,7 @@ namespace TEAMModelOS.Controllers
 
                 CosmosContainer cosmosContainer = _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Student");
                 //查學生的基本資料
-                string queryText = $"SELECT * FROM c WHERE c.pk = 'Base' AND c.id IN ({string.Join(",", studentsInfo.Select(o => $"'{o.Key}'"))})";
+                string queryText = $"SELECT * FROM c WHERE c.code = 'Base-{schoolId}' AND c.id IN ({string.Join(",", studentsInfo.Select(o => $"'{o.Key}'"))})";
 
                 List<JsonElement> listStudent = new List<JsonElement>();
 

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

@@ -195,6 +195,10 @@ namespace TEAMModelOS.Controllers
                     }
                 }
             }
+            else //無此學校資料
+            {
+                return BadRequest();
+            }
             if (roles.Count == 0)
             {
                 roles.Add("teacher");
@@ -214,7 +218,7 @@ namespace TEAMModelOS.Controllers
 
             //取得教室
             List<object> school_classes = new List<object>();
-            await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id,c.x,c.y,c.name,c.teacher,c.periodId,c.gradeId,c.sn,c.no,c.style,c.status,c.openType,c.scope FROM c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{school_code}") }))
+            await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryStreamIterator(queryText: $"SELECT c.id,c.x,c.y,c.name,c.teacher,c.periodId,c.gradeId,c.sn,c.no,c.style,c.status,c.openType,c.scope, ARRAY_LENGTH(c.students) AS studCount 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())

+ 171 - 0
TEAMModelOS/Models/SchoolInfo/SchoolProduct.cs

@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Context.Attributes.Azure;
+
+namespace TEAMModelOS.Models.SchoolInfo
+{
+    [CosmosDB(Database = "TEAMModelOS", Name = "School")]
+    public class SchoolProduct
+    {
+        public string id { get; set; }
+        [PartitionKey]
+        public string code { get; set; }
+        public List<SerialInfoBaseWithdeviceBound> serial { get; set; }
+        public ProductService service { get; set; }
+        public List<ProductHard> hard { get; set; }
+        public Aclassone aclassone { get; set; }
+
+    }
+
+    public class Aclassone
+    {
+        public List<string> ids { get; set; }
+        public int total { get; set; }
+        public int used { get; set; }
+        public int less { get; set; }
+    }
+
+    public class SerialInfoBase
+    {
+        public string id { get; set; }
+        public string serial { get; set; }
+        public string prodCode { get; set; }
+        public int clientQty { get; set; }
+        public long regDate { get; set; }
+        public long startDate { get; set; }
+        public long endDate { get; set; }
+        public int deviceMax { get; set; }
+        public object aprule { get; set; }
+        public string expireStatus { get; set; }
+        public int status { get; set; }
+    }
+
+    public class SerialInfoBaseWithdeviceBound : SerialInfoBase
+    {
+        public List<deviceBound> deviceBound { get; set; }
+    }
+    public class SerialInfoBaseWithdeviceBoundExt : SerialInfoBase
+    {
+        public List<deviceBoundExt> deviceBound { get; set; }
+    }
+
+
+    public class deviceBound
+    {
+        public string uuid { get; set; }
+        public string uuid2 { get; set; }
+        public string deviceId { get; set; }
+        public string classId { get; set; }
+    }
+    public class deviceBoundRich : deviceBound
+    {
+        public string serial { get; set; }
+    }
+    public class deviceBoundExt: deviceBound
+    {
+        public string ip { get; set; }
+        public string pcname { get; set; }
+        public string os { get; set; }
+        public string cpu { get; set; }
+        public int ram { get; set; }
+    }
+
+    public class ProductService
+    {
+        public List<ServiceMainPeriod> mainperiod { get; set; }
+        public List<ServicePeriod> period { get; set; }
+        public List<ServiceProduct> product { get; set; }
+    }
+    public class ServiceMainPeriod
+    {
+        public string mainPeriodId { get; set; }
+        public string mainPeriodtype { get; set; }
+        public long startDate { get; set; }
+        public long endDate { get; set; }
+        public bool active { get; set; }
+    }
+    public class ServicePeriod
+    {
+        public string mainPeriodId { get; set; }
+        public string periodId { get; set; }
+        public long startDate { get; set; }
+        public long endDate { get; set; }
+        public bool active { get; set; }
+    }
+
+    public class ServiceProduct
+    {
+        public string prodCode { get; set; }
+        public bool noperiod { get; set; }
+        public string serviceType { get; set; }
+        public List<ServiceProductAuth> auth { get; set; }
+        public int avaliable { get; set; }
+    }
+    public class ServiceProductResult
+    {
+        public string prodCode { get; set; }
+        public bool noperiod { get; set; }
+        public string serviceType { get; set; }
+        public long startDate { get; set; }
+        public long endDate { get; set; }
+        public int avaliable { get; set; }
+    }
+    public class ServiceProductAclassoneResult : ServiceProductResult
+    {
+        public int used { get; set; }
+        public int less { get; set; }
+    }
+    public class ServiceProductAuth
+    {
+        public string orderId { get; set; }
+        public string periodId { get; set; }
+        public int number { get; set; }
+        public string unit { get; set; }
+    }
+
+    public class ServiceProductAuthHistoryBasic
+    {
+        public string orderId { get; set; }
+        public long orderDate { get; set; }
+        public int number { get; set; }
+        public string unit { get; set; }
+    }
+    public class ServiceProductAuthHistory : ServiceProductAuthHistoryBasic
+    {
+        public List<string> periodIdList { get; set; }
+    }
+    public class ServiceProductAuthHistoryStartEnd : ServiceProductAuthHistoryBasic
+    {
+        public long startDate { get; set; }
+        public long endDate { get; set; }
+    }
+
+    public class ProductHard
+    {
+        public string prodCode { get; set; }
+        public string model { get; set; }
+        public string serial { get; set; }
+    }
+
+    public class CoreUuid
+    {
+        public string serial { get; set; }
+        public string os { get; set; }
+        public string ip { get; set; }
+        public string uuid { get; set; }
+        public string uuid2 { get; set; }
+        public string deviceId { get; set; }
+        public string pcname { get; set; }
+        public string cpu { get; set; }
+        public int ram { get; set; }
+    }
+
+    public class periodZone
+    {
+        public long startDate { get; set; }
+        public long endDate { get; set; }
+    }
+}
+