Bladeren bron

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

zhouj1203@hotmail.com 1 jaar geleden
bovenliggende
commit
9c270b7203

+ 2 - 2
TEAMModelOS.SDK/Models/Cosmos/School/Elegant.cs

@@ -49,12 +49,12 @@ namespace TEAMModelOS.SDK.Models
      
         public List<Attachment> attachments { get; set; } = new List<Attachment>();
         public string target { get; set; }
-      
+
         /// <summary>
         /// 业务类型。elegant 德育, art 艺术,弃用
         /// </summary>
         // public List<string> bizCode { get; set; }
-        public List<string> bizType { get; set; }
+        public List<string> bizType { get; set; } = new List<string>();
         /// <summary>
         /// image  video
         /// </summary>

+ 70 - 0
TEAMModelOS.TEST/Program.cs

@@ -17,6 +17,76 @@ namespace TEAMModelOS.TEST
             //var jsonAuth = System.IO.File.ReadAllText("C:\\Users\\CrazyIter\\Downloads\\492266088181141504\\ActivityInfo.json", Encoding.UTF8);
             //var jsonData = jsonAuth.ToObject<LessonRecordActivityInfo>();
             string path = "C:\\Users\\CrazyIter\\Downloads\\消费清单(2022-2023)\\bill";
+            List<List<string>> inputArray = new List<List<string>>
+            {  
+                new List<string> { "2", "11" },
+                new List<string> { "1", "22" },
+                new List<string> { "1", "11", "111" },
+                new List<string> { "2", "22", "222" },
+                new List<string> { "1", "11" },
+                new List<string> { "1", "11" },
+                new List<string> { "1" },
+                new List<string> { "2", "22", "222" },
+                new List<string> { "2", "22" }  ,
+            };
+
+            // 转换为层级结构
+            List<ClassifiedItem> result = ClassifyHierarchy(inputArray);
+
+            // 输出结果
+            foreach (var item in result)
+            {
+                Console.WriteLine($"id: {item.Id}, count: {item.Count}, pid: {item.Pid}");
+            }
+        }
+        static List<ClassifiedItem> ClassifyHierarchy(List<List<string>> inputArray)
+        {
+            Dictionary<string, int> hierarchyCount = new Dictionary<string, int>();
+            List<ClassifiedItem> result = new List<ClassifiedItem>();
+
+            foreach (var list in inputArray)
+            {
+                for (int i = 0; i < list.Count; i++)
+                {
+                    string currentId = list[i];
+                    string parentId = (i > 0) ? list[i - 1] : null;
+
+                    string hierarchyKey = $"{currentId}|{parentId}";
+
+                    if (hierarchyCount.ContainsKey(hierarchyKey))
+                    {
+                        hierarchyCount[hierarchyKey]++;
+                    }
+                    else
+                    {
+                        hierarchyCount[hierarchyKey] = 1;
+                    }
+                    var item = result.Find(item => item.Id == currentId && item.Pid == parentId);
+                    if (item== null  )
+                    {
+                        result.Add(new ClassifiedItem
+                        {
+                            Id = currentId,
+                            Pid = parentId,
+                            Count = 0
+                        });
+                    }
+                }
+            }
+
+            foreach (var item in result)
+            {
+                string hierarchyKey = $"{item.Id}|{item.Pid}";
+                item.Count = hierarchyCount.ContainsKey(hierarchyKey) ? hierarchyCount[hierarchyKey] : 0;
+            }
+
+            return result;
+        }
+        class ClassifiedItem
+        {
+            public string Id { get; set; }
+            public int Count { get; set; }
+            public string Pid { get; set; }
         }
     }
 

+ 0 - 0
TEAMModelOS/ClientApp/src/view/elegant/BaseElegantCloud.vue


+ 219 - 0
TEAMModelOS/ClientApp/src/view/elegant/BaseElegantDash.vue

