浏览代码

Merge branch 'develop' of http://163.228.141.122:3000/TEAMMODEL/TEAMModelOS into develop

CrazyIter_Bin 5 月之前
父节点
当前提交
c965acc2d3

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

@@ -624,4 +624,8 @@ export default {
     getCsSchoolByGeo(data) {
         return post('/schoolcheck/get-school-geo', data)
     },
+    //取得學校或TMID產品授權狀況
+    getSchoolProdauth(data) {
+        return post('/schoolcheck/get-school-prodauth', data)
+    },
 }

+ 4 - 2
TEAMModelBI/ClientApp/src/language/lang/zh-cn.js

@@ -69,6 +69,7 @@ const zh_cn = {
         sendMessage: '消息推送',
         productAuth: '产品授权',
         productAnalysis: '产品分析',
+        productAuthorization: '产品授权',
         userRelated: '用户相关',
         issueCoupon: '发优惠券',
         jointPurchase: '统购设定',
@@ -902,7 +903,7 @@ const zh_cn = {
         space: '空间',
         inputSpace: '请输入容量',
         unit: '单位',
-        expirationDate: '使用期限',
+        expirationDate: '起讫日期筛选',
         startDate: '开始时间',
         endDate: '结束时间',
         quota: '名额',
@@ -962,7 +963,8 @@ const zh_cn = {
         IPALB6EY: 'IES5个人空间',
         Z6ELB6EZ: 'HiTeach5 ID授权',
         VA67B6EZ: 'HiTeach5 县市授权',
-        VAA7B6EY: 'IES5个人空间县市授权',
+        VAA7B6EY: 'IES5个人空间县市授权',        
+        export: '汇出',
     },
     aprule: {
         hdcam: 'USB录影支援',

+ 9 - 3
TEAMModelBI/ClientApp/src/language/lang/zh-tw.js

@@ -68,7 +68,7 @@ const zh_tw = {
         oprateDiary: '操作日志',
         sendMessage: '消息推送',
         productAnalysis: '產品分析',
-        productAuth: '產品授權',
+        productAuthorization: '產品授權',
         userRelated: '用戶相關',
         issueCoupon: '發優惠券',
         jointPurchase: '統購設定',
@@ -895,7 +895,7 @@ const zh_tw = {
         space: '空間',
         inputSpace: '請輸入容量',
         unit: '單位',
-        expirationDate: '使用期限',
+        expirationDate: '起訖日期篩選',
         startDate: '開始時間',
         endDate: '結束時間',
         quota: '名額',
@@ -935,6 +935,11 @@ const zh_tw = {
         message6: '請輸入數字',
         message7: '學校已移除',
     },
+    productAuthorize:
+    {
+        filterType: '篩選條件',
+
+    },
     auth:{
         YMPCVCIM: '學情分析模組',
         IPDYZYLC: '智慧學校管理服務',
@@ -955,7 +960,8 @@ const zh_tw = {
         IPALB6EY: 'IES5個人空間',
         Z6ELB6EZ: 'HiTeach5 ID授權',
         VA67B6EZ: 'HiTeach5 縣市授權',
-        VAA7B6EY: 'IES5個人空間縣市授權',
+        VAA7B6EY: 'IES5個人空間縣市授權',        
+        export: '匯出',
     },
     aprule: {
         hdcam: 'USB錄影支援',

+ 4 - 4
TEAMModelBI/ClientApp/src/router/index.js

@@ -76,14 +76,14 @@ const routes = [
                 isShow: true,
                 component: () => require.ensure([], (require) => require(`@/view/product/index.vue`))
             },
-            //品授權
+            //品授權
             {
-                name: "auth",
-                path: "auth",
+                name: "productAuthorization",
+                path: "productAuthorization",
                 permission: "teacher-read|teacher-upd",
                 roles: ['admin'],
                 isShow: true,
-                component: () => require.ensure([], (require) => require(`@/view/product/index.vue`))
+                component: () => require.ensure([], (require) => require(`@/view/product/authorization.vue`))
             },
             // 統購平台
             {

+ 4 - 4
TEAMModelBI/ClientApp/src/view/common/aside.vue

@@ -137,12 +137,12 @@ export default {
             sort: 7,
           },
           {
-            name: proxy.$t(`menu.productAuth`),
-            router: '/home/auth',
-            icon: '#icon-fenxi1',
+            name: proxy.$t(`menu.productAuthorization`),
+            router: '/home/productAuthorization',
+            icon: '#icon-fuzhi',
             permission: [],
             isShow: true,
-            sort: 7,
+            sort: 9,
           },
           {
             name: proxy.$t(`menu.userRelated`),

+ 1 - 1
TEAMModelBI/ClientApp/src/view/htcommunity/adminpanel.vue

@@ -826,7 +826,7 @@ EEEE    120
           let sheetName = proxy.$t(`purchase.sheetSummary`);
           XLSX.utils.book_append_sheet(wb, ws, sheetName);
           //分頁2 已領取清單
-          let titleTch = [proxy.$t(`purchase.schoolName`), proxy.$t(`purchase.schoolCode`), proxy.$t(`purchase.receiverName`), proxy.$t(`purchase.receiverId`)];
+          let titleTch = [proxy.$t(`purchase.schoolName`), proxy.$t(`purchase.schoolCode`), proxy.$t(`purchase.receiverId`), proxy.$t(`purchase.receiverName`)];
           let keyTch = ['schoolName', 'shortCode', 'tmid', 'name'];
           let dataTch = [];
           Object.entries(res.school).forEach(([purchaseIdNow, purchaseSchoolData]) => {

+ 527 - 0
TEAMModelBI/ClientApp/src/view/product/authorization.vue

@@ -0,0 +1,527 @@
+<template>
+  <el-container> 
+    <el-main class="header-select">
+      <el-collapse v-model="activeName" accordion >
+        <el-collapse-item :title="$t(`productAuthorize.filterType`)" name="1">
+      <!-- 篩選區域 -->
+      <div class="filtratebox">
+        <el-form label-position="top" style="line-height: 40px">
+          <!-- 篩選產品 -->
+          <el-form-item label="篩選產品" >
+            <el-button type="primary" block @click="selectAll(true)" plain size="small">全選</el-button>
+            <el-button type="primary" block @click="selectAll(false)" plain size="small">全不選</el-button>
+            <el-checkbox-group v-model="selectedProducts" style="text-align: left;">
+              <el-checkbox  
+                v-for="product in productData"
+                :key="product.prodcode"
+                :label="product.prodcode"  >
+                {{ product.name }}
+              </el-checkbox>
+            </el-checkbox-group>
+          </el-form-item>
+
+          <!-- 篩選日期 -->
+           
+          <el-form-item label="篩選日期">            
+            <el-input-number v-model="dateFilter.number" :min="1" /> &nbsp;&nbsp;
+            <el-select v-model="dateFilter.unit" placeholder="請選擇單位">
+              <el-option
+                v-for="unit in dateUnits"
+                :key="unit.value"
+                :label="unit.label"
+                :value="unit.value"
+              />
+            </el-select>&nbsp;&nbsp;
+            <el-select v-model="dateFilter.direction" placeholder="請選擇方向">
+              <el-option
+                v-for="direction in dateDirections"
+                :key="direction.value"
+                :label="direction.label"
+                :value="direction.value"
+              />
+            </el-select>
+          </el-form-item>
+
+          <!-- 起訖日期篩選 -->                           
+          <el-form-item :label="$t('purchase.expirationDate')" :label-width="formLabelWidth" prop="time" style="width: 500px">
+              <el-date-picker v-model="dateRange" type="daterange" range-separator="To" :start-placeholder="$t('purchase.startDate')" :end-placeholder="$t('purchase.endDate')" :size="size" format="YYYY/MM/DD" value-format="YYYY-MM-DD" />
+          </el-form-item>
+          <div style="float: left;width: 100%;text-align: left; color: red;">
+            [篩選日期] 與 [起訖日期篩選] 以 [篩選日期] 為優先條件
+          </div>          
+
+          <!-- 篩選對象 -->
+          <el-form-item label="篩選對象">            
+            <el-select v-model="selectedaccountType" placeholder="請選擇對象">
+              <el-option
+                v-for="type in accountTypes"
+                :key="type.value"
+                :label="type.label"
+                :value="type.value"
+              />
+            </el-select>
+          </el-form-item>
+          
+          <!-- 篩選按鈕 -->          
+          <el-button type="primary" block @click="filterData">篩選</el-button>
+          <el-button type="primary" block @click="clearFilter">清除篩選</el-button>
+        </el-form>
+      </div>
+        </el-collapse-item>           
+      </el-collapse>
+
+      <!-- 表格 -->
+      <div style="width:100%;margin-bottom:0px;line-height: 70px !important;">
+        <div style="width:10%;float: left;">
+          資料筆數:{{ filteredData.length }}
+        </div>
+        <!-- margin-right: 1%;
+  line-height: 50px !important;
+  float: right; -->
+        <div style="float: right;">
+          <el-button type="primary" @click="exportExcel">{{$t(`auth.export`)}}</el-button>
+        </div>
+      </div>
+
+      <div class="table-container" v-loading="loading" :element-loading-text="$t(`product.prepareData`)+'...'">
+        <el-table  :data="filteredData" border style="width:100%"  :default-sort="{ prop: 'school', order: 'ascending' }" class="custom-table" >
+          <el-table-column prop="name" label="名稱" sortable align="center" />
+          <el-table-column prop="product" label="產品名稱" sortable align="center" />
+          <el-table-column prop="startDate" label="開始日期" sortable align="center" />
+          <el-table-column prop="endDate" label="結束日期" sortable align="center" />
+        </el-table>
+      </div>
+    </el-main>
+  </el-container>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted, getCurrentInstance } from "vue";
+  let { proxy } = getCurrentInstance()
+  let loading = ref(false)
+  const activeName = ref('1')
+  // 模擬的產品清單
+  const products = ["產品 A", "產品 B", "產品 C", "產品 D"];
+  let productData = ref([
+    {
+      name: 'ezStation 2',
+      prodcode: '3222NIYD',
+    },
+    {
+      name: 'HiTeach STD',
+      prodcode: 'J223IZ6M',
+    },
+    {
+      name: 'HiTeach TBL',
+      prodcode: '3222C6D2',
+    },
+    {
+      name: 'HiTeach PRO',
+      prodcode: 'J223IZAM',
+    },
+    {
+      name: 'HiTeach Lite',
+      prodcode: 'J2236ZCX',
+    },
+    {
+      name: 'HiTeach Mobile',
+      prodcode: '3222DNG2',
+    },
+    {
+      name: 'HiTeach Premium',
+      prodcode: '3222IAVN',
+    },
+    {
+      name: 'HiTeach5',
+      prodcode: 'BYJ6LZ6Z',
+    },
+    {
+      name: 'HiTeachCC',
+      prodcode: 'LZLL6ZEI',
+    },
+    {
+      name: proxy.$t(`auth.YMPCVCIM`),
+      prodcode: 'YMPCVCIM',
+    },
+    {
+      name: proxy.$t(`auth.IPDYZYLC`),
+      prodcode: 'IPDYZYLC',
+    },
+    {
+      name: proxy.$t(`auth._3CLYJ6NP`),
+      prodcode: '3CLYJ6NP',
+    },
+    {
+      name: proxy.$t(`auth.IPALJ6NY`),
+      prodcode: 'IPALJ6NY',
+    },
+    {
+      name: proxy.$t(`auth.VABAJ6NV`),
+      prodcode: 'VABAJ6NV',
+    },
+    {
+      name: proxy.$t(`auth._0VPBDZPG`),
+      prodcode: '0VPBDZPG',
+    },
+    {
+      name: proxy.$t(`auth.B9GPJ6NY`),
+      prodcode: 'B9GPJ6NY',
+    },
+    {
+      name: proxy.$t(`auth.LY9AJ6NY`),
+      prodcode: 'LY9AJ6NY',
+    },
+    {
+      name: proxy.$t(`auth.YL9CJ6NY`),
+      prodcode: 'YL9CJ6NY',
+    },
+    {
+      name: proxy.$t(`auth.LL9MJ6NY`),
+      prodcode: 'LL9MJ6NY',
+    },
+    {
+      name: proxy.$t(`auth.B6V5J6NP`),
+      prodcode: 'B6V5J6NP',
+    },
+    {
+      name: proxy.$t(`auth.LSZYJ6NA`),
+      prodcode: 'LSZYJ6NA',
+    },
+    {
+      name: proxy.$t(`auth.CVGPJ6NN`),
+      prodcode: 'CVGPJ6NN',
+    },
+    {
+      name: proxy.$t(`auth.VDPGJ6NC`),
+      prodcode: 'VDPGJ6NC',
+    },
+    {
+      name: proxy.$t(`auth.YPXSJ6NJ`),
+      prodcode: 'YPXSJ6NJ',
+    },
+    {
+      name: proxy.$t(`auth.VLY6J6N6`),
+      prodcode: 'VLY6J6N6',
+    },
+  ])
+
+
+  // 模擬的表格資料
+  const tableData = reactive([
+    {
+      accountType: "school",
+      name: "學校甲",
+      product: "產品 A",
+      startDate: "2024-01-01",
+      endDate: "2024-12-31",
+    },
+    {
+      accountType: "school",
+      name: "學校乙",
+      product: "產品 B",
+      startDate: "2024-03-01",
+      endDate: "2024-09-30",
+    },
+    {
+      accountType: "school",
+      name: "學校丙",
+      product: "產品 C",
+      startDate: "2024-02-01",
+      endDate: "2024-08-31",
+    },
+    {
+      accountType: "school",
+      name: "學校丁",
+      product: "產品 A",
+      startDate: "2024-05-01",
+      endDate: "2025-04-30",
+    },
+    {
+      accountType: "tmid",
+      name: "ID01",
+      product: "產品 B",
+      startDate: "2024-05-01",
+      endDate: "2025-04-30",
+    },
+    {
+      accountType: "tmid",
+      name: "ID01",
+      product: "產品 A",
+      startDate: "2024-05-01",
+      endDate: "2025-04-30",
+    },
+    {
+      accountType: "tmid",
+      name: "ID02",
+      product: "產品 B",
+      startDate: "2024-05-01",
+      endDate: "2025-04-30",
+    },
+    {
+      accountType: "tmid",
+      name: "ID03",
+      product: "產品 C",
+      startDate: "2024-05-01",
+      endDate: "2025-04-30",
+    },
+  ]);
+
+  // 篩選條件
+  const selectedProducts = ref([]);
+  const selectedaccountType = ref('school');
+  const dateFilter = reactive({
+    number: 0,
+    unit: "",
+    direction: "",
+  });
+  const startDate = ref(null); // 起始日期
+  const endDate = ref(null); // 結束日期
+  let dateRange = ref([]);
+  let filteredData = reactive([]);
+
+  // 日期單位與方向選項
+  const dateUnits = [
+    { label: "請選擇單位", value: "" },
+    { label: "年", value: "year" },
+    { label: "月", value: "month" },
+    { label: "週", value: "week" },
+    { label: "日", value: "day" },
+  ];
+
+  const accountTypes = [
+    { label: "學校", value: "school" },
+    { label: "醍摩豆ID", value: "tmid" },
+  ];
+  const dateDirections = [
+    { label: "請選擇方向", value: "" },
+    { label: "之前", value: "before" },
+    { label: "之後", value: "after" },
+  ];
+
+  //getfilteredData()
+
+  onMounted(() => {
+
+    getfilteredData()
+
+  })
+
+  function getfilteredData() {
+    loading.value = true
+
+    filteredData.splice(0, filteredData.length);
+    let filtered = JSON.parse(JSON.stringify(tableData));
+
+    // 按產品篩選
+    if (selectedProducts.value && selectedProducts.value.length) {
+      filtered = filtered.filter((item) =>
+        selectedProducts.value.includes(item.product)
+      );
+    }
+
+    // 按篩選日期
+    if (dateFilter.number && dateFilter.unit && dateFilter.direction) {
+      const today = new Date();
+      const compareDate = new Date(today);
+      const unitMap = {
+        year: "FullYear",
+        month: "Month",
+        week: "Date",
+        day: "Date",
+      };
+
+      if (dateFilter.unit === "week") {
+        compareDate.setDate(
+          today.getDate() +
+          (dateFilter.direction === "before" ? -7 : 7) * dateFilter.number
+        );
+      } else {
+        compareDate[`set${unitMap[dateFilter.unit]}`](
+          today[`get${unitMap[dateFilter.unit]}`]() +
+          (dateFilter.direction === "before" ? -1 : 1) * dateFilter.number
+        );
+      }
+
+      filtered = filtered.filter((item) => {
+        const endDate = new Date(item.endDate);
+        return dateFilter.direction === "before"
+          ? endDate <= compareDate
+          : endDate >= compareDate;
+      });
+    }
+
+    // 起始/結束日期篩選
+    if (startDate.value) {
+      filtered = filtered.filter(
+        (item) => new Date(item.startDate) >= new Date(startDate.value)
+      );
+    }
+    if (endDate.value) {
+      filtered = filtered.filter(
+        (item) => new Date(item.endDate) <= new Date(endDate.value)
+      );
+    }
+    // 按對象篩選
+    if (selectedaccountType.value) {
+      filtered = filtered.filter((item) =>
+        selectedaccountType.value.includes(item.accountType)
+      );
+    }
+
+
+    // filtered.forEach(element => {
+    //   filteredData.push(element)
+    // });
+
+    let authEndYMWD = dateFilter.number ? dateFilter.number : 0
+    if (dateFilter.direction === "before") {
+      authEndYMWD = authEndYMWD * -1
+    }
+    let authStart = 0
+    let authEnd = 0
+    if (dateRange.value && dateRange.value.length > 0) {
+      authStart = new Date(dateRange.value[0]).getTime() / 1000
+      authEnd = new Date(dateRange.value[1]).getTime() / 1000
+    }
+
+    let data = {
+      idType: selectedaccountType.value, //ID類型 school:學校 tmid:TMID ※預設:school
+      prodCode: selectedProducts.value, //產品代碼
+      authEndYMWD: dateFilter.unit, //日期單位:year:年 month:月 week:週 day:日
+      authEndPeriod: authEndYMWD, //日期計算數 [例] -3:從現在起往前算三個日期單位 3:從現在起往後算三個日期單位
+      authStart: authStart, //授權起始日 ※「授權起始日、授權終止日」與「日期單位、日期計算數」擇一成立
+      authEnd: authEnd, //授權終止日
+    }
+    
+    try {
+      //取得學校或TMID產品授權狀況
+      proxy.$api.getSchoolProdauth(data).then((res) => {
+        loading.value = false
+        if (typeof res.err === "string" && res.err.length === 0 && (res.serial.length > 0 || res.service.length > 0)) {
+          let serial = JSON.parse(JSON.stringify(res.serial));
+          let service = JSON.parse(JSON.stringify(res.service));
+          service.forEach(element => {
+            //  if(!element.startDate || !element.endDate){
+            //   let aaa = element
+
+            // }
+            // 轉換產品名稱
+            let product = productData.value.find(item => {
+              return item.prodcode === element.prodCode
+            })
+            let filteredItem = {
+              name: element.saleClient.name,
+              product: product.name,
+              startDate: proxy.$common.timestampToTime(element.startDate, '', true),
+              endDate: proxy.$common.timestampToTime(element.endDate, '', true),
+            }
+            filteredData.push(filteredItem)
+          });
+          serial.forEach(element => {
+            // if(!element.startDate || !element.endDate){
+            //   let aaa = element
+
+            // }
+            //轉換產品名稱
+            let product = productData.value.find(item => {
+              return item.prodcode === element.prodCode
+            })
+            let filteredItem = {
+              name: element.saleClient.name,
+              product: product.name,
+              startDate: proxy.$common.timestampToTime(element.authSysStartDate, '', true),
+              endDate: proxy.$common.timestampToTime(element.authSysEndDate, '', true),
+            }
+            filteredData.push(filteredItem)
+          });
+        }
+      })
+    } catch (error) {
+      console.log(error)
+    } finally {
+      
+    }
+   
+  }
+
+
+
+  function filterData() {
+    getfilteredData()
+  };
+
+  function clearFilter() {
+    selectedProducts.value = [];
+    selectedaccountType.value = 'school';
+
+    dateFilter.number = 0
+    dateFilter.unit = ""
+    dateFilter.direction = ""
+
+    startDate.value = 0; // 起始日期
+    endDate.value = 0; // 結束日期
+    dateRange.value = [];
+  };
+  function selectAll(flag) {
+    if (flag) {
+      productData.value.forEach(item => {
+        selectedProducts.value.push(item.prodcode)
+      })
+    } else {
+      selectedProducts.value = [];
+    }
+  };
+  //导出日志
+    function exportExcel () {
+      require.ensure([], () => {
+        const { export_json_to_excel } = require('../../until/excel/Export2Excel')
+        const tHeader = ['名稱', '產品名稱', '開始日期', '結束日期'] // excel文档第一行显示的标题
+        const filterVal = ['name', 'product', 'startDate', 'endDate'] // id,version等都是上面标题所对应的数据
+        const list = filteredData
+        const data = formatJson(filterVal, list)
+        export_json_to_excel(tHeader, data, '產品授權') //标题,数据,文件名
+      })
+    }
+    function formatJson (filterVal, jsonData) {
+      return jsonData.map((v) => filterVal.map((j) => v[j]))
+    }
+
+
+</script>
+
+<style>
+.filtratebox {
+  width: 100%;
+  padding: 10px 20px 0px 20px;
+  background: #fff;
+  border-top: 1px solid #ccc;
+  position: relative;
+}
+
+.table-container {
+  margin: 0 auto;
+  width: 98%;
+  margin-bottom: 150px;
+  margin-top: 10px;
+  line-height: 10px
+}
+
+.custom-table >>> .el-table__header,
+.custom-table >>> .el-table__body,
+.custom-table >>> .el-table__row {
+  border-color: black !important;
+}
+.el-form-item {
+  margin-bottom: 5px;
+}
+.header-select,
+.middlebox {
+  /* width: 100%; */
+  /* background-color: #fff; */
+  margin: 10px 20px;
+}
+.header-select .el-collapse-item__header {
+  font-size: 16px;
+  padding-left: 0.5%;
+}
+</style>

+ 1 - 1
TEAMModelBI/appsettings.Development.json

@@ -17,7 +17,7 @@
     "Exp": 86400,
     "IdTokenSalt": "8263692E2213497BB55E74792B7900B4",
     "HttpTrigger": "https://teammodelosfunction-test.chinacloudsites.cn/api/",
-	"Audience": "72643704-b2e7-4b26-b881-bd5865e7a7a5",
+    "Audience": "72643704-b2e7-4b26-b881-bd5865e7a7a5",
     "Authority": "https://login.chinacloudapi.cn/4807e9cf-87b8-4174-aa5b-e76497d7392b/v2.0"
     //"HttpTrigger": "http://localhost:7071/api/"
   },

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

@@ -320,6 +320,10 @@ export default {
 			})
 		})
     },
+    // 需要删除的错题
+    deleteWrongItem: function(data) {
+        return post("/common/exam/delete-wrong-item", data)
+    },
     // 获取错题数量
     getErrorItemCnt: function(data) {
         return post("/common/exam/get-error-item-cnt", data)

+ 1 - 1
TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.vue

@@ -154,7 +154,7 @@
 	import JsPDF from "jspdf";
 	import domtoimage from "@/utils/dom_to_image";
 	import AnalysisItemTable from "@/components/evaluation/AnalysisItemTable";
-	import OptionsTable from "@/components/evaluation/OptionsTable";
+	import OptionsTable from "@/components/evaluation/htOptionsTable";
 	import BaseLine from "@/components/student-analysis/total/BaseLine.vue";
 	import BaseRateLine from "@/components/student-analysis/total/BaseRateLine.vue";
 	import BaseTestSingleScatter from "@/components/student-analysis/total/BaseTestSingleScatter.vue";

+ 11 - 6
TEAMModelOS/ClientApp/src/components/evaluation/htExerciseList.vue

@@ -109,7 +109,8 @@
 								<div class="item-explain-details">
 									<AnalysisItemTable :analysisJson="analysisJson[index]"></AnalysisItemTable>
 									<br />
-									<OptionsTable v-if="item.type === 'single' || item.type === 'multiple' || item.type === 'judge'" :options="item.option.map((i) => i.code)" :optionRate="optionRate[index]" :answer="item.answer"> </OptionsTable>
+									<OptionsTable v-if="item.type === 'single' || item.type === 'multiple' || item.type === 'judge'" :options="item.option.map((i) => i.code)" 
+										:optionRate="optionRate[index]" :answer="item.answer"> </OptionsTable>
 									<br />
 									<Row>
 										<Col span="24">
@@ -407,7 +408,11 @@
 			getScatterData() {
 				let analysisJson = JSON.parse(JSON.stringify(this.getAnalysisJson));
 				if (!analysisJson) return;
-				let curSubjectIndex = analysisJson.subjects.map((i) => i.name).indexOf(this.$store.state.totalAnalysis.currentSubject);
+				// 必須改成以class為單位的統計資料
+				//let curSubjectIndex = analysisJson.subjects.map((i) => i.name).indexOf(this.$store.state.totalAnalysis.currentSubject);
+				let subjectId = analysisJson.classes[this.$store.state.totalAnalysis.curClassIndex].subjects[0].id;
+				let curSubjectIndex = analysisJson.subjects.findIndex(item => item.id === subjectId);
+				//let subject = analysisJson.subjects.find(sub => sub.id === subjectId);
 				let result = [];
 				analysisJson.paper[curSubjectIndex].value.forEach((exercise, exerciseIndex) => {
 					let obj = {};
@@ -460,8 +465,8 @@
 			getOptionLineData(item, index) {
 				let result = [];
 				let n = this.optionRate[index];
-				let total = this.$store.state.totalAnalysis.analysisJson.all.total; // 取总人数
-                let lost = this.$store.state.totalAnalysis.analysisJson.all.lost; // 缺考人数
+				let total = this.$store.state.totalAnalysis.analysisJsonJoint.all.total; // 取总人数
+                let lost = this.$store.state.totalAnalysis.analysisJsonJoint.all.lost; // 缺考人数
 				let phCount = Math.floor(total * 0.27); //取高分组低分组人数
 				let realCount = total - lost;
 				item.option
@@ -605,8 +610,8 @@
 		},
 		computed: {
 			// 获取最新试题分析模块数据
-			getAnalysisJson() {
-				return this.$store.state.totalAnalysis.analysisJson;
+			getAnalysisJson() {				
+				return this.$store.state.totalAnalysis.analysisJsonJoint;
 			},
 			curScope() {
 				return this.filterOrigin === this.$store.state.userInfo.schoolCode ? "school" : "private";

+ 6 - 3
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/SubjectMould.vue

@@ -338,9 +338,12 @@ export default {
         margin-bottom: 15px;
         width: 70%;
 
-        img {
-            width: 100%;
-        }
+        /* img {
+            width: 100% !important;
+        } */
+    }
+    .que-item /deep/ img {
+        width: 100%;
     }
 
     .left-img {

+ 12 - 4
TEAMModelOS/ClientApp/src/components/student-web/WrongQusetion/AnswerBox.vue

@@ -486,11 +486,11 @@ export default {
             })
             this.$api.studentWeb.getExamItem(params).then(async res => {
                 try {
+                    let newList = this._.cloneDeep(this.qsAll)
                     if(res.error.length) {
                         let sas = await this.getSas(this.courseNow.scope)
                         let code = this.courseNow.scope === 'school' ? (this.userInfo.scope === 'student' ? this.userInfo.azp : this.userInfo.sub) : this.courseNow.teacherId
                         let courId = this.courseNow.scope === 'school' ? (this.$route.params.subjectId || this.courseNow.subject.id) : this.courseNow.id
-                        let newList = this._.cloneDeep(this.qsAll)
                         for (let i = 0; i < newList.length; i++) {
                             let info = res.error.find(err => newList[i].qId === err.items.id)
                             if(!info) {
@@ -559,6 +559,14 @@ export default {
                             }
                         }
                     } else {
+                        for (let i = 0; i < newList.length; i++) {
+                            this.noInfoExe ++
+                            this.noInfoExamArr.push({
+                                activityId: newList[i].activityId,
+                                qId: newList[i].qId,
+                                unitId: newList[i].unitId
+                            })
+                        }
                         this.$Message.error(this.$t('studentWeb.wrongQues.message2'))
                         setTimeout(() => {
                             this.$router.go(-1)
@@ -581,10 +589,10 @@ export default {
         deleteExam() {
             let params = {
                 sid: this.courseNow.scope == 'school' ? `${this.courseNow.school}-${this.userInfo.sub}` : `${this.userInfo.sub}`,
-                data: JSON.stringify(this.noInfoExamArr)
+                data: this.noInfoExamArr
             }
-            this.$api.studentWeb.deleteExam(params, this.$parent.wrongApi).then(res => {
-                if(res.data.err) {
+            this.$api.studentWeb.deleteWrongItem(params).then(res => {
+                if(res.errors) {
 
                 }
             })

+ 7 - 2
TEAMModelOS/ClientApp/src/utils/evTools.js

@@ -559,8 +559,13 @@ export default {
 			let sasString = curScope === 'school' ? await $tools.getSchoolSas() : await $tools.getPrivateSas()
 			let privateSas = sasString.sas
 			// 如果是活動版sas拿法不同
-			if(paper.blob.indexOf("jointexam") != -1){			
-				privateSas = await this.getBlobPrivateSas(paper.creatorId)
+			if(paper.blob.indexOf("jointexam") != -1){	
+				if(paper.creatorId){
+					privateSas = await this.getBlobPrivateSas(paper.creatorId)
+				}else{
+					privateSas = await this.getBlobPrivateSas(paper.examId)
+				}		
+				
 				blobHost ="https://teammodel.blob.core.windows.net/"+paper.creatorId
 			}
 			

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

@@ -34,7 +34,7 @@
           </Checkbox>
         </div>
         <span style="margin-left: 15px;vertical-align: top;color: #2d8cf0;">{{$t('unit.text12')}}{{tableFilterData.length}}{{$t('unit.text7')}}</span>
-        <span style="float:right">
+        <span style="float:right"  v-if="$store.state.config.srvAdr === 'China'">
             <Dropdown>
                 <a href="javascript:void(0)">
                     {{ $t('researchCenter.dashboard.cols') }}
@@ -42,7 +42,7 @@
                 </a>
                 <template #list>
                     <DropdownMenu>
-                      <DropdownItem>
+                      <DropdownItem v-if="$store.state.config.srvAdr === 'China'">
                           <Checkbox v-model="selectColumns[2]" style="margin-right:10px" @on-change="filterColumn('imei',selectColumns[2])">
                             <span style="margin-left: 5px">{{ $t('stuAccount.imei') }}</span>
                           </Checkbox>

+ 6 - 3
TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/TestAnalysis/htQuestionList.vue

@@ -305,8 +305,11 @@ export default {
     /* 获取所有试题的对应分析数据 */
     getExerciseList() {
       let analysisJson = JSON.parse(JSON.stringify(this.getAnalysisJson))
-      let curSubjectIndex = analysisJson.subjects.map(i => i.id).indexOf(this.$store.state.totalAnalysis
-        .currentSubjectJoint)
+      // 必須改成以class為單位的統計資料
+
+      //let curSubjectIndex = analysisJson.subjects.map(i => i.id).indexOf(this.$store.state.totalAnalysis.currentSubjectJoint)
+      let subjectId = analysisJson.classes[this.$store.state.totalAnalysis.curClassIndex].subjects[0].id;
+			let curSubjectIndex = analysisJson.subjects.findIndex(item => item.id === subjectId);
       let result = []
       analysisJson.paper[curSubjectIndex].value.forEach((exercise, exerciseIndex) => {
         let obj = {}
@@ -315,7 +318,7 @@ export default {
         })
 
         analysisJson.classes.forEach(classItem => {
-          obj[classItem.className] = classItem.subjects[curSubjectIndex].item[exerciseIndex]
+          obj[classItem.className] = classItem.subjects[0].item[exerciseIndex]
         })
         result.push(obj)
       })

+ 74 - 0
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -5356,6 +5356,80 @@ namespace TEAMModelOS.Controllers
             return Ok(new { error });
         }
 
+        //刪除不存在的錯題
+        [ProducesDefaultResponseType]
+        //[AuthToken(Roles = "teacher,admin,student")]
+        [HttpPost("delete-wrong-item")]
+        [Authorize(Roles = "IES")]
+        public async Task<IActionResult> deleteWrongItem(JsonElement request)
+        {
+            if (!request.TryGetProperty("data", out JsonElement items)) return BadRequest();
+            if(!request.TryGetProperty("sid", out JsonElement _sid)) return BadRequest();
+            try
+            {
+                List<errorItemInfo> errors = items.ToObject<List<errorItemInfo>>();
+                string sid = _sid.GetString();
+                var client = _azureCosmos.GetCosmosClient();
+                List<string> ReqExamIds = errors.Select(c => c.activityId).Distinct().ToList(); //
+                var queryClass = $"select value(c) from c where c.id in ({string.Join(",", ReqExamIds.Select(o => $"'{o}'"))}) and c.pk = 'Exam'";
+                List<ExamInfo> exams = new();
+                await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIteratorSql<ExamInfo>(queryText: queryClass))
+                {
+                    exams.Add(item);
+                }
+                List<errorItemInfo> existItems = new List<errorItemInfo>();
+                if (exams.Count > 0)
+                {
+                    foreach (errorItemInfo itemInfo in errors)
+                    {
+                        ExamInfo info = exams.Where(c => c.id.Equals(itemInfo.activityId)).FirstOrDefault();
+                        if (info == null) continue;
+                        string subjectId = itemInfo.unitId;
+                        int subjectIndex = info.subjects.Select((item, index) => (item, index)).FirstOrDefault(s => s.item.id.Equals(subjectId)).index;
+                        string itemId = itemInfo.qId;
+                        string container = (info.scope.Equals("school")) ? $"{info.school}" : $"{info.creatorId}";
+                        string blob = info.papers[subjectIndex].blob;
+                        if (_azureStorage.GetBlobContainerClient($"{container}").GetBlobClient($"{blob}/{itemInfo.qId}.json").Exists())
+                        {
+                            existItems.Add(itemInfo);
+                        }
+                    }
+                }
+                if (existItems.Count > 0)
+                {
+                    foreach (errorItemInfo existItem in existItems)
+                    {
+                        errors.RemoveAll(e => e.activityId.Equals(existItem.activityId) && e.qId.Equals(existItem.qId) && e.unitId.Equals(existItem.unitId));
+                    }
+                }
+                //呼叫刪除API
+                int delFlg = 0;
+                if (errors.Count > 0)
+                {
+                    string domain = _configuration.GetValue<string>("MaLearn:url");
+                    string key = _configuration.GetValue<string>("MaLearn:key");
+                    string url = $"{domain}/api/qdelete";
+                    var httpClient = _httpClient.CreateClient();
+                    httpClient.DefaultRequestHeaders.Add("x-functions-key", key);
+                    Dictionary<string, string> dataDic = new Dictionary<string, string>();
+                    dataDic.Add("sid", sid);
+                    dataDic.Add("data", errors.ToJsonString());
+                    HttpContent content = new StringContent(dataDic.ToJsonString(), Encoding.UTF8, "application/json");
+                    HttpResponseMessage httpResponse = await httpClient.PostAsync(url, content);
+                    if (httpResponse.IsSuccessStatusCode)
+                    {
+                        delFlg = 1;
+                    }
+                }
+
+                return Ok(new { del = delFlg, errors });
+            }
+            catch (Exception ex)
+            {
+                return BadRequest(ex.Message);
+            }
+        }
+
         //記入班級評量結果批改結果 ※目前只套用統測結果批改ReadItemAsync
         private async Task upsertExamClassResultMark(ExamClassResult examClassResult, string userId, string userName, List<stus> students, List<List<double>> points)
         {