Browse Source

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

CrazyIter_Bin 1 year ago
parent
commit
3c476acf2b

+ 5 - 3
TEAMModelBI/ClientApp/src/until/http.js

@@ -21,8 +21,7 @@ axios.interceptors.request.use(
             config.url.indexOf('bizconfig') != -1 ||
             config.url.indexOf('paper') != -1 ||
             config.url.indexOf('notice') != -1 ||
-            config.url.indexOf('bizuser') != -1 ||
-            config.url.indexOf('prodanalysis') != -1 
+            config.url.indexOf('bizuser') != -1
         ) {
             config.headers = {
                 'Content-Type': 'application/json',
@@ -33,7 +32,10 @@ axios.interceptors.request.use(
                 'Content-Type': 'application/json',
                 // 'site': 'china'
             }
-        } else if (config.url.indexOf('ies5') != -1 || config.url.indexOf('tmidstics') != -1 || config.url.indexOf('/service/PushNotify') != -1) {
+        } else if (config.url.indexOf('ies5') != -1 ||
+            config.url.indexOf('tmid') != -1 ||
+            config.url.indexOf('/service/PushNotify') != -1 ||
+            config.url.indexOf('prodanalysis') != -1) {
             config.headers = {
                 'Content-Type': 'application/json',
                 'authorization': 'Bearer ' + JSON.parse(localStorage.access_token)

+ 3 - 2
TEAMModelBI/Controllers/BIProductAnalysis/ProductAnalysisController.cs

@@ -1,5 +1,6 @@
 using Azure.Cosmos;
 using DingTalk.Api.Request;
+using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Configuration;
@@ -55,7 +56,7 @@ namespace TEAMModelBI.Controllers.ProductAnalysis
         /// <param name="jsonElement"></param>
         /// <returns></returns>
         [ProducesDefaultResponseType]
-        [AuthToken(Roles = "admin")]
+        [Authorize(Roles = "IES")]
         [HttpPost("get-iotstics")]
         public async Task<IActionResult> GetIotStics(JsonElement jsonElement)
         {
@@ -161,7 +162,7 @@ namespace TEAMModelBI.Controllers.ProductAnalysis
         /// <param name="jsonElement"></param>
         /// <returns></returns>
         [ProducesDefaultResponseType]
-        [AuthToken(Roles = "admin")]
+        [Authorize(Roles = "IES")]
         [HttpPost("get-school")]
         public async Task<IActionResult> GetSchoolInfo(JsonElement jsonElement)
         {

+ 1 - 3
TEAMModelBI/Controllers/BITmid/TmidController.cs

@@ -50,7 +50,6 @@ namespace TEAMModelBI.Controllers.BITmid
         /// <param name="jsonElement"></param>
         /// <returns></returns>
         [ProducesDefaultResponseType]
-        //[AuthToken(Roles = "admin")]
         [Authorize(Roles = "IES")]
         [HttpPost("get-tmidstics")]
         public async Task<IActionResult> GetTmidStics(JsonElement jsonElement)
@@ -538,7 +537,7 @@ namespace TEAMModelBI.Controllers.BITmid
         /// <param name="jsonElement"></param>
         /// <returns></returns>
         [ProducesDefaultResponseType]
-        //[AuthToken(Roles = "admin")]
+        [Authorize(Roles = "IES")]
         [HttpPost("get-tmid-iot")]
         public async Task<IActionResult> GetTmidIot(JsonElement jsonElement)
         {
@@ -642,7 +641,6 @@ namespace TEAMModelBI.Controllers.BITmid
         /// <param name="jsonElement"></param>
         /// <returns></returns>
         [ProducesDefaultResponseType]
-        //[AuthToken(Roles = "admin")]
         [Authorize(Roles = "IES")]
         [HttpPost("get-tmid-useprod")]
         public async Task<IActionResult> GetTmidUseProd(JsonElement jsonElement)

+ 5 - 0
TEAMModelOS/ClientApp/src/static/Global.js

@@ -569,6 +569,11 @@ const HI_TEACH_EVENT = () => {
 			text: '智慧评分',
 			type: 'fn',
 			relation: 'smart'
+		},
+		CoworkLoad: {
+			text: '协作',
+			type: 'fn',
+			relation: 'cowork'
 		}
 	}
 }

+ 184 - 214
TEAMModelOS/ClientApp/src/view/areaMgmt/AreaIndex.vue

@@ -18,7 +18,7 @@
 						<!-- <div class="right-top-bottombox-title" :class="[item.classname === 'school' || item.classname === 'teach' || item.classname === 'datas' ? 'all-top-box' : item.classname === 'size' || item.classname === 'months' ? 'sizeclass' : '']">
 							<p class="right-top-title">{{ item.title }}</p>
 						</div> -->
-            <p class="right-top-title">{{ item.title }}</p>
+						<p class="right-top-title">{{ item.title }}</p>
 						<!-- <div class="right-top-bottombox-comparison" v-if="item.classname === 'school' || item.classname === 'teach' || item.classname === 'datas' || item.classname === 'student'">
 							<p>
 								{{ $t("areaStatistics.header.weekAdd") }}:<span class="week-comparison">{{ item.addweek }}</span>
@@ -37,26 +37,17 @@
 					</p>
 				</div>
 				<div :class="[items.type === 'month' ? 'alonebox' : 'totalalonebox']" v-for="(items, indexs) in activityData.oneself" :key="indexs">
-					<p :class="[items.type === 'month' ? 'alonebox-title' : 'total-alonebox-title']">{{ items.title }}</p>
 					<p :class="[items.type === 'month' ? 'alonebox-content' : 'total-alonebox-content']">{{ items.num }}</p>
+					<p :class="[items.type === 'month' ? 'alonebox-title' : 'total-alonebox-title']">{{ items.title }}</p>
 				</div>
 			</div>
-			<div class="rightbox">
+			<div class="rightbox center-resource-right">
 				<div class="tagbox">
-					<p class="tags basics">
-						<span>{{ $t("areaStatistics.tag.basics") }}</span>
+					<p class="tags dynamic">
+						<span>{{ $t("areaStatistics.tag.classActive") }}</span>
 					</p>
 				</div>
-				<!--<div class="rightbox-pie">
-               <CommonPie :proportionData="areaData.alonePie"></CommonPie>
-            </div>-->
-				<div class="rightbox-pie anaphase">
-					<!-- <ConventionPie :proportionData="areaData.research"></ConventionPie> -->
-					<ConventionPie :pieData="areaData.research"></ConventionPie>
-				</div>
-				<div class="rightbox-pie versions">
-					<ConventionPie :pieData="areaData.versions"></ConventionPie>
-				</div>
+				<CommonLine :lineData="areaData.dynamic"></CommonLine>
 			</div>
 		</div>
 		<div class="center-resource">
@@ -66,11 +57,18 @@
 						<span>{{ $t("areaStatistics.tag.classData") }}</span>
 					</p>
 				</div>
+				<div class="chats-tabs">
+					<RadioGroup v-model="timeTab" type="button" button-style="solid" size="small">
+						<Radio label="今年"></Radio>
+						<Radio label="上周"></Radio>
+						<Radio label="本月"></Radio>
+					</RadioGroup>
+				</div>
 				<div class="center-resource-left-leftEcharts">
-					<div class="echarts-title">{{ $t("areaStatistics.class.total") }}:</div>
-					<ConventionPie :pieData="areaData.class"></ConventionPie>
+					<!-- <div class="echarts-title">{{ $t("areaStatistics.class.total") }}:</div> -->
+					<ConventionPie :pieData="timeTab === '今年' ? areaData.class : timeTab === '上周' ? areaData.lastweek : areaData.monthsContrast"></ConventionPie>
 				</div>
-				<div class="center-resource-left-rightbox">
+				<!-- <div class="center-resource-left-rightbox">
 					<div class="week-echart">
 						<div class="echarts-title-pie">{{ $t("areaStatistics.class.lastWeek") }}:</div>
 						<ConventionPie :pieData="areaData.lastweek"></ConventionPie>
@@ -79,15 +77,22 @@
 						<div class="echarts-title-pie">{{ $t("areaStatistics.class.nowMonth") }}:</div>
 						<ConventionPie :pieData="areaData.monthsContrast"></ConventionPie>
 					</div>
-				</div>
+				</div> -->
 			</div>
-			<div class="center-resource-right">
+			<div class="center-resource-right rightbox" style="margin-top: 0">
 				<div class="tagbox">
-					<p class="tags dynamic">
-						<span>{{ $t("areaStatistics.tag.classActive") }}</span>
+					<p class="tags basics">
+						<span>{{ $t("areaStatistics.tag.basics") }}</span>
 					</p>
 				</div>
-				<CommonLine :lineData="areaData.dynamic"></CommonLine>
+
+				<div class="rightbox-pie anaphase">
+					<!-- <ConventionPie :proportionData="areaData.research"></ConventionPie> -->
+					<ConventionPie :pieData="areaData.research"></ConventionPie>
+				</div>
+				<div class="rightbox-pie versions">
+					<ConventionPie :pieData="areaData.versions"></ConventionPie>
+				</div>
 			</div>
 		</div>
 		<div class="bottom-resource">
@@ -105,9 +110,9 @@
 						<span>{{ $t("areaStatistics.tag.size") }}</span>
 					</p>
 				</div>
-				<div class="bottom-rightbox-left">
+				<!-- <div class="bottom-rightbox-left">
 					<Liquidfill :liquidfillData="areaData.size"></Liquidfill>
-				</div>
+				</div> -->
 				<div class="bottom-rightbox-right">
 					<ConventionPie :pieData="areaData.sizeProportion"></ConventionPie>
 				</div>
@@ -120,7 +125,7 @@
 				</p>
 			</div>
 			<div class="listbox">
-				<Table :columns="columns" :data="data">
+				<Table :columns="columns" :data="data" stripe>
 					<template #badge="{ row }">
 						<!--<img :src="row.picture" width="50px" height="50px" />-->
 						<img :src="row.picture" class="schoolImg" v-if="row.picture" />
@@ -148,6 +153,7 @@
 			return {
 				loadingshow: false,
 				states: "develop",
+				timeTab: "今年",
 				showPattern: "all",
 				aspectsData: [
 					{ id: 1, title: this.$t("areaStatistics.header.areaTilte"), num: 0, icon: "iconfont icon-24gl-school", classname: "months", addweek: 0, addmonth: 0 },
@@ -327,30 +333,28 @@
 					},
 					//某个学区活动占比
 					research: {
-						color: ["#ff7979", "#badc58", "#7ed6df", "#f6e58d", "#686de0"],
+						color: ["#37a2da", "#32c5e9", "#9fe6b8", "#ffdb5c", "#ff9f7f", "#fb7293", "#e7bcf3", "#8378ea"],
 						legend: {
-							right: "0%",
-							orient: "vertical",
-							itemWidth: 8,
+							bottom: 0,
+							itemWidth: 16,
 							itemHeight: 8, // 修改icon图形大小
 							textStyle: {
-								fontSize: 12,
-								color: "#333"
+								fontFamily: "Hm",
+								color: "#666"
 							}
 						},
 						series: [
 							{
 								name: this.$t("areaStatistics.basics.activityP"),
 								type: "pie",
-								radius: "70%",
-								center: ["45%", "50%"],
+								radius: "65%",
+								center: ["50%", "40%"],
 								itemStyle: {
 									borderRadius: 8
 								},
 								label: {
 									normal: {
-										position: "inner",
-										show: false
+										position: "outer"
 									}
 								},
 								data: [
@@ -364,31 +368,29 @@
 					},
 					//版本占比
 					versions: {
-						color: ["#5c7bd9", "#9fe080", "#ffdc60"],
+						color: ["#ff9f7f", "#fb7293", "#e7bcf3"],
 						legend: {
-							right: "0%",
-							orient: "vertical",
-							itemWidth: 8,
+							bottom: 0,
+							itemWidth: 16,
 							itemHeight: 8, // 修改icon图形大小
 							textStyle: {
-								fontSize: 12,
-								color: "#333"
+								fontFamily: "Hm",
+								color: "#666"
 							}
 						},
 						series: [
 							{
 								name: this.$t("areaStatistics.basics.versionsP"),
 								type: "pie",
-								radius: "70%",
-								center: ["45%", "50%"],
+								radius: "65%",
+								center: ["50%", "40%"],
 								// roseType: 'area',
 								itemStyle: {
 									borderRadius: 8
 								},
 								label: {
 									normal: {
-										position: "inner",
-										show: false
+										position: "outer"
 									}
 								},
 								data: [
@@ -401,21 +403,15 @@
 					},
 					//课例数据
 					class: {
-						color: ["#3fb1e3", "#6be6c1", "#626c91", "#a0a7e6", "#c4ebad", "#96dee8"],
+						color: ["#afa3f5", "#00d488", "#3feed4", "#3bafff", "#f1bb4c"],
 						legend: {
-							bottom: "1%",
-							orient: "horizontal",
-							itemWidth: 8,
+							bottom: 0,
+							itemWidth: 12,
 							itemHeight: 8, // 修改icon图形大小
 							textStyle: {
 								fontSize: 12,
-								color: "#333"
-							},
-							formatter: function (name) {
-								if (name.length > 6) {
-									name = name.slice(0, 6) + "...";
-								}
-								return name;
+								color: "#666",
+								fontFamily: "Hm"
 							},
 							tooltip: {
 								show: true
@@ -426,43 +422,28 @@
 								name: this.$t("areaStatistics.basics.schoolP"),
 								type: "pie",
 								radius: "65%",
-								center: ["40%", "40%"],
-								itemStyle: {
-									borderRadius: 5
-								},
+								center: ["50%", "40%"],
 								label: {
 									normal: {
-										position: "inner",
-										show: false
+										position: "outer",
+										show: true
 									}
 								},
-								data: [
-									{ value: 1340, name: "锦江区外国语小学" },
-									{ value: 380, name: "川师大附小" },
-									{ value: 658, name: "树德中学" },
-									{ value: 936, name: "石室中学" },
-									{ value: 200, name: "石室小学" }
-								]
+								data: []
 							}
 						]
 					},
 					//上周课例对比
 					lastweek: {
-						color: ["#3fb1e3", "#6be6c1", "#626c91", "#a0a7e6", "#c4ebad", "#96dee8"],
+						color: ["#afa3f5", "#00d488", "#3feed4", "#3bafff", "#f1bb4c"],
 						legend: {
-							right: "1%",
-							orient: "vertical",
-							itemWidth: 8,
+							bottom: 0,
+							itemWidth: 12,
 							itemHeight: 8, // 修改icon图形大小
 							textStyle: {
 								fontSize: 12,
-								color: "#333"
-							},
-							formatter: function (name) {
-								if (name.length > 12) {
-									name = name.slice(0, 12) + "...";
-								}
-								return name;
+								color: "#666",
+								fontFamily: "Hm"
 							},
 							tooltip: {
 								show: true
@@ -472,44 +453,29 @@
 							{
 								name: this.$t("areaStatistics.basics.schoolP"),
 								type: "pie",
-								radius: "75%",
-								center: ["30%", "50%"],
-								itemStyle: {
-									borderRadius: 5
-								},
+								radius: "65%",
+								center: ["50%", "40%"],
 								label: {
 									normal: {
-										position: "inner",
-										show: false
+										position: "outer",
+										show: true
 									}
 								},
-								data: [
-									// { value: 1340, name: '锦江区外国语小学' },
-									// { value: 380, name: '川师大附小' },
-									// { value: 58, name: '树德中学' },
-									// { value: 36, name: '石室中学' },
-									// { value: 100, name: '石室小学' },
-								]
+								data: []
 							}
 						]
 					},
 					//本月课例对比
 					monthsContrast: {
-						color: ["#3fb1e3", "#6be6c1", "#626c91", "#a0a7e6", "#c4ebad", "#96dee8"],
+						color: ["#afa3f5", "#00d488", "#3feed4", "#3bafff", "#f1bb4c"],
 						legend: {
-							right: "1%",
-							orient: "vertical",
-							itemWidth: 8,
+							bottom: 0,
+							itemWidth: 12,
 							itemHeight: 8, // 修改icon图形大小
 							textStyle: {
 								fontSize: 12,
-								color: "#333"
-							},
-							formatter: function (name) {
-								if (name.length > 12) {
-									name = name.slice(0, 12) + "...";
-								}
-								return name;
+								color: "#666",
+								fontFamily: "Hm"
 							},
 							tooltip: {
 								show: true
@@ -519,22 +485,15 @@
 							{
 								name: this.$t("areaStatistics.basics.schoolP"),
 								type: "pie",
-								radius: "75%",
-								center: ["30%", "50%"],
-								itemStyle: {
-									borderRadius: 5
-								},
+								radius: "65%",
+								center: ["50%", "40%"],
 								label: {
 									normal: {
-										position: "inner",
-										show: false
+										position: "outer",
+										show: true
 									}
 								},
-								data: [
-									// { value: 140, name: '锦江区外国语小学' },
-									// { value: 380, name: '川师大附小' },
-									// { value: 100, name: '石室小学' },
-								]
+								data: []
 							}
 						]
 					},
@@ -562,12 +521,19 @@
 							}
 						},
 						calculable: true,
+						legend: {
+							textStyle: {
+								fontFamily: "Hm"
+							},
+							itemWidth: 12,
+							itemHeight: 10
+						},
 						xAxis: [
 							{
 								type: "category",
 								axisLine: {
 									lineStyle: {
-										color: "rgba(204,187,225,0.5)"
+										color: "#666"
 									}
 								},
 								splitLine: {
@@ -576,6 +542,11 @@
 								axisTick: {
 									show: false
 								},
+								axisLabel: {
+									// 其他样式设置,如字体大小、颜色等
+									fontFamily: "Hm",
+									padding: [10, 0]
+								},
 								data: []
 							}
 						],
@@ -587,8 +558,13 @@
 								},
 								axisLine: {
 									lineStyle: {
-										color: "rgba(204,187,225,0.5)"
+										color: "#666"
 									}
+								},
+								axisLabel: {
+									// 其他样式设置,如字体大小、颜色等
+									fontFamily: "Hm",
+									padding: [0, 5]
 								}
 							}
 						],
@@ -597,7 +573,7 @@
 								show: true,
 								height: 10,
 								xAxisIndex: [0],
-								bottom: 10,
+								bottom: 0,
 								start: 0,
 								end: 65,
 								handleIcon: "path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z",
@@ -664,21 +640,17 @@
 							formatter: "{a} <br/>{b} : {c} ({d}%)"
 						},
 						legend: {
-							orient: "vertical",
-							right: "5%",
-							top: "15%",
-							bottom: "15%",
-							itemWidth: 15,
-							itemHeight: 15,
+							bottom: 0,
+							itemWidth: 12,
+							itemHeight: 8, // 修改icon图形大小
 							textStyle: {
-								fontSize: 12
+								fontSize: 12,
+								color: "#666",
+								fontFamily: "Hm"
 							},
-							data: [
-								//{ value: 0, name: '线上研修' },
-								//{ value: 0, name: '校本研修' },
-								//{ value: 0, name: '认证材料' },
-								//{ value: 0, name: '课堂实录' },
-							]
+							tooltip: {
+								show: true
+							}
 						},
 						series: [
 							{
@@ -686,7 +658,8 @@
 								name: "",
 								type: "pie",
 								selectedMode: "single",
-								radius: [0, "55%"],
+								radius: [0, "35%"],
+								center:['50%','40%'],
 								label: {
 									normal: {
 										position: "inner",
@@ -707,7 +680,8 @@
 							{
 								name: this.$t("areaStatistics.basics.scheduleP"),
 								type: "pie",
-								radius: ["65%", "90%"],
+								center:['50%','40%'],
+								radius: ["45%", "65%"],
 								data: [
 									//{ value: 0, name: '线上研修' },
 									//{ value: 0, name: '校本研修' },
@@ -724,13 +698,16 @@
 						// color: ['#05f8d6', '#0082fc', '#fdd845', '#22ed7c', '#09b0d3', '#1d27c9', '#f9e264', '#f47a75', '#009db2', '#024b51', '#0780cf', '#765005'],
 						color: ["#3fb1e3", "#6be6c1", "#626c91", "#a0a7e6", "#c4ebad", "#96dee8", "#05f8d6", "#09b0d3", "#765005", "#22ed7c", "#fdd845", "#0082fc"],
 						legend: {
-							right: "1%",
-							orient: "vertical",
-							itemWidth: 8,
+							bottom: 0,
+							itemWidth: 12,
 							itemHeight: 8, // 修改icon图形大小
 							textStyle: {
 								fontSize: 12,
-								color: "#333"
+								color: "#666",
+								fontFamily: "Hm"
+							},
+							tooltip: {
+								show: true
 							}
 						},
 						tooltip: {
@@ -743,8 +720,8 @@
 							{
 								name: this.$t("areaStatistics.basics.sizeP"),
 								type: "pie",
-								radius: "90%",
-								center: ["50%", "50%"],
+								radius: "65%",
+								center: ["50%", "40%"],
 								itemStyle: {
 									borderRadius: 8,
 									normal: {
@@ -1053,13 +1030,16 @@
 	};
 </script>
 
-<style scoped>
+<style lang="less" scoped>
+	:deep(.listbox .ivu-table-header thead tr th){
+		padding: 20px 0;
+	}
 	.statisticsbox,
 	.statisticsbox-all {
 		width: 100%;
 		height: 100%;
 		padding: 1.5%;
-		line-height: 20px;
+		/* line-height: 20px; */
 		overflow: auto;
 		background: #f2f2f2;
 	}
@@ -1100,15 +1080,13 @@
 
 	.right-top-text {
 		width: 64%;
-		/* background: rgba(178, 190, 195, 0.2);*/
 		background: #fff;
 		float: left;
-		height: 100%;
+		height: 110px;
 		display: flex;
-    flex-direction: column;
-    justify-content: center;
-    align-items: center;
-    padding-top: 15px;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
 	}
 
 	.top-header-icon {
@@ -1123,12 +1101,9 @@
 	}
 
 	.right-top-num {
-		/* position: absolute; */
-		font-size: 48px;
-		/* top: 25%;
-		left: 50%; */
-		/* transform: translate(-50%, -50%); */
-		color: #515a6e;
+		font-size: 40px;
+		font-weight: 600;
+		color: #5f5f5f;
 	}
 
 	.right-top-num span {
@@ -1140,9 +1115,10 @@
 	.right-top-bottombox {
 		width: 100%;
 		text-align: center;
-    margin-top: 15px;
-		line-height: 14px;
-    color: #ccc;
+		/* margin-top: 15px; */
+		/* line-height: 14px; */
+		margin-bottom: 5px;
+		color: #ccc;
 	}
 
 	.right-top-bottombox-title,
@@ -1196,7 +1172,7 @@
 		flex-wrap: wrap;
 		background-color: #fff;
 		margin-top: 30px;
-		padding: 15px;
+		padding: 60px 15px 30px 15px;
 		position: relative;
 		justify-content: space-between;
 		border-radius: 5px;
@@ -1214,47 +1190,26 @@
 		border-radius: 5px;
 	}
 
-	.alonebox {
-		width: 19%;
-		padding: 7px;
-		text-align: left;
-		background-color: #b8ced7;
-		border-radius: 5px;
-		margin: 2% 2.5%;
-	}
-
+	.alonebox,
 	.totalalonebox {
 		width: 19%;
 		padding: 7px;
-		text-align: left;
-		background-color: #96c3d7;
-		border-radius: 5px;
-		margin: 1% 2.5%;
-	}
-
-	.alonebox-title {
-		font-size: 14px;
-		color: #535c68;
-		font-weight: 600;
-		margin-bottom: 16px;
+		text-align: center;
+		color: #a19f9f;
+		/* background-color: #96c3d7; */
+		margin: 1% 2%;
 	}
 
+	.alonebox-title,
 	.total-alonebox-title {
 		font-size: 14px;
-		font-weight: 600;
-		color: #dff9fb;
-		margin-bottom: 16px;
-	}
-
-	.alonebox-content {
-		font-size: 26px;
-		color: #fff;
-		font-weight: 750;
+		margin-top: 10px;
+		margin-bottom: 10px;
 	}
-
+	.alonebox-content,
 	.total-alonebox-content {
-		font-size: 26px;
-		color: #fff;
+		font-size: 32px;
+		color: #5f5f5f;
 		font-weight: 750;
 	}
 
@@ -1267,25 +1222,27 @@
 		width: 100%;
 		height: 40px;
 		position: absolute;
-		top: 3px;
+		top: 10px;
 		left: 1px;
 	}
 
 	.tags {
-		width: 100px;
-		height: 20px;
+		width: 120px;
+		height: 30px;
 		background: rgb(256 169 100) no-repeat center;
 		background-size: contain;
-		font-size: 12px;
+		font-size: 13px;
 		font-weight: 400;
 		color: #ffffff;
 		text-align: center;
 		position: relative;
-		display: block;
+		display: flex;
+		align-items: center;
+		padding-left: 20px;
 	}
 
 	.tags:after {
-		border: 12px solid #fff;
+		border: 15px solid #fff;
 		content: "";
 		border-top-color: transparent;
 		border-left-color: transparent;
@@ -1293,7 +1250,7 @@
 		width: 0px;
 		height: 0px;
 		position: absolute;
-		left: calc(118px - 40px);
+		left: calc(130px - 40px);
 		top: 0px;
 	}
 
@@ -1307,22 +1264,29 @@
 
 	.center-resource-left {
 		width: 48%;
-		height: 300px;
+		/* height: 300px; */
 		position: relative;
 		background: #fff;
-		padding: 15px;
+		/* padding: 15px; */
+		padding: 60px 20px 30px 20px;
 		display: flex;
 		justify-content: space-between;
 		border-radius: 5px;
 	}
 
+	.center-resource-left .chats-tabs {
+		position: absolute;
+		right: 20px;
+		top: 20px;
+	}
+
 	.center-resource-right {
 		width: 50%;
 		/* margin-left: 2%; */
-		height: 300px;
+		/* height: 300px; */
 		position: relative;
 		background: #fff;
-		padding: 23px;
+		padding: 60px 20px 30px 20px;
 		overflow: hidden;
 		border-radius: 5px;
 	}
@@ -1344,22 +1308,24 @@
 
 	.bottom-leftbox {
 		width: 48%;
-		padding: 1%;
-		height: 200px;
+		/* padding: 1%; */
+		height: 400px;
 		background: #fff;
 		position: relative;
 		border-radius: 5px;
+		padding: 60px 20px 30px 20px;
 	}
 
 	.bottom-rightbox {
 		width: 50%;
-		height: 200px;
+		/* height: 200px; */
 		background-color: #fff;
 		display: flex;
 		justify-content: center;
 		position: relative;
 		margin-left: 2%;
 		border-radius: 5px;
+		padding: 60px 20px 30px 20px;
 	}
 
 	.bottom-rightbox-left {
@@ -1369,7 +1335,7 @@
 	}
 
 	.bottom-rightbox-right {
-		width: 55%;
+		width: 100%;
 		height: 100%;
 	}
 
@@ -1541,7 +1507,7 @@
 
 	.center-resource-left-leftEcharts,
 	.center-resource-left-rightbox {
-		width: 50%;
+		width: 100%;
 		height: 270px;
 		position: relative;
 	}
@@ -1678,7 +1644,7 @@
 	.school-Listinfo {
 		width: 100%;
 		margin-top: 0px;
-		padding: 30px 15px 15px 15px;
+		padding: 60px 15px 15px 15px;
 		position: relative;
 		background-color: #fff;
 		margin-bottom: 30px;
@@ -1729,13 +1695,17 @@
 		top: 30%;
 	}
 	.schoolImg {
-		width: 15%;
+		width: 50px;
+		margin: 15px 0;
+		border-radius: 50%;
 	}
 	.schoolnot {
-		width: 20%;
-		line-height: 60px;
-		margin: 0 auto;
-		background: #95a5a6;
+		width: 50px;
+		height: 50px;
+		margin: 15px auto;
+		font-size: 12px;
+		background: #c1c3c4;
+		border-radius: 50%;
 		color: #ecf0f1;
 		display: flex;
 		justify-content: center;

+ 24 - 4
TEAMModelOS/ClientApp/src/view/classrecord/ClassRecord.vue

@@ -149,7 +149,7 @@
                     <!-- 互动数据 -->
                     <div v-for="event in item.pageData" :key="event.Time">
                       <!-- 即问即答 -->
-                      <PopQues class="event-item" v-if="event.Event === 'PopQuesLoad' || event.Event === 'ReAtmpAnsStrt'" :evtType="event.Event" :irsData="event.data" :students="baseData.student"></PopQues>
+                      <PopQues class="event-item" v-if="event.Event === 'PopQuesLoad' || event.Event === 'ReAtmpAnsStrt'" :evtType="event.Event" :irsData="event.data" :students="baseData.student" :recordInfo="recordInfo" :blobInfo="blobInfo"></PopQues>
                       <!-- 抢权 -->
                       <Buzr class="event-item student-event" v-else-if="event.Event === 'BuzrAns'" :buzrData="event.data" :students="baseData.student"></Buzr>
                       <!-- 推送 -->
@@ -164,6 +164,8 @@
                       <Exam class="event-item" :examInfo="event.data" :recordInfo="recordInfo" v-else-if="event.Event === 'SPQStrt'"></Exam>
                       <!-- 智慧评分 -->
                       <SmartRating class="event-item student-event" :recordInfo="recordInfo" :smartRate="event.data" :students="baseData.student" :vote="event.vote" :blobInfo="blobInfo" v-else-if="event.Event === 'RatingStart'"></SmartRating>
+                      <!-- 协作 -->
+                      <Cowork class="event-item student-event" :recordInfo="recordInfo" :blobInfo="blobInfo" :cowork="event.data" :students="baseData.student" v-else-if="event.Event === 'CoworkLoad'"></Cowork>
                     </div>
                   </div>
                 </div>
@@ -262,13 +264,14 @@ import Exam from './eventchart/Exam.vue'
 import Receive from './eventchart/Receive.vue'
 import DataCount from './eventchart/DataCount.vue'
 import SmartRating from './eventchart/SmartRating.vue';
+import Cowork from './eventchart/Cowork.vue';
 import CountTo from 'vue-count-to'
 import BlobTool from '@/utils/blobTool.js'
 import FileSaver from "file-saver";
 import JSZip from "jszip";
 export default {
   components: {
-    PopQues, Pick, Push, Receive, DataCount, CountTo, Buzr, Exam, WrkCmp, BaseReportPie, BaseReportRadar, SmartRating
+    PopQues, Pick, Push, Receive, DataCount, CountTo, Buzr, Exam, WrkCmp, BaseReportPie, BaseReportRadar, SmartRating, Cowork
   },
   data() {
     return {
@@ -308,7 +311,13 @@ export default {
           text: this.$t('cusMgt.rcd.filter6'),
           events: ['RatingStart'],
           count: 0
-        }
+        },
+        {
+          value: 'CoworkLoad',
+          text: this.$t('cusMgt.rcd.filter7'),
+          events: ['CoworkLoad'],
+          count: 0
+        },
       ],
       split1: 0.4,
       backPage: undefined,
@@ -317,6 +326,7 @@ export default {
       irsData: [],//irs.json
       taskData: [],//task.json
       smartData: [],//smartRating.json
+      coworkData: [],//Cowork.json
       fnEvents: [],//功能事件
       events: [],//事件ID
       hiTeachEvent: [],//需要解析的事件信息
@@ -525,7 +535,7 @@ export default {
         return
       }
       // 时间轴数据正常
-      //获取Push.json、IRS.json、Task.json、Base.json数据  新增SmartRating.json(智慧评分)
+      //获取Push.json、IRS.json、Task.json、Base.json数据  新增SmartRating.json(智慧评分)  新增Cowork.json(协作)
       try {
         let pushUrl = `${this.blobInfo.blob_uri}/records/${this.recordInfo.id}/IES/Push.json${this.blobInfo.blob_sas}`
         this.pushData = JSON.parse(await this.$tools.getFile(pushUrl) || '[]')
@@ -564,6 +574,12 @@ export default {
       } catch (e) {
         this.smartData = []
       }
+      try {
+        let coworkUrl = `${this.blobInfo.blob_uri}/records/${this.recordInfo.id}/IES/Cowork.json${this.blobInfo.blob_sas}`
+        this.coworkData = JSON.parse(await this.$tools.getFile(coworkUrl) || '[]')
+      } catch (e) {
+        this.coworkData = []
+      }
       let pgids = this.pageIds
       //这里需要判断录制开始的pageid
       // let startInfo = this.pageEvents?.findLast(item => item.Event === 'EzsStartRecord')
@@ -632,6 +648,10 @@ export default {
               }
               this.filterInte[5].count++
               break
+            case 'cowork':
+              e.data = this.coworkData.find(t => t.pageID == e.Pgid)
+              this.filterInte[6].count++
+              break
             default:
               break
           }

+ 14 - 8
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/Buzr.vue

@@ -1,11 +1,13 @@
 <template>
     <div class="buzz-wrap">
-        <p class="event-type">
-            {{$t('cusMgt.rcd.evt1')}}
-        </p>
-        <div class="buzz-box" v-for="(item,index) in buzzClients" :key="index">
-            <!-- <Icon custom="iconfont icon-buzz" size="24" /> -->
-            <span>{{getChinese(item.name)}}</span>
+        <div>
+            <p class="event-type">
+                {{$t('cusMgt.rcd.evt1')}}
+            </p>
+            <div class="buzz-box" v-for="(item,index) in buzzClients" :key="index">
+                <!-- <Icon custom="iconfont icon-buzz" size="24" /> -->
+                <span>{{getChinese(item.name)}}</span>
+            </div>
         </div>
         <StudentClient></StudentClient>
     </div>
@@ -106,8 +108,12 @@ export default {
     }
 }
 .event-type {
-    margin-right: 20px;
+    /* margin-right: 20px;
     font-size: 15px;
-    font-weight: 600;
+    font-weight: 600; */
+    
+    text-align: right;
+    font-weight: bold;
+    margin: 0 10px 5px 0;
 }
 </style>

+ 107 - 0
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/Cowork.vue

@@ -0,0 +1,107 @@
+<template>
+    <div>
+        <div>
+            <p class="action-type">{{ cowork.coworkType === 'All' ? '全体协作' : (cowork.coworkType === 'Group' ? '分组协作' : '差异化协作') }}</p>
+            <div class="cowork-box">
+                <div v-for="(item, index) in coworkData" :key="index" style="">
+                    <img :src="item.snapshotUrl" class="receive-img" alt="" @click="viewImage(item.snapshotUrl)">
+                    <p>
+                        {{ item.title }}
+                        <Tooltip placement="bottom" v-show="cowork.coworkType != 'All'">
+                            <Icon type="ios-contacts" size="20" color="#2D8CF0" />
+                            <template #content>
+                                <span v-for="(stu, sIndex) in item.stuList" :key="sIndex">
+                                    <span v-if="stu">
+                                        {{ stu.name }}
+                                        <span v-show="sIndex != (item.stuList.length - 1)">、</span>
+                                    </span>
+                                </span>
+                            </template>
+                        </Tooltip>
+                    </p>
+                </div>
+            </div>
+        </div>
+        <StudentClient class="receive-student"></StudentClient>
+    </div>
+</template>
+
+<script>
+import StudentClient from './StudentClient.vue'
+export default {
+    components: {
+        StudentClient
+    },
+    props: {
+        recordInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        cowork: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        blobInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        students: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+    },
+    computed: {
+        coworkData() {
+            let data = []
+            if(this.cowork.coworkGroupInfoList.length) {
+                data = this._.cloneDeep(this.cowork.coworkGroupInfoList)
+                data.forEach(item => {
+                    item.snapshotUrl = `${this.blobInfo.blob_uri}/records/${this.recordInfo.id}${item.snapshot}${this.blobInfo.blob_sas}`
+                    item.stuList = item.members.map(members => {
+                        return this.students.find(stu => stu.seatID == members)
+                    })
+                })
+            }
+            return data
+        }
+    },
+    methods: {
+        viewImage(url) {
+            this.$hevueImgPreview(url)
+        },
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.action-type {
+    text-align: right;
+    font-weight: bold;
+    margin: 0 10px 5px 0;
+}
+.cowork-box {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-end;
+
+    &>div {
+        margin: 0 10px 15px;
+        text-align: center;
+    }
+
+    .receive-img {
+        max-width: 150px;
+        max-height: 150px;
+        border: 1px solid #eeeeee;
+        cursor: pointer;
+    }
+}
+</style>

+ 14 - 8
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/Pick.vue

@@ -1,12 +1,14 @@
 <template>
     <!-- 随机挑人 -->
     <div class="pick-wrap">
-        <p class="event-type">
-            {{$t('cusMgt.rcd.evt3')}}
-        </p>
-        <div class="pick-item" v-for="(item,index) in pickRes" :key="index">
-            <p class="student-no">{{item.seatNo}}</p>
-            <p class="student-name">{{item.name}}</p>
+        <div>
+            <p class="event-type">
+                {{$t('cusMgt.rcd.evt3')}}
+            </p>
+            <div class="pick-item" v-for="(item,index) in pickRes" :key="index">
+                <p class="student-no">{{item.seatNo}}</p>
+                <p class="student-name">{{item.name}}</p>
+            </div>
         </div>
         <StudentClient></StudentClient>
     </div>
@@ -109,8 +111,12 @@ export default {
     }
 }
 .event-type {
-    margin-right: 20px;
+    /* margin-right: 20px;
     font-size: 15px;
-    font-weight: 600;
+    font-weight: 600; */
+    
+    text-align: right;
+    font-weight: bold;
+    margin: 0 10px 5px 0;
 }
 </style>

+ 47 - 27
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/PopQues.vue

@@ -1,30 +1,34 @@
 <template>
     <div class="pop-ques-wrap">
         <TeacherClient></TeacherClient>
-        <p class="event-type">
-            {{evtType == 'PopQuesLoad' ? $t('studentWeb.hiteachNote.qA') : $t('studentWeb.hiteachNote.qaAgain')}}
-            <Tooltip :content="$t('talMgmt.text47')">
-            <v-icon class="qu-flip-icon" :style="{'color':isOverview ? '':'#2d8cf0'}" :iconClass="imgSrc" v-show="quType === 'single' || quType === 'multiple' || quType === 'judge'" @click.native="isOverview = !isOverview" />
-            </Tooltip>
-        </p>
-        <!-- 统计数据 -->
-        <template v-if="isOverview">
-            <!-- 单选、多选、判断选项分布 -->
-            <OptionCount v-if="quType === 'single' || quType === 'multiple' || quType === 'judge'" :optionCount="optionData" :answer="answer"></OptionCount>
-            <!-- 填空题(文字题) -->
-            <CompleteAns v-else-if="quType === 'complete'" :answer="answerData" :students="students"></CompleteAns>
-            <!-- 如果有设置正确答案的正确率统计 -->
-            <CorrectRate :correctData="correctData" v-if="hasAnswer"></CorrectRate>
-        </template>
-        <div v-else>
-            <p v-for="key in Object.keys(optionData)" :key="key">
-                <Tag :color="key == 'noAns' ? 'warning' : 'primary'">
-                    {{key == 'noAns' ? $t('cusMgt.rcd.noAns') : key}}
-                </Tag>
-                <Tag color="default" v-for="s in optionData[key]" :key="s">
-                    {{s}}
-                </Tag>
+        <div>
+            <p class="event-type">
+                {{evtType == 'PopQuesLoad' ? $t('studentWeb.hiteachNote.qA') : $t('studentWeb.hiteachNote.qaAgain')}}
+                <Tooltip :content="$t('talMgmt.text47')">
+                <v-icon class="qu-flip-icon" :style="{'color':isOverview ? '':'#2d8cf0'}" :iconClass="imgSrc" v-show="quType === 'single' || quType === 'multiple' || quType === 'judge'" @click.native="isOverview = !isOverview" />
+                </Tooltip>
             </p>
+            <!-- 统计数据 -->
+            <div v-if="isOverview">
+                <!-- 单选、多选、判断选项分布 -->
+                <OptionCount v-if="quType === 'single' || quType === 'multiple' || quType === 'judge'" :optionCount="optionData" :answer="answer"></OptionCount>
+                <!-- 填空题(文字题) -->
+                <CompleteAns v-else-if="quType === 'complete'" :answer="answerData" :students="students"></CompleteAns>
+                <!-- 如果有设置正确答案的正确率统计 -->
+                <CorrectRate :correctData="correctData" v-if="hasAnswer"></CorrectRate>
+                <!-- 问答题(文字、图片、音频) -->
+                <SubjectiveAns v-else-if="quType === 'subjective'" :answer="answerData" :students="students" :recordInfo="recordInfo" :blobInfo="blobInfo" :answerType="irsData.question?.exercise?.answerType" />
+            </div>
+            <div v-else>
+                <p v-for="key in Object.keys(optionData)" :key="key">
+                    <Tag :color="key == 'noAns' ? 'warning' : 'primary'">
+                        {{key == 'noAns' ? $t('cusMgt.rcd.noAns') : key}}
+                    </Tag>
+                    <Tag color="default" v-for="s in optionData[key]" :key="s">
+                        {{s}}
+                    </Tag>
+                </p>
+            </div>
         </div>
     </div>
 </template>
@@ -33,8 +37,15 @@ import TeacherClient from './TeacherClient.vue'
 import CorrectRate from './CorrectRate.vue'
 import OptionCount from './OptionCount.vue'
 import CompleteAns from './CompleteAns.vue'
+import SubjectiveAns from './SubjectiveAns.vue'
 export default {
     props: {
+        recordInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
         irsData: {
             type: Object,
             default: () => {
@@ -50,7 +61,13 @@ export default {
             default: () => {
                 return []
             }
-        }
+        },
+        blobInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
     },
     data() {
         require('@/icons/svg/flop.svg')
@@ -78,7 +95,7 @@ export default {
         }
     },
     components: {
-        OptionCount, CorrectRate, TeacherClient, CompleteAns
+        OptionCount, CorrectRate, TeacherClient, CompleteAns, SubjectiveAns
     },
     watch: {
         irsData: {
@@ -162,8 +179,11 @@ export default {
     display: flex;
 }
 .event-type {
-    margin-right: 20px;
+    /* margin-right: 20px;
     font-size: 15px;
-    font-weight: 600;
+    font-weight: 600; */
+    
+    font-weight: bold;
+    margin-bottom: 5px;
 }
 </style>

+ 4 - 2
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/Push.vue

@@ -37,8 +37,10 @@ export default {
 </script>
 <style lang="less" scoped>
 .text-label{
-    display: inline-block;
-    vertical-align: top;
+    // display: inline-block;
+    // vertical-align: top;
+    font-weight: bold;
+    margin-bottom: 5px;
 }
 .tea-push-img{
     max-width: 120px;

+ 55 - 41
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/Receive.vue

@@ -1,47 +1,51 @@
 <template>
     <!-- 作品收集 -->
     <div class="receive-wrap">
-        <p class="clt-type">
-            <!-- {{cltTypeMap[collateType].text}}收集:{{collateType}} -->
-            {{$t('cusMgt.rcd.evt4')}}
-        </p>
-        <div v-for="(item) in receiveData" :key="item.seatID+item.groupID" class="receive-item">
-            <div v-if="collateType == 0">
-                {{$t('cusMgt.rcd.evt5')}}
-            </div>
-            <!-- 图片类型作品 -->
-            <div v-else-if="collateType == 'Image'">
-                <img v-for="(blob,index) in item.blobFiles" :key="index" class="receive-img" :src="blob" alt="" @click="viewImage(blob)">
-            </div>
-            <!-- HTEX作品类型 -->
-            <div v-else-if="collateType == 'Htex'">
-                <!-- 收集的HTEX作品 -->
-            </div>
-            <!-- 音频 -->
-            <div v-else-if="collateType == 'Audio'">
-                <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="viewAudio(blob)">
-                    <Icon class="collate-type-icon" custom="iconfont icon-audio-outline" />
-                </div>
-            </div>
-            <!-- 视频 -->
-            <div v-else-if="collateType == 'Video'">
-                <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="viewAudio(blob)">
-                    <Icon class="collate-type-icon" custom="iconfont icon-video-outline" />
-                </div>
-            </div>
-            <!-- 文字 -->
-            <div v-else-if="collateType == 'Text'">
-                收集的文字
-            </div>
-            <!-- 附件 -->
-            <div v-else-if="collateType == 'File'">
-                <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="downloadFile(blob,item)">
-                    <Icon class="collate-type-icon" custom="iconfont icon-file" />
+        <div>
+            <p class="clt-type">
+                <!-- {{cltTypeMap[collateType].text}}收集:{{collateType}} -->
+                {{$t('cusMgt.rcd.evt4')}}
+            </p>
+            <div class="receive-box">
+                <div v-for="(item) in receiveData" :key="item.seatID+item.groupID" class="receive-item">
+                    <div v-if="collateType == 0">
+                        {{$t('cusMgt.rcd.evt5')}}
+                    </div>
+                    <!-- 图片类型作品 -->
+                    <div v-else-if="collateType == 'Image'">
+                        <img v-for="(blob,index) in item.blobFiles" :key="index" class="receive-img" :src="blob" alt="" @click="viewImage(blob)">
+                    </div>
+                    <!-- HTEX作品类型 -->
+                    <div v-else-if="collateType == 'Htex'">
+                        <!-- 收集的HTEX作品 -->
+                    </div>
+                    <!-- 音频 -->
+                    <div v-else-if="collateType == 'Audio'">
+                        <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="viewAudio(blob)">
+                            <Icon class="collate-type-icon" custom="iconfont icon-audio-outline" />
+                        </div>
+                    </div>
+                    <!-- 视频 -->
+                    <div v-else-if="collateType == 'Video'">
+                        <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="viewAudio(blob)">
+                            <Icon class="collate-type-icon" custom="iconfont icon-video-outline" />
+                        </div>
+                    </div>
+                    <!-- 文字 -->
+                    <div v-else-if="collateType == 'Text'">
+                        收集的文字
+                    </div>
+                    <!-- 附件 -->
+                    <div v-else-if="collateType == 'File'">
+                        <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="downloadFile(blob,item)">
+                            <Icon class="collate-type-icon" custom="iconfont icon-file" />
+                        </div>
+                    </div>
+                    <p class="name-text">
+                        {{ item.isGroupItem ? item.groupID : item.sName}}
+                    </p>
                 </div>
             </div>
-            <p class="name-text">
-                {{ item.isGroupItem ? item.groupID : item.sName}}
-            </p>
         </div>
         <StudentClient class="receive-student"></StudentClient>
         <!--文件预览-->
@@ -230,8 +234,14 @@ export default {
     right: 0px;
     top: 5px;
 }
+.receive-box {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-end;
+}
 .receive-item {
     margin-right: 20px;
+    text-align: center;
 }
 .receive-img {
     max-width: 150px;
@@ -260,8 +270,12 @@ export default {
     font-size: 30px;
 }
 .clt-type {
-    margin-right: 10px;
+    /* margin-right: 10px;
     font-size: 15px;
-    font-weight: 600;
+    font-weight: 600; */
+    
+    text-align: right;
+    font-weight: bold;
+    margin: 0 10px 5px 0;
 }
 </style>

+ 38 - 33
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/SmartRating.vue

@@ -6,38 +6,39 @@
             mutualSummary:互评
             mutualDetailSummary:互评数据
          -->
-        <div class="smart-wrap">
+        <div>
             <p class="clt-type">
                 {{ smartType.name }}
                 <Icon type="md-eye-off" :title="$t('cusMgt.rcd.anonymousSub')" v-if="smartRate.smartRateSummary.rateInfo.AnonyCandi" />
-                :
             </p>
-            <template v-if="smartType.value === 'vote'">
-                <!-- <div v-for="(item, index) in scoreListNew" :key="index" style="margin-bottom: 10px;"> -->
-                    {{ $t('answerSheet.tip2') }}{{ vote.round }}{{ $t('cusMgt.rcd.wheel') }}({{ vote.votes }}{{ $t('studentWeb.vote.tickets') }})
-                    <Icon :title="$t('cusMgt.rcd.viewCom')" type="md-chatbubbles" size="17" color="#2EC7C9" @click="openComment('vote')" style="cursor: pointer; margin-top: 3px; margin-right: 5px;" />
-                    <SmartVote :smartData="scoreListNew"></SmartVote>
-                <!-- </div> -->
-            </template>
-            <template v-else-if="smartType.value === 'score'">
-                <SmartScore :smartData="scoreListNew"></SmartScore>
-            </template>
-            <template v-else>
-                <div v-for="(item, index) in scoreListNew" :key="index" class="smart-list">
-                    <p style="height: 21px;">
-                        <Icon type="md-trophy" v-if="item.king" color="#ff880d" />
-                    </p>
-                    <p>
-                        <span style="color: #2d8cf0;">{{ item.name }}
-                            <span v-show="!item.isGeneral">({{ item.id }})</span>
-                        </span>
-                        <Icon type="md-chatbubbles" color="#2EC7C9" style="cursor: pointer;" @click="openComment('mutal', index, item)" />
-                    </p>
-                    <p>{{ $t('cusMgt.rcd.avgScore') }}:{{ item.result }}</p>
-                    <img v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'StudentWork'" :src="item.material" @click="$hevueImgPreview(item.material)" />
-                    <p v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'IRS'" v-html="item.material" class="smart-material"></p>
-                </div>
-            </template>
+            <div class="smart-wrap">
+                <template v-if="smartType.value === 'vote'">
+                    <!-- <div v-for="(item, index) in scoreListNew" :key="index" style="margin-bottom: 10px;"> -->
+                        {{ $t('answerSheet.tip2') }}{{ vote.round }}{{ $t('cusMgt.rcd.wheel') }}({{ vote.votes }}{{ $t('studentWeb.vote.tickets') }})
+                        <Icon :title="$t('cusMgt.rcd.viewCom')" type="md-chatbubbles" size="17" color="#2EC7C9" @click="openComment('vote')" style="cursor: pointer; margin-top: 3px; margin-right: 5px;" />
+                        <SmartVote :smartData="scoreListNew"></SmartVote>
+                    <!-- </div> -->
+                </template>
+                <template v-else-if="smartType.value === 'score'">
+                    <SmartScore :smartData="scoreListNew"></SmartScore>
+                </template>
+                <template v-else>
+                    <div v-for="(item, index) in scoreListNew" :key="index" class="smart-list">
+                        <p style="height: 21px;">
+                            <Icon type="md-trophy" v-if="item.king" color="#ff880d" />
+                        </p>
+                        <p>
+                            <span style="color: #2d8cf0;">{{ item.name }}
+                                <span v-show="!item.isGeneral">({{ item.id }})</span>
+                            </span>
+                            <Icon type="md-chatbubbles" color="#2EC7C9" style="cursor: pointer;" @click="openComment('mutal', index, item)" />
+                        </p>
+                        <p>{{ $t('cusMgt.rcd.avgScore') }}:{{ item.result }}</p>
+                        <img v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'StudentWork'" :src="item.material" @click="$hevueImgPreview(item.material)" />
+                        <p v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'IRS'" v-html="item.material" class="smart-material"></p>
+                    </div>
+                </template>
+            </div>
         </div>
         <StudentClient></StudentClient>
         <Modal v-model="isComment" :title="$t('homework.table.comment')" :footer-hide="true">
@@ -257,17 +258,21 @@ export default {
 </script>
 
 <style lang="less" scoped>
+.clt-type {
+    /* margin-right: 10px;
+    font-size: 15px;
+    font-weight: 600; */
+    
+    text-align: right;
+    font-weight: bold;
+    margin: 0 10px 5px 0;
+}
 .smart-wrap {
     display: flex;
     flex-wrap: wrap;
     justify-content: end;
     position: relative;
 
-    .clt-type {
-        margin-right: 10px;
-        font-size: 15px;
-        font-weight: 600;
-    }
 
     .smart-vote {
         width: 200px;

+ 135 - 0
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/SubjectiveAns.vue

@@ -0,0 +1,135 @@
+<template>
+    <div class="text-answer-item">
+        <div v-for="(student) in fullData" :key="student.seatID" @click="handleShowContent(student)">
+            <span class="student-name">{{ student.name }}:</span>
+            <div v-for="(item, index) in student.answer" :key="index">
+                <!-- 文本、画记 -->
+                <template v-if="answerType === 'text' || answerType === 'text_Image'">
+                    <div v-html="item" class="popques-svg"></div>
+                </template>
+                <template v-else>
+                    <div v-if="item.type === 'image'" class="popques-img">
+                        <img :src="item.url" alt="" @click="viewImage(item.url)">
+                    </div>
+                    <div v-if="item.type === 'audio'">
+                        <audio controls>
+                            <source :src="item.url">
+                            {{$t('teachContent.notAudio')}}
+                        </audio>
+                    </div>
+                    <div v-if="item.type === 'video'">
+                        <video :src="item.url" width="870" controls="controls" style="max-height: 800px;"></video>
+                    </div>
+                </template>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import Video from '../../video/Video.vue'
+export default {
+    components: { Video },
+    props: {
+        recordInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        answer: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        students: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        answerType: {
+            type: String,
+            default: '',
+        },
+        blobInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+    },
+    computed: {
+        fullData() {
+            if (this.answer && this.answer.length) {
+                if (this.students && this.students.length) {
+                    let students = this._.cloneDeep(this.students)
+                    students.forEach((student, index) => {
+                        if(this.answerType != 'text' && this.answerType != 'text_Image') {
+                            student.answer = this.answer[index].map(item => {
+                                // 示例: "/Clients/202106001/Ans/1-1709794642933001.png"
+                                let url = {
+                                    type: '',
+                                    url: `${this.blobInfo.blob_uri}/records/${this.recordInfo.id}${item}${this.blobInfo.blob_sas}`
+                                }
+                                if(this.answerType === 'file') {
+                                    let suffix = item.substr(item.lastIndexOf(".") + 1)
+                                    let isImg = ['jpg', 'png', 'gif'].includes(suffix)
+                                    let isDoc = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf'].includes(suffix)
+                                    let isPdf = suffix === 'pdf'
+                                    let isVideo = suffix === 'mp4' || suffix === 'webm'
+                                    let isAudio = ['mp3', 'wav'].includes(suffix)
+                                    url.type = isImg ? 'image' : isVideo ? 'video' : isAudio ? 'audio' : (isDoc ? 'doc' : 'other')
+                                } else {
+                                    url.type = this.answerType
+                                }
+                                return url
+                            })
+                        } else {
+                            student.answer = this.answer[index]
+                        }
+                    });
+                    students = students.filter(s => !!s.answer?.length)
+                    console.log('完整数据', students)
+                    return students
+                }
+            } else {
+                return []
+            }
+        }
+    },
+    methods: {
+        viewImage(url) {
+            this.$hevueImgPreview(url)
+        },
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.text-answer-item {
+    display: flex;
+    flex-wrap: wrap;
+
+    &>div {
+        margin-right: 20px;
+        display: flex;
+
+        .student-name {
+            margin-right: 5px;
+        }
+    }
+}
+.popques-img img{
+    cursor: pointer;
+    width: 100px !important;
+    height: 100px !important;
+}
+</style>
+<style lang="less">
+.popques-svg svg{
+    width: 200px;
+    height: auto;
+}
+</style>

+ 53 - 40
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/WrkCmp.vue

@@ -1,46 +1,50 @@
 <template>
     <!-- 作品收集 -->
     <div class="receive-wrap">
-        <p class="clt-type">
-            {{$t('cusMgt.rcd.wrkCmp')}}:
-        </p>
-        <div v-for="(item) in cmpDataList" :key="item.url" class="receive-item">
-            <div v-if="collateType == 0">
-                {{$t('cusMgt.rcd.evt5')}}
-            </div>
-            <!-- 图片类型作品 -->
-            <div v-else-if="collateType == 1">
-                <img class="receive-img" :src="item.url" alt="" @click="viewImage(item.url)">
-            </div>
-            <!-- HTEX作品类型 -->
-            <div v-else-if="collateType == 2">
-                <!-- 收集的HTEX作品 -->
-            </div>
-            <!-- 音频 -->
-            <div v-else-if="collateType == 3">
-                <div class="audio-box" @click="viewAudio(item.url)">
-                    <Icon class="collate-type-icon" custom="iconfont icon-audio-outline" />
-                </div>
-            </div>
-            <!-- 视频 -->
-            <div v-else-if="collateType == 4">
-                <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="viewAudio(item.url)">
-                    <Icon class="collate-type-icon" custom="iconfont icon-video-outline" />
-                </div>
-            </div>
-            <!-- 文字 -->
-            <div v-else-if="collateType == 5">
-                收集的文字
-            </div>
-            <!-- 附件 -->
-            <div v-else-if="collateType == 6">
-                <div class="audio-box" @click="downloadFile(item.url,item)">
-                    <Icon class="collate-type-icon" custom="iconfont icon-file" />
+        <div>
+            <p class="clt-type">
+                {{$t('cusMgt.rcd.wrkCmp')}}:
+            </p>
+            <div class="receive-box">
+                <div v-for="(item) in cmpDataList" :key="item.url" class="receive-item">
+                    <div v-if="collateType == 0">
+                        {{$t('cusMgt.rcd.evt5')}}
+                    </div>
+                    <!-- 图片类型作品 -->
+                    <div v-else-if="collateType == 1">
+                        <img class="receive-img" :src="item.url" alt="" @click="viewImage(item.url)">
+                    </div>
+                    <!-- HTEX作品类型 -->
+                    <div v-else-if="collateType == 2">
+                        <!-- 收集的HTEX作品 -->
+                    </div>
+                    <!-- 音频 -->
+                    <div v-else-if="collateType == 3">
+                        <div class="audio-box" @click="viewAudio(item.url)">
+                            <Icon class="collate-type-icon" custom="iconfont icon-audio-outline" />
+                        </div>
+                    </div>
+                    <!-- 视频 -->
+                    <div v-else-if="collateType == 4">
+                        <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="viewAudio(item.url)">
+                            <Icon class="collate-type-icon" custom="iconfont icon-video-outline" />
+                        </div>
+                    </div>
+                    <!-- 文字 -->
+                    <div v-else-if="collateType == 5">
+                        收集的文字
+                    </div>
+                    <!-- 附件 -->
+                    <div v-else-if="collateType == 6">
+                        <div class="audio-box" @click="downloadFile(item.url,item)">
+                            <Icon class="collate-type-icon" custom="iconfont icon-file" />
+                        </div>
+                    </div>
+                    <p class="name-text">
+                        {{ item.isGroupItem ? item.groupID : item.sName}}
+                    </p>
                 </div>
             </div>
-            <p class="name-text">
-                {{ item.isGroupItem ? item.groupID : item.sName}}
-            </p>
         </div>
         <StudentClient></StudentClient>
         <!--文件预览-->
@@ -223,6 +227,11 @@ export default {
 .receive-wrap {
     display: flex;
 }
+.receive-box {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-end;
+}
 .receive-item {
     margin-right: 20px;
 }
@@ -253,8 +262,12 @@ export default {
     font-size: 30px;
 }
 .clt-type {
-    margin-right: 10px;
+    /* margin-right: 10px;
     font-size: 15px;
-    font-weight: 600;
+    font-weight: 600; */
+    
+    text-align: right;
+    font-weight: bold;
+    margin: 0 10px 5px 0;
 }
 </style>