@@ -0,0 +1,219 @@
+<template>
+	<div class="elegant-dash-container">
+		<!-- <div class="title">
+			<span>数据总览</span>
+			<span class="btn-details" @click="goDetails">数据详情 ></span>
+		</div> -->
+		<div class="count-wrap">
+			<div class="count-item">
+				<p class="label">全部活动数</p>
+				<p class="value">41</p>
+                <img src="../../assets/source/folder.png">
+			</div>
+			<div class="count-item">
+				<p class="label">德育风采数</p>
+				<p class="value">23</p>
+                <img src="../../assets/mark/5.png" style="width: 70px">
+			</div>
+			<div class="count-item">
+				<p class="label">艺术特色数</p>
+				<p class="value">18</p>
+                <img src="../../assets/mark/7.png" style="width: 70px">
+			</div>
+			<div class="count-item">
+				<p class="label">图片总数</p>
+				<p class="value">42</p>
+                <img src="../../assets/source/image.png">
+			</div>
+			<div class="count-item">
+				<p class="label">视频总数</p>
+				<p class="value">18</p>
+                <img src="../../assets/source/video.png">
+			</div>
+			<div class="count-item">
+				<p class="label">文档总数</p>
+				<p class="value">48</p>
+                <img src="../../assets/source/pdf.png">
+			</div>
+		</div>
+		<div class="chart-wrap">
+			<div class="chart-block">
+				<div class="chart-title">
+					<p class="title">艺术特色素材类型</p>
+				</div>
+				<div class="chart-content">
+					<BasePie echartsId="pie1"></BasePie>
+				</div>
+			</div>
+			<div class="chart-block chart-right">
+				<div class="chart-title">
+					<p class="title">艺术特色素材类型</p>
+				</div>
+				<div class="chart-content">
+					<BaseBar echartsId="bar1"></BaseBar>
+				</div>
+			</div>
+		</div>
+		<div class="chart-wrap">
+			<div class="chart-block">
+				<div class="chart-title">
+					<p class="title">德育风采素材类型</p>
+				</div>
+				<div class="chart-content">
+					<BasePie echartsId="pie2"></BasePie>
+				</div>
+			</div>
+			<div class="chart-block chart-right">
+				<div class="chart-title">
+					<p class="title">德育风采素材类型</p>
+				</div>
+				<div class="chart-content">
+					<BaseBar echartsId="bar2"></BaseBar>
+				</div>
+			</div>
+		</div>
+		<div class="chart-wrap">
+			<div class="chart-block" style="width: 49.5%">
+				<div class="chart-title">
+					<p class="title">德育风采文字云</p>
+				</div>
+				<div class="chart-content">
+					<!-- <BaseUploadLine></BaseUploadLine> -->
+				</div>
+			</div>
+			<div class="chart-block" style="width: 49.5%; margin-left: 1%">
+				<div class="chart-title">
+					<p class="title">艺术特色文字云</p>
+				</div>
+				<div class="chart-content">
+					<!-- <BaseUploadLine></BaseUploadLine> -->
+				</div>
+			</div>
+		</div>
+		<div class="chart-wrap">
+			<div class="chart-block" style="width: 100%">
+				<div class="chart-title">
+					<p class="title">素材上传趋势图</p>
+				</div>
+				<div class="chart-content">
+					<BaseUploadLine></BaseUploadLine>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+	import BasePie from "./BaseTypePie.vue";
+	import BaseBar from "./BaseTypeBar.vue";
+	import BaseUploadLine from "./BaseUploadLine.vue";
+	export default {
+		components: {
+			BasePie,
+			BaseBar,
+			BaseUploadLine
+		},
+		methods: {
+			goDetails() {
+				this.$emit("goDetails");
+			}
+		}
+	};
+</script>
+
+<style lang="less">
+	.elegant-dash-container {
+		// padding: 30px 50px;
+		// background: #eeeef0;
+		// .title {
+		// 	font-size: 26px;
+		// 	margin-left: 3px;
+		// 	margin-bottom: 20px;
+		// 	display: flex;
+		// 	align-items: center;
+
+		// 	.btn-details {
+		// 		display: inline-block;
+		// 		font-size: 12px;
+		// 		border: 1px solid #2787e0;
+		// 		padding: 5px 10px;
+		// 		border-radius: 50px;
+		// 		margin-left: 10px;
+		// 		color: #0d78be;
+		// 		cursor: pointer;
+		// 	}
+		// }
+		.count-wrap {
+			display: flex;
+			justify-content: space-between;
+			.count-item {
+				width: 15.8%;
+				background-color: #fff;
+				border-radius: 10px;
+				display: flex;
+				flex-direction: column;
+				justify-content: space-between;
+				padding: 20px;
+                position: relative;
+				.label {
+					font-size: 14px;
+					color: #818181;
+					margin-bottom: 20px;
+				}
+				.value {
+					font-size: 28px;
+					font-weight: bold;
+					color: #0d78be;
+					letter-spacing: 1px;
+				}
+                img{
+                    width: 60px;
+                    position: absolute;
+                    bottom: 20px;
+                    right: 20px;
+                    opacity: .3;
+                }
+			}
+		}
+		.chart-wrap {
+			display: flex;
+			margin: 1% 0;
+
+			.chart-block {
+				width: 28%;
+				height: 440px;
+				background-color: #fff;
+				border-radius: 10px;
+				display: flex;
+				flex-direction: column;
+				justify-content: space-between;
+				padding: 20px;
+				padding-bottom: 30px;
+
+				.title {
+					font-size: 14px;
+                    display: flex;
+                    align-items: center;
+					&::before {
+						content: "";
+						display: inline-block;
+						border: 3px solid #0d78be;
+						border-radius: 4px;
+                        height: 14px;
+						margin-right: 10px;
+					}
+				}
+
+				.chart-content {
+					height: 100%;
+					width: 100%;
+				}
+			}
+
+			.chart-right {
+				width: 71%;
+				margin-left: 1%;
+			}
+		}
+	}
+</style>

+ 127 - 0
TEAMModelOS/ClientApp/src/view/elegant/BaseTypeBar.vue

@@ -0,0 +1,127 @@
+<template>
+	<div :id='echartsId' class="art-echart"></div>
+</template>
+<script>
+	export default {
+        props: ['echartsId'],
+		data() {
+			return {
+				option: null,
+				stuIndex: 0,
+				average: 0,
+				classStuArr: []
+			};
+		},
+		methods: {
+			doRender(yData) {
+				let myChart = this.$echarts.init(document.getElementById(this.echartsId));
+				let that = this;
+				var colorList = {
+					type: "linear",
+					x: 0,
+					y: 0,
+					x2: 0,
+					y2: 1,
+					colorStops: [
+						{
+							offset: 0,
+							color: "#06DBF5" // 0% 处的颜色
+						},
+						{
+							offset: 1,
+							color: "#00d386" // 100% 处的颜色
+						}
+					],
+					globalCoord: false // 缺省为 false
+				};
+				let option = {
+					backgroundColor: "#fff",
+					tooltip: {
+						trigger: "axis",
+						axisPointer: {
+							// 坐标轴指示器,坐标轴触发有效
+							type: "shadow" // 默认为直线,可选为:'line' | 'shadow'
+						}
+					},
+					grid: {
+						left: "3%",
+						right: "4%",
+						bottom: "0%",
+						containLabel: true
+					},
+					xAxis: {
+						type: "category",
+						data: that.echartsId === 'bar1' ? [ "演奏", "影视", "舞蹈", "戏剧", "常规活动", "获奖活动"] : ["读书分享","思想沙龙","文学创作","社会服务","文化交流","思德教育","反思日志","小组合作","行为习惯挑战"],
+						// axisTick: {
+						//     alignWithLabel: true
+						// }
+						axisLine: {
+							lineStyle: {
+								color: "#333"
+							}
+						},
+						axisLabel: {
+							interval: 0,
+							textStyle: {
+								fontFamily: "Hm"
+							}
+						}
+					},
+
+					yAxis: {
+						type: "value",
+						axisLine: {
+							lineStyle: {
+								color: "#333"
+							}
+						},
+						splitLine: {
+							show: true,
+							lineStyle: {
+								color: "rgba(255,255,255,0.3)"
+							}
+						},
+						axisLabel: {
+							textStyle: {
+								fontFamily: "Hm"
+							}
+						}
+					},
+
+					series: [
+						{
+							name: "",
+							type: "bar",
+							barWidth: "30",
+							itemStyle: {
+								color: function (params) {
+									return colorList;
+								}
+							},
+							emphasis: {
+								itemStyle: {
+									color: "#FBB419"
+								}
+							},
+							data: [154,132,112,62,55,41,22,32,10,112,62,55,41,22,32,10]
+						}
+					]
+				};
+				myChart.clear();
+				myChart.setOption(option);
+			}
+		},
+		mounted() {
+			this.doRender();
+		}
+	};
+</script>
+
+<style>
+	.art-echart {
+		width: 100%;
+		height: 100%;
+		margin: 0 auto;
+		display: block;
+	}
+</style>

+ 116 - 0
TEAMModelOS/ClientApp/src/view/elegant/BaseTypePie.vue

@@ -0,0 +1,116 @@
+<template>
+	<div :id='echartsId' class="art-echart"></div>
+</template>
+<script>
+	export default {
+        props: ['echartsId'],
+		data() {
+			return {
+				option: null,
+				stuIndex: 0,
+				average: 0,
+				classStuArr: []
+			};
+		},
+		methods: {
+			doRender(yData) {
+				let myChart = this.$echarts.init(document.getElementById(this.echartsId));
+				let that = this;
+				var data = [
+					{
+						value: 6,
+						name: this.echartsId === 'pie1' ? "课程活动" : "理想信念"
+					},
+					{
+						value: 3,
+						name: this.echartsId === 'pie1' ? "艺术社团" : "社会责任"
+					},
+					{
+						value: 8,
+						name: this.echartsId === 'pie1' ? "艺术活动" : "行为习惯"
+					}
+				];
+				let option = {
+					title: {
+						text: this.echartsId === 'pie1' ? "23" : "18",
+						subtext: "活动总数",
+						x: "center",
+						y: "center",
+						textStyle: {
+							fontWeight: "600",
+							fontFamily: "Hm",
+							fontSize: 28
+						},
+						subtextStyle: {
+							fontWeight: "normal",
+							fontFamily: "Hm"
+						}
+					},
+					grid: {
+						bottom: "10%",
+						top: "12%",
+						containLabel: true
+					},
+					tooltip: {
+						show: true,
+						trigger: "item",
+						formatter: "{b}: {c} ({d}%)",
+						textStyle: {
+							fontWeight: "normal",
+							fontFamily: "Hm"
+						}
+					},
+					legend: {
+						orient: "horizontal",
+						textStyle: {
+							fontWeight: "normal",
+							fontFamily: "Hm"
+						},
+						bottom: "0%",
+						data: that.echartsId === 'pie1' ? ["课程活动", "艺术社团", "艺术活动"] : ["理想信念","社会责任","行为习惯"]
+					},
+					series: [
+						{
+							type: "pie",
+							selectedMode: "single",
+							radius: ["38%", "65%"],
+							color: ["#86D560", "#AF89D6", "#59ADF3"],
+							label: {
+								normal: {
+									position: "inner",
+									formatter: "{d}%",
+									textStyle: {
+										color: "#fff",
+										fontWeight: "bold",
+										fontFamily: "Hm",
+										fontSize: 12
+									}
+								}
+							},
+							labelLine: {
+								normal: {
+									show: false
+								}
+							},
+							data: data
+						}
+					]
+				};
+				myChart.clear();
+				myChart.setOption(option);
+			}
+		},
+		mounted() {
+			this.doRender();
+		}
+	};
+</script>
+
+<style>
+	.art-echart {
+		width: 100%;
+		height: 100%;
+		margin: 0 auto;
+		display: block;
+	}
+</style>

+ 216 - 0
TEAMModelOS/ClientApp/src/view/elegant/BaseUploadLine.vue

@@ -0,0 +1,216 @@
+<template>
+	<div id="BaseUploadLine" class="art-echart"></div>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				option: null,
+				stuIndex: 0,
+				average: 0,
+				classStuArr: []
+			};
+		},
+		methods: {
+			doRender(yData) {
+				let myChart = this.$echarts.init(document.getElementById("BaseUploadLine"));
+
+				let color = ["#8B5CFF", "#00CA69"];
+
+				let xAxisData = ["1", "2", "3", "4", "5", "6", "7", "8"];
+				let yAxisData1 = [100, 138, 350, 173, 180, 150, 180, 230];
+				let yAxisData2 = [233, 233, 200, 180, 199, 233, 210, 180];
+
+				const hexToRgba = (hex, opacity) => {
+					let rgbaColor = "";
+					let reg = /^#[\da-f]{6}$/i;
+					if (reg.test(hex)) {
+						rgbaColor = `rgba(${parseInt("0x" + hex.slice(1, 3))},${parseInt("0x" + hex.slice(3, 5))},${parseInt("0x" + hex.slice(5, 7))},${opacity})`;
+					}
+					return rgbaColor;
+				};
+				let option = {
+					color: color,
+					legend: {
+						top: 20
+					},
+					tooltip: {
+						trigger: "axis",
+						formatter: function (params) {
+							let html = "";
+							params.forEach((v) => {
+								html += `<div style="color: #666;font-size: 14px;line-height: 24px">
+	                <span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${color[v.componentIndex]};"></span>
+	                ${v.seriesName}2020.${v.name}
+	                <span style="color:${color[v.componentIndex]};font-weight:700;font-size: 18px;margin-left:5px">${v.value}</span>
+	                个`;
+							});
+							return html;
+						},
+						extraCssText: "background: #fff; border-radius: 0;box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);color: #333;",
+						axisPointer: {
+							type: "shadow",
+							shadowStyle: {
+								color: "#ffffff",
+								shadowColor: "rgba(225,225,225,1)",
+								shadowBlur: 5
+							}
+						}
+					},
+					grid: {
+						left: "2%",
+						right: "2%",
+						bottom: "0",
+						containLabel: true
+					},
+					xAxis: [
+						{
+							type: "category",
+							axisLabel: {
+								margin: 20,
+								formatter: "第{value}周",
+								textStyle: {
+									color: "#333",
+									fontFamily: "Hm"
+								}
+							},
+							axisLine: {
+								lineStyle: {
+									color: "#D9D9D9"
+								}
+							},
+							data: xAxisData
+						}
+					],
+					yAxis: [
+						{
+							type: "value",
+							name: "单位(个)",
+							axisLabel: {
+								textStyle: {
+									color: "#666"
+								}
+							},
+							nameTextStyle: {
+								color: "#666",
+								fontSize: 12,
+								lineHeight: 40
+							},
+							// 分割线
+							splitLine: {
+								lineStyle: {
+									type: "dashed",
+									color: "#E9E9E9"
+								}
+							},
+							axisLine: {
+								show: false
+							},
+							axisTick: {
+								show: false
+							}
+						}
+					],
+					series: [
+						{
+							// name: "2018",
+							name: "德育风采",
+							type: "line",
+							smooth: true,
+							symbolSize: 8,
+							zlevel: 3,
+							lineStyle: {
+								normal: {
+									color: color[0],
+									shadowBlur: 3,
+									shadowColor: hexToRgba(color[0], 0.5),
+									shadowOffsetY: 8
+								}
+							},
+							symbol: "circle", //数据交叉点样式
+							areaStyle: {
+								normal: {
+									color: new echarts.graphic.LinearGradient(
+										0,
+										0,
+										0,
+										1,
+										[
+											{
+												offset: 0,
+												color: hexToRgba(color[0], 0.3)
+											},
+											{
+												offset: 1,
+												color: hexToRgba(color[0], 0.1)
+											}
+										],
+										false
+									),
+									shadowColor: hexToRgba(color[0], 0.1),
+									shadowBlur: 10
+								}
+							},
+							data: yAxisData1
+						},
+						{
+							name: "艺术特色",
+							type: "line",
+							smooth: true,
+							symbolSize: 8,
+							zlevel: 3,
+							lineStyle: {
+								normal: {
+									color: color[1],
+									shadowBlur: 3,
+									shadowColor: hexToRgba(color[1], 0.5),
+									shadowOffsetY: 8
+								}
+							},
+							symbol: "circle", //数据交叉点样式 (实心点)
+							areaStyle: {
+								normal: {
+									color: new echarts.graphic.LinearGradient(
+										0,
+										0,
+										0,
+										1,
+										[
+											{
+												offset: 0,
+												color: hexToRgba(color[1], 0.3)
+											},
+											{
+												offset: 1,
+												color: hexToRgba(color[1], 0.1)
+											}
+										],
+										false
+									),
+									shadowColor: hexToRgba(color[1], 0.1),
+									shadowBlur: 10
+								}
+							},
+							data: yAxisData2
+						}
+					]
+				};
+
+				myChart.clear();
+				myChart.setOption(option);
+			}
+		},
+		mounted() {
+			this.doRender();
+		}
+	};
+</script>
+
+<style>
+	.art-echart {
+		width: 100%;
+		height: 100%;
+		margin: 0 auto;
+		display: block;
+	}
+</style>

+ 128 - 91
TEAMModelOS/ClientApp/src/view/elegant/Elegant.vue

@@ -1,61 +1,70 @@
 <template>
 	<div class="elegant-container">
 		<Spin fix v-if="isLoading"></Spin>
-		<div class="header">
-			<span class="title">学生风采</span>
-			<div>
-				<Button type="success" size="small" @click="onDownloadExcel" icon="md-download" style="margin-right: 10px">导出数据</Button>
-				<Button type="success" size="small" @click="onAddElegant">+ 添加素材</Button>
+		<div class="elegant-details-box">
+			<div class="container-title">
+				<div style="display: flex; align-items: center">
+					<span>{{isShowDash ? '数据总览' : '数据详情'}}</span>
+					<span class="btn-details" @click="isShowDash = !isShowDash">{{!isShowDash ? '数据总览' : '数据详情'}} ></span>
+				</div>
+				<div class="tools">
+					<Button shape="circle" @click="onAddElegant" icon="md-add">添加素材</Button>
+					<Button shape="circle" @click="onDownloadExcel" icon="md-download" style="margin-left: 10px">导出数据</Button>
+				</div>
+			</div>
+			<ElegantDash v-if="isShowDash" @goDetails="isShowDash = false"></ElegantDash>
+			<div class="details-wrap" v-if="!isShowDash">
+				<div class="filter-wrap">
+					<!-- 类型筛选 -->
+					<Select v-model="filterTypeIndex" style="width: 200px" @on-change="onFilterTypeChange">
+						<Option v-for="(item, index) in typeList" :value="index" :key="index">
+							{{ item }}
+						</Option>
+					</Select>
+				</div>
+				<div class="elegant-list">
+					<EmptyData :top="300" v-if="!elegantList.length"></EmptyData>
+					<Timeline v-else>
+						<TimelineItem v-for="(item, index) in elegantList" :key="index">
+							<p class="elegant-title">
+								<span>{{ item.title }}</span>
+								<span style="margin: 0 10px">
+									<Tag color="geekblue" v-if="item.bizType">{{ item.bizType.join("-") }}</Tag>
+									<Tag color="green" v-if="item.classes && item.classes.length">{{ getClassNameById(item.classes[0]) }}</Tag>
+									<Tag color="primary">{{ $tools.formatTime(item.createTime) }}</Tag>
+								</span>
+								<!-- <Icon type="md-create" color="#2d8cf0" @click="onEditElegant(item,index)"></Icon> -->
+								<Icon type="md-trash" color="#2d8cf0" @click="onDelElegant(item, index)"></Icon>
+							</p>
+							<p class="elegant-content">{{ item.content }}</p>
+							<div class="img-list" v-if="item.fileType === 'image'">
+								<div class="img-item" v-for="(img, imgIndex) in item.attachments" :key="imgIndex" @click="onImgClick(item.attachments, imgIndex)">
+									<img :src="getFullPath(img.url)" alt="" />
+								</div>
+							</div>
+							<div class="img-list" v-if="item.fileType === 'video'">
+								<div class="img-item" v-for="(img, imgIndex) in item.attachments" :key="imgIndex">
+									<video :src="getFullPath(img.url)" style="width: 300px" controls="controls"></video>
+								</div>
+							</div>
+							<div class="file-list" v-if="item.fileType === 'doc'">
+								<div class="file-item" v-for="(item, imgIndex) in item.attachments" :key="imgIndex" @click="onPreview(item)">
+									<img src="../../assets/source/word.png" v-if="item.type === 'doc' && docType.includes(getSuffix(item.blob))" />
+									<img src="../../assets/source/excel.png" v-else-if="item.type === 'doc' && excelType.includes(getSuffix(item.name))" />
+									<img src="../../assets/source/ppt.png" v-else-if="item.type === 'doc' && pptType.includes(getSuffix(item.name))" />
+									<img src="../../assets/source/pdf.png" v-else-if="item.type === 'doc' && getSuffix(item.name) === 'pdf'" />
+									<img src="../../assets/source/zip.png" v-else-if="getSuffix(item.name) === 'zip' || getSuffix(item.name) === 'rar'" />
+									<img src="../../assets/source/audio.png" v-else-if="item.type === 'audio'" />
+									<img src="../../assets/source/unknow.png" v-else />
+									<p>{{ item.name }}</p>
+								</div>
+							</div>
+						</TimelineItem>
+					</Timeline>
+				</div>
 			</div>
 		</div>
-		<div class="filter-wrap">
-			<!-- 类型筛选 -->
-			<Select v-model="filterTypeIndex" style="width:200px;"  @on-change="onFilterTypeChange">
-				<Option v-for="(item,index) in typeList" :value="index" :key="index">
-					{{ item }}
-				</Option>
-			</Select>
-		</div>
-		<div class="elegant-list">
-			<EmptyData :top="300" v-if="!elegantList.length"></EmptyData>
-			<Timeline v-else>
-				<TimelineItem v-for="(item, index) in elegantList" :key="index">
-					<p class="elegant-title">
-						<span>{{ item.title }}</span>
-						<span style="margin: 0 10px">
-							<Tag color="geekblue" v-if="item.bizType">{{ item.bizType.join("-") }}</Tag>
-							<Tag color="green" v-if="item.classes && item.classes.length">{{ getClassNameById(item.classes[0]) }}</Tag>
-							<Tag color="primary">{{ $tools.formatTime(item.createTime) }}</Tag>
-						</span>
-						<!-- <Icon type="md-create" color="#2d8cf0" @click="onEditElegant(item,index)"></Icon> -->
-						<Icon type="md-trash" color="#2d8cf0" @click="onDelElegant(item, index)"></Icon>
-					</p>
-					<p class="elegant-content">{{ item.content }}</p>
-					<div class="img-list" v-if="item.fileType === 'image'">
-						<div class="img-item" v-for="(img, imgIndex) in item.attachments" :key="imgIndex" @click="onImgClick(item.attachments, imgIndex)">
-							<img :src="getFullPath(img.url)" alt="" />
-						</div>
-					</div>
-					<div class="img-list" v-if="item.fileType === 'video'">
-						<div class="img-item" v-for="(img, imgIndex) in item.attachments" :key="imgIndex">
-							<video :src="getFullPath(img.url)" style="width: 300px" controls="controls"></video>
-						</div>
-					</div>
-					<div class="file-list" v-if="item.fileType === 'doc'">
-						<div class="file-item" v-for="(item, imgIndex) in item.attachments" :key="imgIndex" @click="onPreview(item)">
-							<img src="../../assets/source/word.png" v-if="item.type === 'doc' && docType.includes(getSuffix(item.blob))" />
-							<img src="../../assets/source/excel.png" v-else-if="item.type === 'doc' && excelType.includes(getSuffix(item.name))" />
-							<img src="../../assets/source/ppt.png" v-else-if="item.type === 'doc' && pptType.includes(getSuffix(item.name))" />
-							<img src="../../assets/source/pdf.png" v-else-if="item.type === 'doc' && getSuffix(item.name) === 'pdf'" />
-							<img src="../../assets/source/zip.png" v-else-if="getSuffix(item.name) === 'zip' || getSuffix(item.name) === 'rar'" />
-							<img src="../../assets/source/audio.png" v-else-if="item.type === 'audio'" />
-							<img src="../../assets/source/unknow.png" v-else />
-							<p>{{ item.name }}</p>
-						</div>
-					</div>
-				</TimelineItem>
-			</Timeline>
-		</div>
+
 		<Drawer :title="isEdit ? '编辑素材' : '添加素材'" :closable="false" v-model="editModal" width="520" :transfer="false" @on-visible-change="onDrawerChange">
 			<Form :model="curElegantItem" label-position="top" v-if="curElegantItem">
 				<FormItem :label="`活动名称`">
@@ -154,13 +163,18 @@
 <script>
 	import BlobTool from "@/utils/blobTool.js";
 	import excel from "@/utils/excel.js";
+	import ElegantDash from "./BaseElegantDash.vue";
 	export default {
+		components: {
+			ElegantDash
+		},
 		data() {
 			return {
-				isLoading:false,
-				allList:[],
-				typeList:['全部风采','德育风采','艺术特色'],
-				filterTypeIndex:0,
+				isShowDash: true,
+				isLoading: false,
+				allList: [],
+				typeList: ["全部风采", "德育风采", "艺术特色"],
+				filterTypeIndex: 0,
 				props: {
 					multiple: false,
 					value: "id",
@@ -177,7 +191,7 @@
 				btnLoading: false,
 				fileArr: [],
 				priviewSrc: "",
-				originList:[],
+				originList: [],
 				imgFilePreviewList: [],
 				videoFilePreviewList: [],
 				docFilePreviewList: [],
@@ -254,27 +268,27 @@
 					etime: null //可选
 				})
 				.then((res) => {
-					this.originList = res.elegants.reverse()
+					this.originList = res.elegants.reverse();
 					this.onFilterTypeChange();
 					this.getTargetList();
 				});
 		},
 		methods: {
-			onDownloadExcel(){
-				this.isLoading = true
+			onDownloadExcel() {
+				this.isLoading = true;
 				let schoolName = this.$store.state.user.schoolProfile.school_base.name;
 				let periodName = this.curPeriod.name;
-				let list = this.originList.map(i => {
+				let list = this.originList.map((i) => {
 					return {
 						schoolName: schoolName,
 						className: this.getClassNameById(i.classes[0]),
 						classId: i.classes[0],
-						bizType: i.bizType ? i.bizType.join('-') : '-', 
-						title:i.title,
-						content:i.content,
+						bizType: i.bizType ? i.bizType.join("-") : "-",
+						title: i.title,
+						content: i.content,
 						time: this.$tools.formatTime(i.createTime)
-					}
-				})
+					};
+				});
 				const params = {
 					title: ["学校名称", "班级名称", "班级ID", "素材类别", "活动名称", "活动描述", "活动时间"],
 					key: ["schoolName", "className", "classId", "bizType", "title", "content", "time"],
@@ -285,9 +299,9 @@
 				excel.export_array_to_excel(params);
 				this.isLoading = false;
 			},
-			onFilterTypeChange(){
-				console.log(this.allList)
-				this.elegantList = this.filterTypeIndex === 0 ? this._.cloneDeep(this.originList) : this.originList.filter(i => i.bizType && i.bizType.includes(this.typeList[this.filterTypeIndex]))
+			onFilterTypeChange() {
+				console.log(this.allList);
+				this.elegantList = this.filterTypeIndex === 0 ? this._.cloneDeep(this.originList) : this.originList.filter((i) => i.bizType && i.bizType.includes(this.typeList[this.filterTypeIndex]));
 			},
 			onPreview(file) {
 				let fullLink = this.getFullPath(file.url);
@@ -317,7 +331,7 @@
 			},
 			getTargetList() {
 				if (!this.curPeriod?.id) return;
-				this.isLoading = true
+				this.isLoading = true;
 				let params = {
 					tmdid: this.$store.state.userInfo.TEAMModelId,
 					schoolId: this.$store.state.userInfo.schoolCode,
@@ -327,11 +341,11 @@
 				this.$api.common.getActivityTarget(params).then(
 					(res) => {
 						this.allList = res.groupLists;
-						this.isLoading = false
+						this.isLoading = false;
 					},
 					(err) => {
 						this.$Messag.error(this.$t("ae.ae15"));
-						this.isLoading = false
+						this.isLoading = false;
 					}
 				);
 			},
@@ -535,10 +549,10 @@
 			}
 		},
 		computed: {
-			getClassNameById(){
-				return id => {
-					return this.allList.length ? this.allList.find(i => i.id === id)?.name : ''
-				}
+			getClassNameById() {
+				return (id) => {
+					return this.allList.length ? this.allList.find((i) => i.id === id)?.name : "";
+				};
 			},
 			getSuffix() {
 				return (name) => {
@@ -615,27 +629,50 @@
 			"$store.state.user.curSemester": {
 				deep: true,
 				handler(n, old) {
-          			this.getTargetList()
+					this.getTargetList();
 				}
 			}
-		},	
+		}
 	};
 </script>
 
 <style lang="less">
 	.elegant-container {
 		height: 100%;
-		.header {
-			width: 100%;
-			display: flex;
-			justify-content: space-between;
-			align-items: center;
-			padding: 10px 15px;
-			border-bottom: 1px solid rgba(204, 204, 204, 0.377);
-			font-size: 16px;
+		background: #eeeef0;
 
-			.title {
-				font-weight: bold;
+		.elegant-details-box {
+			padding: 30px 50px;
+			background: #eeeef0;
+
+			.filter-wrap {
+				padding: 0 0 40px 0;
+			}
+
+			.container-title {
+				font-size: 26px;
+				margin-left: 3px;
+				margin-bottom: 20px;
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+
+				.btn-details {
+					display: inline-block;
+					font-size: 12px;
+					border: 1px solid #2787e0;
+					padding: 5px 10px;
+					border-radius: 50px;
+					margin-left: 10px;
+					color: #0d78be;
+					cursor: pointer;
+				}
+			}
+
+			.details-wrap {
+				background: #fff;
+				padding: 20px;
+				border-radius: 10px;
 			}
 		}
 
@@ -649,8 +686,8 @@
 			display: flex;
 			justify-content: space-between;
 			align-items: center;
-			padding: 15px;
 			overflow: auto;
+			background: #fff;
 
 			.ivu-timeline {
 				height: 100%;
@@ -680,7 +717,7 @@
 			}
 		}
 
-		.ivu-timeline-item{
+		.ivu-timeline-item {
 			padding-bottom: 40px !important;
 		}
 		.file-list {

+ 170 - 1
TEAMModelOS/Controllers/School/ElegantController.cs

@@ -5,6 +5,8 @@ using System.Dynamic;
 using System.IO;
 using System.Linq;
 using System.Net;
+using System.Net.Http;
+using System.Net.Http.Json;
 using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
@@ -16,6 +18,7 @@ using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Cryptography.KeyDerivation;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
 using TEAMModelOS.Filter;
 using TEAMModelOS.Models;
 using TEAMModelOS.SDK.DI;
@@ -23,6 +26,8 @@ using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;
 using TEAMModelOS.SDK.Models.Cosmos;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
+using TEAMModelOS.SDK.Models.Dtos;
+using TEAMModelOS.SDK.Models.Service;
 namespace TEAMModelOS.Controllers
 {
     [Route("school/elegant")]
@@ -34,17 +39,181 @@ namespace TEAMModelOS.Controllers
         private readonly AzureStorageFactory _azureStorage;
         private readonly DingDing _dingDing;
         private readonly Option _option;
+        private IHttpClientFactory _httpClientFactory;
         public ElegantController(
            AzureCosmosFactory azureCosmos,
            AzureStorageFactory azureStorage,
            DingDing dingDing,
-           IOptionsSnapshot<Option> option
+           IOptionsSnapshot<Option> option,IHttpClientFactory httpClientFactory
            )
         {
             _azureCosmos = azureCosmos;
             _azureStorage = azureStorage;
             _dingDing = dingDing;
             _option = option?.Value;
+            _httpClientFactory = httpClientFactory;
+        }
+
+        /// <summary>
+        /// 新增 或 修改
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("statistics")]
+#if !DEBUG
+        [Authorize(Roles = "IES")]
+        [AuthToken(Roles = "teacher,admin,area")]
+#endif
+        public async Task<IActionResult> Statistics(JsonElement  json)
+        {
+            if (!json.TryGetProperty("scope", out JsonElement _scope))
+            {
+                return BadRequest();
+            }
+            if (!json.TryGetProperty("code", out JsonElement _code))
+            {
+                return BadRequest();
+            }
+            List<Elegant> elegants = new List<Elegant>();
+            if ($"{_scope}".Equals("area"))
+            {
+                string sql = $"select value c.id  from c where c.areaId ='{_code}'";
+                var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).GetList<string>(sql, "Base");
+                if (result.list.IsNotEmpty())
+                {
+                    string sqlE = $"select value c from c where c.school in ({string.Join(",", result.list.Select(z => $"'{z}'"))})";
+                    var resultE = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).GetList<Elegant>(sqlE);
+                    if (resultE.list.IsNotEmpty())
+                    {
+                        elegants.AddRange(resultE.list);
+                    }
+                }
+            }
+            else if ($"{_scope}".Equals("school"))
+            {
+                string sqlE = $"select value c from c ";
+                var resultE = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).GetList<Elegant>(sqlE,$"Elegant-{_code}");
+                if (resultE.list.IsNotEmpty())
+                {
+                    elegants.AddRange(resultE.list);
+                }
+            }
+            List<ClassifiedItem> items = ClassifyHierarchy(elegants);
+            string rs = string.Empty;
+            List<dynamic> comments = new List<dynamic>();
+            foreach (var es in elegants) {
+                comments.Add(new { comment = $"{es.title} {es.content}" });
+            }
+            if (comments.IsNotEmpty()) {
+                rs= comments.ToJsonString();
+            }
+            foreach (var item in items)
+            {
+                var es = elegants.FindAll(z => item.sid.Contains(z.id));
+                if (es.IsNotEmpty()) {
+                    var video = es.SelectMany(z => z.attachments).Where(a =>!string.IsNullOrWhiteSpace(a.type) &&  a.type.Equals("video"));
+                    if (video.Any()) { 
+                        item.videoCount = video.Count();
+                    }
+                    var image = es.SelectMany(z => z.attachments).Where(a => !string.IsNullOrWhiteSpace(a.type) &&a.type.Equals("image"));
+                    if (image.Any())
+                    {
+                        item.imageCount = image.Count();
+                    }
+                    var doc = es.SelectMany(z => z.attachments).Where(a => !string.IsNullOrWhiteSpace(a.type) && a.type.Equals("doc"));
+                    if (doc.Any())
+                    {
+                        item.docCount = doc.Count();
+                    }
+                    var other = es.SelectMany(z => z.attachments).Where(a => string.IsNullOrWhiteSpace(a.type) ||  (!a.type.Equals("doc") && !a.type.Equals("video") && !a.type.Equals("image")  && !a.type.Equals("doc")));
+                    if (other.Any())
+                    {
+                        item.otherCount = other.Count();
+                    }
+                }
+            }
+            List<CommentKeyCount> keyCounts = new List<CommentKeyCount>();
+            if (!string.IsNullOrWhiteSpace(rs)) {
+               var httpClient=  _httpClientFactory.CreateClient();
+                if (!httpClient.DefaultRequestHeaders.Contains("x-functions-key")) {
+                    httpClient.DefaultRequestHeaders.Add("x-functions-key", "2BcXFR_hvzG1pZjqIkaM7Dx74Hcu6m0PwwOacFpDpq44AzFuHJBRXA==");
+                }
+                string paramJson = JsonConvert.SerializeObject(new { rs = $"{rs}", fmt = "0" });
+                var content = new StringContent(paramJson, Encoding.UTF8, "application/json");
+                HttpResponseMessage httpResponse=  await httpClient.PostAsync("https://malearn.teammodel.cn/api/txtwc", content);
+                if (httpResponse.IsSuccessStatusCode) {
+                    string str = await httpResponse.Content.ReadAsStringAsync();
+                    if (str.Contains("freq"))
+                    { keyCounts= str.ToObject<List<CommentKeyCount>>(); }
+                }
+            }
+            return Ok(new { keyCounts,items= items.Select(z=>new {z.id ,z.pid,z.count,z.videoCount,z.docCount,z.imageCount,z.otherCount })});
+        }
+
+        private  List<ClassifiedItem> ClassifyHierarchy(List<Elegant> inputArray)
+        {
+            Dictionary<string, int> hierarchyCount = new Dictionary<string, int>();
+            List<ClassifiedItem> result = new List<ClassifiedItem>();
+
+            foreach (var list in inputArray)
+            {
+                if (list.bizType.IsEmpty()) {
+                    list.bizType.Add("德育风采");
+                }
+                for (int i = 0; i < list.bizType.Count; i++)
+                {
+                    string currentId = list.bizType[i];
+                    string parentId = (i > 0) ? list.bizType[i - 1] : null;
+                    string hierarchyKey = $"{currentId}|{parentId}";
+                    if (hierarchyCount.ContainsKey(hierarchyKey))
+                    {
+                        hierarchyCount[hierarchyKey]++;
+                    }
+                    else
+                    {
+                        hierarchyCount[hierarchyKey] = 1;
+                    }
+                    var item =  result.Find(item => item.id == currentId && item.pid == parentId);
+                    if (item== null)
+                    {
+                        result.Add(new ClassifiedItem
+                        {
+                            id = currentId,
+                            pid = parentId,
+                            count = 0,
+                            sid= new List<string> {list.id }
+                        });
+                    }
+                    else {
+                        item.sid.Add(list.id);
+                    }
+                }
+            }
+            foreach (var item in result)
+            {
+                string hierarchyKey = $"{item.id}|{item.pid}";
+                item.count = hierarchyCount.ContainsKey(hierarchyKey) ? hierarchyCount[hierarchyKey] : 0;
+            }
+            return result;
+        }
+        class CommentKeyCount
+        { 
+            public int id { get; set; }
+            public string word { get; set; }
+            public int freq { get; set; }
+
+        }
+        class ClassifiedItem
+        {
+            public List<string> sid { get; set; } = new List<string>();
+            public string id { get; set; }
+            public int count { get; set; }
+            public string pid { get; set; }
+            public int videoCount { get; set; }
+            public int imageCount { get; set; }
+            public int docCount { get; set; }
+            public int otherCount { get; set; }
         }
         /// <summary>
         /// 新增 或 修改