Browse Source

Merge branch 'develop5.0-tmd' of http://106.12.23.251:10000/TEAMMODEL/TEAMModelOS into develop5.0-tmd

liqk 4 years ago
parent
commit
c7f0bbd923

+ 77 - 0
TEAMModelOS.SDK/Models/Service/NotificationService.cs

@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Text;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Extension;
+
+namespace TEAMModelOS.SDK.Models.Service
+{
+    public class NotificationService
+    {
+        private readonly HttpClient _httpClient;
+        public NotificationService(HttpClient httpClient)
+        {
+            _httpClient = httpClient;
+        }
+        public    async Task<int> SendNotification(string clientID, string clientSecret, string location, string url, Notification notification) {
+            if (location.Contains("China")) {
+                location = "China";
+            }
+            else if (location.Contains("Global"))
+            {
+                location = "Global";
+            }
+            var token = await CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, location);
+            _httpClient.DefaultRequestHeaders.Add("Authorization",$"Bearer {token.AccessToken}" );
+            HttpResponseMessage responseMessage = await _httpClient.PostAsJsonAsync(url, notification);
+            if (responseMessage.StatusCode == HttpStatusCode.OK)
+            {
+                return 200;
+            }
+            else if (responseMessage.StatusCode == HttpStatusCode.Unauthorized)
+            {
+                return 401;
+            }
+            else {
+                return 500;
+            }
+        }
+    }
+
+    public class Notification {
+        /// <summary>
+        /// App name (hita) 小寫
+        /// </summary>
+        public string hubName { get; set; }
+        /// <summary>
+        /// 通知訊息種類,分為msg,info及notice。       
+        /// msg : 一般訊息,會存Redis,拿了就刪。
+        /// info : 公告訊息或給多人讀取的訊息,會存Redis,拿了不刪,只能等時間到期或透過API刪除。
+        /// notice : 屬於系統層級訊息,不存Redis,直接裸送訊息。
+        /// </summary>
+        public string type { get; set; }
+        /// <summary>
+        /// 送訊息的來源端 格式為"{服務}:{類別}: ..." 如"ies5:hbrd","ies5:hbrd:hb0901"
+        /// </summary>
+        public string from { get; set; }
+        /// <summary>
+        /// 接收對象或手機註冊的tag,ID或服務等...
+        /// </summary>
+        public List<string> to { get; set; }
+        /// <summary>
+        /// 	標題。
+        /// </summary>
+        public string label { get; set; }
+        /// <summary>
+        /// 正文。
+        /// </summary>
+        public string body { get; set; }
+        /// <summary>
+        /// 該訊息到期時間(UTC),單位為秒,且必須大於現在時間。
+        /// </summary>
+        public long expires { get; set; }
+    }
+}

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

@@ -29,6 +29,7 @@ import spaceAuth from './spaceAuth'
 import room from './room'
 import mark from './mark'
 import openMgmt from './openMgmt';
+import service from './service';
 
 export default {
     accessToken,
@@ -59,6 +60,7 @@ export default {
     room,
     mark,
     openMgmt,
+	service,
 
     // 获取登录跳转链接
     getLoginLink: function (data) {

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

@@ -0,0 +1,7 @@
+import { fetch, post } from '@/api/http'
+export default {
+	/* 获取端外通知 */
+    getNotification: function (data) {
+        return post('https://api2.teammodel.net/service/getnotification', data)
+    },
+}

+ 116 - 39
TEAMModelOS/ClientApp/src/common/BaseNotification.vue

@@ -1,78 +1,155 @@
 <template>
 	<div class="base-notification">
 		<Poptip :title="$t('utils.newNotice')" class="dark-iview-poptip">
-			<Badge :count="3">
+			<Badge :count="msgArr.length">
 				<Icon type="md-notifications" />
 			</Badge>
-			<!-- <div slot="content" class="notice-wrap">
-				<div class="notice-item">
-					<p class="item-name">青城山学校</p>
-					<p class="item-content">活动[2020下学期国文期末考]达成率已达100%</p>
-					<span class="item-time">2020-08-14 18:22</span>
+			<div slot="content" class="notice-wrap">
+				<div class="notice-item" v-for="(item,index) in msgArr" :key="index" @click="doClickMsg(item)">
+					<p class="item-name">{{ getMsgType(item) }}</p>
+					<p class="item-content">{{ getMsgContent(item) }}</p>
+					<!-- <span class="item-time">{{ item.expires }}</span> -->
 				</div>
-				<div class="notice-item">
-					<p class="item-name">成都市高新区芳草小学</p>
-					<p class="item-content">已成功加入</p>
-					<span class="item-time">2020-08-14 18:22</span>
-				</div>
-				<div class="notice-item">
-					<p class="item-name">成都市泡桐树小学</p>
-					<p class="item-content">活动[2020下学期国文期末考]达成率已达100%</p>
-					<span class="item-time">2020-08-14 18:22</span>
-				</div>
-			</div> -->
+			</div>
 		</Poptip>
 	</div>
 </template>
 
 <script>
+	export default {
+		props: {
+			msgs: {
+				type: Array,
+				default: () => []
+			}
+		},
+		data() {
+			return {
+				msgArr: [],
+				msgTypes: {
+					'request_school': '申请通知',
+					'invite_school': '邀请通知',
+				}
+			}
+		},
+		created() {
+
+		},
+		methods: {
+			getBodyJson(str) {
+				let reg = /\\/g;
+				//使用replace方法将全部匹配正则表达式的转义符替换为空
+				let replaceAfter = str.replace(reg, '').replace(/=/g, ':');
+				return replaceAfter
+			},
+			getMsgType(msg) {
+				return this.msgTypes[msg.label]
+			},
+			getMsgContent(msg) {
+				let body = JSON.parse(this.getBodyJson(msg.body))
+				console.log(body)
+				switch (msg.label) {
+					case 'request_school':
+						return `${body.tmdname}(${body.tmdid}) 申请加入 ${body.schoolname}`
+						break;
+					case 'invite_school':
+						return `${body.tmdname}(${body.tmdid}) 邀请您加入 ${body.schoolname}`
+						break;
+					case 'request-join_school':
+						return `${body.schoolname} 已同意您的加入请求`
+						break;
+					case 'invite-join_school':
+						return `${body.tmdname}(${body.tmdid}) 已接受您的加入 ${body.schoolname} 的邀请`
+						break;
+					case 'remove_school':
+						return `${body.schoolname} 已将您移除`
+						break;
+					default:
+						break;
+				}
+			},
+			doClickMsg(msg) {
+				switch (msg.label) {
+					case 'request_school':
+						this.$router.push('teachermgmt')
+						break;
+					case 'invite_school':
+						this.$router.push('settings')
+						break;
+					default:
+						break;
+				}
+			}
+		},
+		watch: {
+			msgs: {
+				handler(n, o) {
+					this.msgArr = n
+					console.log(this.msgArr)
+				}
+			}
+		}
+	}
 </script>
 
 <style lang="less">
-	.base-notification{
-		.ivu-icon{
+	.base-notification {
+		font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+
+		.ivu-icon {
 			font-size: 22px;
 			color: #d0d0d0;
 			cursor: pointer;
 		}
-		
-		.dark-iview-poptip .ivu-poptip-popper{
+
+		.dark-iview-poptip .ivu-poptip-popper {
 			padding: 0;
 			top: 40px !important;
 			z-index: 9999;
 		}
-		
-		.ivu-badge-count{
-			height: auto;
-			padding: 2px 3px;
-			line-height: 10px;
+
+		.ivu-badge-count {
+			width: 16px;
+			height: 16px;
+			display: inline-block;
+			border-radius: 50%;
+			color: white;
+			border: none;
+			font-size: 12px;
+			line-height: 16px;
+			text-align: center;
+			margin-left: 10px;
+			min-width: 16px;
+			box-shadow: none;
+			padding: 0;
 		}
-		
-		.notice-wrap{
+
+		.notice-wrap {
 			display: flex;
 			flex-direction: column;
-			
-			.notice-item{
+
+			.notice-item {
 				position: relative;
 				padding: 10px;
-				color:#d0d0d0;
+				color: #d0d0d0;
 				border-bottom: 1px solid #424242;
-				
-				&:last-child{
+				cursor: pointer;
+
+				&:last-child {
 					border: none;
 				}
-				
-				.item-name{
+
+				.item-name {
 					font-size: 16px;
 					font-weight: bold;
-					color:#fff;
+					color: #fff;
 					margin: 10px 0;
 				}
-				
-				.item-time{
+
+				.item-time {
 					margin-top: 5px;
 					font-size: 12px;
-					color:#777777;
+					color: #777777;
 					float: right;
 				}
 			}

+ 88 - 47
TEAMModelOS/ClientApp/src/components/syllabus/DragTree.vue

@@ -3,17 +3,18 @@
 		<vuescroll>
 			<el-tree :data="treeDatas" :props="defaultProps" :allow-drop="allowDrop" class="tree" node-key="id"
 				default-expand-all highlight-current @node-drop="handleDrop" @node-click="onNodeClick"
-				:draggable="editable" :expand-on-click-node="false">
+				:draggable="editable" :expand-on-click-node="false" ref="tree">
 				<span class="custom-tree-node" slot-scope="{ node, data }">
 					<span class="tree-node-lable">
-						{{data.title}}{{ data.id }}
-						<Icon type="md-cube" title="有关联资源" v-if="data.rnodes && data.rnodes.length" />
+						{{data.title}}
+						<!-- {{data.id}} -->
+						<Icon type="md-cube" :title="$t('syllabus.tree.hasResource')" v-if="data.rnodes && data.rnodes.length" />
 					</span>
 					<span class="custom-tree-tools" v-if="editable">
-						<Icon type="md-create" size="16" title="编辑" @click="onEditItem(node,data,$event)" />
-						<Icon type="md-add" size="16" title="添加" @click="onAddNode(data,$event)" />
-						<Icon type="md-remove" size="16" title="删除" @click="remove(node,data)" />
-						<Icon type="md-share" v-if="isFirstLevel(data)" :title="isSchool ? '共编该章节' : '分享该章节'"
+						<Icon type="md-create" size="16" :title="$t('syllabus.tree.edit')" @click="onEditItem(node,data,$event)" />
+						<Icon type="md-add" size="16" :title="$t('syllabus.tree.add')" @click="onAddNode(node,data,$event)" />
+						<Icon type="md-remove" size="16" :title="$t('syllabus.tree.remove')" @click="remove(node,data)" />
+						<Icon type="md-share" v-if="isFirstLevel(data)" :title="isSchool ? $t('syllabus.tree.coEdit') : $t('syllabus.tree.share')"
 							@click="doShare(data)" />
 					</span>
 				</span>
@@ -22,15 +23,15 @@
 
 		<!-- 新增或者编辑弹窗 -->
 		<Modal v-model="isEditOrAdd" width="500" footer-hide class="tree-modal">
-			<div class="modal-header" slot="header">{{ isEditItem ? '编辑节点':'新增节点'}}</div>
+			<div class="modal-header" slot="header">{{ isEditItem ? $t('syllabus.tree.editTitle'):$t('syllabus.tree.addTitle')}}</div>
 			<div class="modal-content">
-				<p class="node-title">父节点</p>
+				<p class="node-title">{{ $t('syllabus.tree.parent') }}</p>
 				<Input v-model="nodeInfo.parent" style="width: 100%" disabled />
-				<p class="node-title">节点名称</p>
-				<Input v-model="nodeInfo.title" placeholder="请输入节点名称..." style="width: 100%" />
+				<p class="node-title">{{ $t('syllabus.tree.nodeName') }}</p>
+				<Input v-model="nodeInfo.title" :placeholder="$t('syllabus.tree.place1')" style="width: 100%" />
 			</div>
 			<Button @click="onSubmitNode" class="modal-btn"
-				style="width: 88%;margin-left: 6%;margin-bottom: 20px;">确认</Button>
+				style="width: 88%;margin-left: 6%;margin-bottom: 20px;">{{ $t('syllabus.tree.confirm') }}</Button>
 		</Modal>
 	</div>
 </template>
@@ -69,6 +70,7 @@
 				currentResources: [],
 				currentItems: [],
 				curNode: null,
+				curData:null,
 				nodeInfo: {
 					id: null,
 					title: '',
@@ -82,10 +84,11 @@
 					pid: '',
 					code: '',
 					status: 1,
-					parent: ''
+					parent: '',
+					rnodes:[]
 				},
-				modifyIdArr: [],
-				isSchool: false
+				isSchool: false,
+				flatArr:[]
 			}
 		},
 		created() {
@@ -93,9 +96,12 @@
 		},
 		methods: {
 			onNodeClick(data, node) {
-				console.log(data, node)
-				this.curNode = data
-				this.$emit('onNodeClick', data)
+				this.curNode = node
+				this.curData = data
+				this.$emit('onNodeClick', {
+					data:data,
+					node:node
+				})
 			},
 
 			doShare(data) {
@@ -120,8 +126,8 @@
 			},
 			// 拖拽完成回调
 			handleDrop(draggingNode, dropNode, dropType) {
-				this.modifyIdArr.push(draggingNode.data.pid)
-				this.modifyIdArr.push(dropNode.data.pid)
+				this.$emit('addModifyId',this.getChapterIdById(draggingNode.data.id))
+				this.$emit('addModifyId',this.getChapterIdById(dropNode.data.id))
 				switch (dropType) {
 					case 'before':
 						draggingNode.data.pid = dropNode.data.pid
@@ -143,10 +149,8 @@
 			remove(node, data) {
 				let isFirstLevel = this.isFirstLevel(data)
 				this.$Modal.confirm({
-					title: '删除节点',
-					content: '<p>确认删除该节点?</p>',
-					okText: '确认',
-					cancelText: '取消',
+					title: this.$t('syllabus.tree.removeTitle'),
+					content: this.$t('syllabus.tree.removeConfirm'),
 					onOk: () => {
 						const parent = node.parent
 						const children = parent.data.children || parent.data
@@ -160,10 +164,10 @@
 							}).then(res => {
 								if (!res.error) {
 									if(res.code === 404){
-										this.modifyIdArr  = this.modifyIdArr.filter(i => i !== data.id)
+										this.$parent.modifyIdArr  = this.$parent.modifyIdArr.filter(i => i !== data.id)
 									}
 									children.splice(index, 1)
-									this.$Message.success('删除成功')
+									this.$Message.success(this.$t('syllabus.tree.removeSucTip'))
 								} else {
 									this.$Message.warning(res.error);
 								}
@@ -172,16 +176,16 @@
 							})
 						}else{
 							children.splice(index, 1)
-							this.$Message.success('删除成功')
+							this.$Message.success(this.$t('syllabus.tree.removeSucTip'))
 							this.$parent.hasModify = true
-							this.modifyIdArr.push(data.pid)
+							this.$emit('addModifyId',this.getChapterIdById(data.id))
 						}
 					}
 				})
 			},
 
 			// 点击添加展开弹窗
-			onAddNode(data, e) {
+			onAddNode(node,data, e) {
 				e.stopPropagation() // 防止点击事件穿透到父层
 				this.isEditItem = false
 				this.isEditOrAdd = true
@@ -189,6 +193,7 @@
 				this.nodeInfo.parent = data.title
 				this.nodeInfo.id = data.id
 				this.nodeInfo.title = ''
+				this.curNode = node
 			},
 
 			// 编辑节点操作
@@ -200,12 +205,23 @@
 				this.nodeInfo.parent = node.parent.data.title || this.volume.name
 				this.nodeInfo.title = node.data.title
 				this.nodeInfo.id = node.data.id
+				this.curNode = node
+			},
+			
+			/* 根据节点获取它所在的章节信息 */
+			getChapterByNode(node){
+				console.log(node)
+				if(node.level === 1){
+					return node
+				}else{
+					return this.getChapterByNode(node.parent)
+				}
 			},
 
 			// 提交编辑或者新增
 			onSubmitNode() {
 				if (!this.nodeInfo.title) {
-					this.$Message.warning('节点名称不能为空')
+					this.$Message.warning(this.$t('syllabus.tree.nodeNameTip'))
 					return
 				}
 				if (this.isEditItem) {
@@ -227,9 +243,46 @@
 				}
 				this.isEditOrAdd = false
 				this.$parent.hasModify = true
-				this.modifyIdArr.push(this.nodeInfo.id)
-				this.$Message.success(this.isEditItem ? '编辑成功' : '添加成功')
+				this.$emit('addModifyId',this.getChapterIdById(this.curData.id))
+				this.$Message.success(this.isEditItem ? this.$t('syllabus.tree.editSucTip') : this.$t('syllabus.tree.addSucTip'))
+			},
+			
+			/* 获取整个树的章节与子节点归属 */
+			getAllChild(arr){
+				let result = []
+				arr.forEach(item => {
+					result.push({
+						chapterId:item.id,
+						children:this.flatChildren(item.children)
+					})
+				})
+				this.flatArr = result
 			},
+			
+			/* 递归拉平所有children */
+			flatChildren(children){
+				let result = []
+				const fn = (source)=>{
+				    source.forEach(i => {
+				    	result.push(i.id)
+				    	if(i.children.length){
+				    		fn(i.children)
+				    	}
+				    })
+				}
+				fn(children)
+				return result
+			},
+			
+			/* 根据某个节点ID换取它对应的章节ID */
+			getChapterIdById(id){
+				if(this.flatArr.map(i => i.chapterId).includes(id)){
+					return id
+				}else{
+					let targetChapter = this.flatArr.find(i => i.children.includes(id))
+					return targetChapter.chapterId
+				}
+			}
 
 		},
 		computed: {
@@ -244,20 +297,8 @@
 			treeData: {
 				handler: function(n, o) {
 					// 以下为拼接树形数据以及册别数据
-					console.log(n)
-					// let charpterIds = this.volume.syllabusIds
-					// let trees = []
-					// try{
-					// 	trees = charpterIds.map(i => {
-					// 		let nodeObj = n.find(j => j.id === i)
-					// 		return nodeObj.trees[0]
-					// 	})
-					// 	this.treeDatas = trees
-					
-					// }catch(e){
-					// 	console.log(e)
-					// }
 					this.treeDatas = n.map(i => i.trees[0])
+					this.getAllChild(this.treeDatas)
 					this.$nextTick().then(() => {
 						const firstNode = document.querySelector('.el-tree-node')
 						firstNode.click();
@@ -317,7 +358,7 @@
 	}
 
 	.tree /deep/ .el-tree-node:before {
-		border-left: 1px dashed #343434;
+		border-left: 1px dashed #626262;
 		bottom: 0px;
 		height: 100%;
 		top: -20px;
@@ -325,7 +366,7 @@
 	}
 
 	.tree /deep/ .el-tree-node:after {
-		border-top: 1px dashed #343434;
+		border-top: 1px dashed #626262;
 		height: 20px;
 		top: 20px;
 		width: 12px;

+ 59 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/syllabus.js

@@ -1,3 +1,61 @@
 export default{
-    
+	praviteSyllabus:'个人课纲',
+	btnSave:'存储变更',
+	volumeList:'册别清单',
+	place1:'输入册别名称...',
+	syllabusMenu:'课纲目录',
+	relate:'关联资源',
+	addResource:'添加资源',
+	type1:'内容资源',
+	type2:'试题资源',
+	type3:'试卷资源',
+	type4:'本地文件',
+	type5:'超链接',
+	preview:'查看',
+	remove:'删除',
+	upload:'上传本地文件',
+	editVolume:'编辑册别',
+	addVolume:'新增册别',
+	grade:'年级',
+	semester:'学期',
+	name:'名称',
+	place2:'非必填,默认由年级+科目+学期组成...',
+	place3:'填写册别名称',
+	confirm:'确认',
+	addLink:'添加超链接',
+	linkName:'超链接标题',
+	linkUrl:'超链接地址',
+	chooseCoTeacher:'选择共编教师',
+	chooseShareTeacher:'选择分享教师',
+	score:'配分',
+	count:'题数',
+	deleteVolume:'删除册别',
+	deleteConfirm:'确认删除该册别?',
+	deleteSuc:'删除成功',
+	deleteFail:'删除失败',
+	uploadSuc:'上传成功',
+	removeConfirm:'确定要移除该资源吗?',
+	doSuc:'操作成功',
+	isExistVolume:'已存在相同册别',
+    tree:{
+		hasResource:'有关联资源',
+		edit:'编辑',
+		add:'添加',
+		remove:'删除',
+		coEdit:'共编该章节',
+		share:'分享该章节',
+		editTitle:'编辑节点',
+		addTitle:'新增节点',
+		parent:'父节点',
+		nodeName:'节点名称',
+		place1:'请输入节点名称...',
+		confirm:'确认',
+		removeTitle:'删除节点',
+		removeConfirm:'确认删除该节点?',
+		cancel:'取消',
+		removeSucTip:'删除成功',
+		nodeNameTip:'节点名称不能为空',
+		editSucTip:'编辑成功',
+		addSucTip:'添加成功',
+	}
 }

+ 61 - 3
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/syllabus.js

@@ -1,3 +1,61 @@
-export default{
-    
-}
+export default {
+	praviteSyllabus: '個人課綱',
+	btnSave: '存儲變更',
+	volumeList: '册別清單',
+	place1: '輸入册別名稱…',
+	syllabusMenu: '課綱目錄',
+	relate: '關聯資源',
+	addResource: '添加資源',
+	type1: '內容資源',
+	type2: '試題資源',
+	type3: '試卷資源',
+	type4: '本地檔案',
+	type5: '超連結',
+	preview: '查看',
+	remove: '删除',
+	upload: '上傳本地檔案',
+	editVolume: '編輯册別',
+	addVolume: '新增册別',
+	grade: '年級',
+	semester: '學期',
+	name: '名稱',
+	place2: '非必填,默認由年級+科目+學期組成…',
+	place3: '填寫册別名稱',
+	confirm: '確認',
+	addLink: '添加超連結',
+	linkName: '超連結標題',
+	linkUrl: '超連結地址',
+	chooseCoTeacher: '選擇共編教師',
+	chooseShareTeacher: '選擇分享教師',
+	score: '配分',
+	count: '題數',
+	deleteVolume: '删除册別',
+	deleteConfirm: '確認删除該册別?',
+	deleteSuc: '删除成功',
+	deleteFail: '删除失敗',
+	uploadSuc: '上傳成功',
+	removeConfirm: '確定要移除該資源嗎?',
+	doSuc: '操作成功',
+	isExistVolume: '已存在相同册別',
+	tree: {
+		hasResource: '有關聯資源',
+		edit: '編輯',
+		add: '添加',
+		remove: '删除',
+		coEdit: '共編該章節',
+		share: '分享該章節',
+		editTitle: '編輯節點',
+		addTitle: '新增節點',
+		parent: '父節點',
+		nodeName: '節點名稱',
+		place1: '請輸入節點名稱…',
+		confirm: '確認',
+		removeTitle: '删除節點',
+		removeConfirm: '確認删除該節點?',
+		cancel: '取消',
+		removeSucTip: '删除成功',
+		nodeNameTip: '節點名稱不能為空',
+		editSucTip: '編輯成功',
+		addSucTip: '添加成功',
+	}
+}

+ 10 - 2
TEAMModelOS/ClientApp/src/view/Home.vue

@@ -5,7 +5,7 @@
             <!-- 头部右侧个人中心部分 -->
             <div class="header-right-box fl-around" slot="header-content">
                 <Icon custom="iconfont icon-home" :color="routerName == 'homePage' ? '#1CC0F3':'#d0d0d0'" @click="toHome" />
-                <BaseNotification></BaseNotification>
+                <BaseNotification :msgs="msgs"></BaseNotification>
                 <Icon type="ios-settings" :color="routerName == 'settings' ? '#1CC0F3':'#d0d0d0'" @click="toSettings" />
                 <!-- 问题答疑页面暂未开发 暂时注释 -->
                 <!-- <Icon type="md-help-circle" :color="routerName == 'feedback' ? '#1CC0F3':'#d0d0d0'" @click="toFeedback"/> -->
@@ -28,6 +28,7 @@ export default {
     components: {},
     data() {
         return {
+			msgs:[],
             isShowMock: false,
             isOpenDrawer: false,
             routerName: '',
@@ -57,7 +58,14 @@ export default {
                 schoolCode: this.$GLOBAL.DEFAULT_SCHOOL_CODE
             })
         }
-
+		
+		this.$api.service.getNotification({
+			"from":"ies5:" + user_profile.defaultschool || user_profile.schools[0].schoolId,
+			"receiver": user.id
+		}).then(res => {
+			console.log('端外通知',res)
+			this.msgs = res.msgs
+		})
 
 
     },

+ 42 - 25
TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.vue

@@ -62,7 +62,7 @@
 				<div class="syllabus-tree-box">
 					<EmptyData :top="-240" v-if="!treeOrigin.length"></EmptyData>
 					<Tree ref="treeRef" :treeData="treeOrigin" :volume="curVolume"
-						:editable="$access.can('admin.*|Syllabus_Edit')" @onNodeClick="onNodeClick" @doShare="doShare" v-else></Tree>
+						:editable="$access.can('admin.*|Syllabus_Edit')" @onNodeClick="onNodeClick" @doShare="doShare" @addModifyId="addModifyId"v-else></Tree>
 				</div>
 			</div>
 			<div class="syllabus-right">
@@ -285,6 +285,7 @@
 				previewFile: {},
 				previewPaper: null,
 				curShareNode:null,
+				curChapter:null,
 				curLink: {
 					name: '',
 					link: ''
@@ -315,6 +316,7 @@
 					"children": [],
 					"rnodes": []
 				},
+				modifyIdArr:[]
 			}
 		},
 		created() {
@@ -380,6 +382,7 @@
 						this.volumeList = res.volumes.reverse()
 						this.originVolumeList = JSON.parse(JSON.stringify(this.volumeList))
 						let activeIndex = this.isEditVolume ? this.activeVolumeIndex : 0
+						// this.isEditVolume = false
 						res.volumes.length && this.onVolumeClick(res.volumes[activeIndex], activeIndex)
 					} else {
 						this.$Message.warning(res.error);
@@ -440,8 +443,6 @@
 				this.$Modal.confirm({
 					title: "删除册别",
 					content: "<p>确认删除该册别?</p>",
-					okText: "确认",
-					cancelText: "取消",
 					onOk: () => {
 						this.isLoading = true;
 						this.$api.syllabus.DeleteVolume({
@@ -456,7 +457,7 @@
 									.activeVolumeIndex] : null, this.activeVolumeIndex)
 								this.isLoading = false;
 							} else {
-								this.$Message.success("删除失败");
+								this.$Message.error("删除失败");
 							}
 						});
 					},
@@ -467,8 +468,8 @@
 				this.curNode.rnodes = []
 				this.curNode.id = ''
 				this.$api.syllabus.GetTreeByVolume({
-					code: volume.id,
-					// code: volume.code.replace('Volume-', ''),
+					volumeId: volume.id,
+					volumeCode: volume.code,
 					scope: volume.scope
 				}).then(res => {
 					if (!res.error) {
@@ -497,7 +498,7 @@
 				})
 				// 只要本地新增的章节 都要记录到修改数据里
 				this.$nextTick(() => {
-					this.$refs.treeRef.modifyIdArr.push(this.nodeInfo.id)
+					this.modifyIdArr.push(this.nodeInfo.id)
 					// 还原默认值
 					this.nodeInfo = {
 						"id": "",
@@ -511,13 +512,16 @@
 				this.isAddTreeModal = false
 				
 			},
+			/* 有章节发生变化记录章节ID */
+			addModifyId(id){
+				this.modifyIdArr.push(id)
+			},
 			/* 存储变更保存最新课纲数据 */
 			onSaveSyllabus() {
-				console.log(this.$refs.treeRef.modifyIdArr)
-				console.log(this.$refs.treeRef.treeDatas)
 				this.isEditVolume = true
 				// 拿到有修改变动的章节ID集合
-				let modifyIdArr = [...new Set(this.$refs.treeRef.modifyIdArr)].filter(i => i !== this.curVolume.id)
+				let modifyIdArr = [...new Set(this.modifyIdArr)].filter(i => i !== this.curVolume.id)
+				console.log(modifyIdArr)
 				let allChapter = this.$refs.treeRef ? this.$refs.treeRef.treeDatas : [];
 				let modifyChapters = modifyIdArr.length ? modifyIdArr.map(id => {
 					return {
@@ -534,9 +538,7 @@
 							this.treeOrigin = res;
 							this.hasModify = false;
 							this.$refs.treeRef && (this.$refs.treeRef.modifyIdArr = [])
-							this.isEditVolume = false
-							// this.$Message.success("保存成功");
-							// this.getTreeByVolumeId(this.curVolume)
+							// this.isEditVolume = false
 						})
 					} else {
 						this.$Message.warning("获取数据失败");
@@ -545,7 +547,17 @@
 			},
 			/* 点击某个节点 */
 			onNodeClick(data) {
-				this.curNode = data
+				this.curChapter = this.getChapterByNode(data.node)
+				console.log(this.curChapter)
+				this.curNode = data.data
+			},
+			/* 根据节点获取它所在的章节信息 */
+			getChapterByNode(node){
+				if(node.level === 1){
+					return node
+				}else{
+					return this.getChapterByNode(node.parent)
+				}
 			},
 			/* 点击节点进行分享 */
 			doShare(data){
@@ -558,7 +570,8 @@
 			},
 			/* 添加超链接 */
 			onAddLink() {
-				this.$refs.treeRef.curNode.rnodes.push({
+				console.log(this.$refs.treeRef.curData)
+				this.$refs.treeRef.curData.rnodes.push({
 					code: this.curCode,
 					scope: this.curScope,
 					id: this.$tools.guid(),
@@ -567,6 +580,7 @@
 					type: 'link',
 					ctnr: ''
 				})
+				this.modifyIdArr.push(this.curChapter.data.id)
 				this.hasModify = true
 				this.isShowLinkModal = false
 			},
@@ -590,7 +604,7 @@
 			/* 上传本地文件成功回调 */
 			uploadFinish(result) {
 				result.forEach(file => {
-					this.$refs.treeRef.curNode.rnodes.push({
+					this.$refs.treeRef.curData.rnodes.push({
 						code: this.curCode,
 						scope: this.curScope,
 						id: this.$tools.guid(),
@@ -600,6 +614,7 @@
 						cntr: this.curCode
 					})
 				})
+				this.modifyIdArr.push(this.curChapter.data.id)
 				this.hasModify = true
 				this.isUploadModal = false
 				this.$Message.success('上传成功')
@@ -615,7 +630,7 @@
 						let promiseArr = []
 						let privateSas = await this.$tools.getPrivateSas()
 						let schoolSas = await this.$tools.getSchoolSas()
-						let curResourceArr = this.$refs.treeRef.curNode.rnodes
+						let curResourceArr = this.$refs.treeRef.curData.rnodes
 						for(let i = 0 ; i < list.length ; i++){
 							promiseArr.push(new Promise((r2,j2) => {
 								let file = list[i]
@@ -653,7 +668,7 @@
 			onSelectQuestion(val) {
 				let list = this.$refs.chooseContentRef.$refs.exListRef.selectItems
 				if(!list.length) return
-				let curResourceArr = this.$refs.treeRef.curNode.rnodes
+				let curResourceArr = this.$refs.treeRef.curData.rnodes
 				list.forEach(i => {
 					if(!curResourceArr.filter(j => j.type === 'item').map(k => k.id).includes(i.id)){
 						curResourceArr.push({
@@ -673,7 +688,7 @@
 			onSelectPaper(){
 				let list = this.$refs.chooseContentRef.$refs.paperListRef.checkedPaperList
 				if(!list.length) return
-				let curResourceArr = this.$refs.treeRef.curNode.rnodes
+				let curResourceArr = this.$refs.treeRef.curData.rnodes
 				list.forEach(i => {
 					if(!curResourceArr.filter(j => j.type === 'paper').map(k => k.id).includes(i.id)){
 						curResourceArr.push({
@@ -691,14 +706,15 @@
 			},
 			/* 点击确认 去获取关联的内容数据、试题试卷数据 */
 			onRelateContent() {
-				let curResourceArr = this.$refs.treeRef.curNode.rnodes
+				let curResourceArr = this.$refs.treeRef.curData.rnodes
 				this.onSelectFile().then(res => {
 					this.onSelectQuestion()
 					this.onSelectPaper()
-					// this.$refs.treeRef.curNode.rnodes = this.relateFiles
-					console.log(curResourceArr)
+					// this.$refs.treeRef.curData.rnodes = this.relateFiles
+					console.log(this.curNode)
 					this.isRelateContentModal = false
 					this.hasModify = true
+					this.modifyIdArr.push(this.curChapter.data.id)
 				}).catch(err=> {
 					this.$Message.error(err)
 				})
@@ -781,18 +797,19 @@
 							this.isLoading = true
 							this.deleteBlobPrefix(item).then(r => {
 								this.$Message.success(this.$t('evaluation.deleteSuc'))
-								this.$refs.treeRef.curNode.rnodes.splice(index, 1)
+								this.$refs.treeRef.curData.rnodes.splice(index, 1)
 								this.hasModify = true
+								this.modifyIdArr.push(this.curChapter.data.id)
 							}).catch(err => {
 								this.$Message.error(err)
 							}).finally(() => {
 								this.isLoading = false
 							})
 						}else{
-							this.$refs.treeRef.curNode.rnodes.splice(index, 1)
+							this.$refs.treeRef.curData.rnodes.splice(index, 1)
 							this.hasModify = true
+							this.modifyIdArr.push(this.curChapter.data.id)
 						}
-						
 					}
 				})
 			},

+ 7 - 2
TEAMModelOS/Controllers/Core/CoreController.cs

@@ -7,6 +7,7 @@ using System.Collections.Generic;
 using System.Drawing.Imaging;
 using System.IO;
 using System.Linq;
+using System.Net.Http;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.RegularExpressions;
@@ -15,6 +16,7 @@ using TEAMModelOS.Models;
 using TEAMModelOS.Models.Request;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
+using TEAMModelOS.SDK.Models.Service;
 using TEAMModelOS.SDK.PngQuant;
 
 namespace TEAMModelOS.Controllers.Core
@@ -26,13 +28,16 @@ namespace TEAMModelOS.Controllers.Core
         private readonly AzureStorageFactory _azureStorage;
         private readonly DingDing _dingDing;
         private readonly Option _option;
-
-        public CoreController(AzureStorageFactory azureStorage , DingDing dingDing, IOptionsSnapshot<Option> option)
+        private readonly HttpClient _httpClient;
+        public CoreController(AzureStorageFactory azureStorage , DingDing dingDing, IOptionsSnapshot<Option> option, HttpClient httpClient)
         {
             _azureStorage = azureStorage;
             _dingDing = dingDing;
             _option = option?.Value;
+            _httpClient = httpClient;
         }
+       
+
         [HttpPost("apply-school")]
         public async Task<IActionResult> ApplySchool(ApplySchool request) {
             await _dingDing.SendBotMsg($"有新学校申请。\n申请站点:{_option.Location}\n申请学校:{request.name}\n所在国家\\地区:{request.area}\n申请人:{request.tmdname}({request.tmdid})\n联系电话:{request.cellphone}\n备注:{request.content}", GroupNames.AI智慧學校申請通知群);

+ 83 - 4
TEAMModelOS/Controllers/School/SchoolTeacherController.cs

@@ -19,6 +19,9 @@ using Microsoft.Extensions.Options;
 using System.Net.Http;
 using TEAMModelOS.SDK.Context.Configuration;
 using System.Net;
+using Microsoft.Extensions.Configuration;
+using TEAMModelOS.SDK.Models.Service;
+using TEAMModelOS.Filter;
 
 namespace TEAMModelOS.Controllers
 {
@@ -33,11 +36,15 @@ namespace TEAMModelOS.Controllers
         private readonly AzureCosmosFactory _azureCosmos;
         private readonly AzureStorageFactory _azureStorage;
         private readonly Option _option;
-        public SchoolTeacherController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, IOptionsSnapshot<Option> option)
+        private readonly IConfiguration _configuration;
+        private readonly NotificationService _notificationService;
+        public SchoolTeacherController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, IOptionsSnapshot<Option> option, IConfiguration configuration, NotificationService notificationService)
         {
             _azureCosmos = azureCosmos;
             _azureStorage = azureStorage;
             _option = option?.Value;
+            _configuration = configuration;
+            _notificationService = notificationService;
         }
         /// <summary>
         /// 取得學校所有老師(不論加入狀態)
@@ -194,8 +201,10 @@ namespace TEAMModelOS.Controllers
         /// <returns></returns>
         [ProducesDefaultResponseType]
         [HttpPost("add-teacher-status")]
+        [AuthToken(Roles = "teacher,admin")]
         public async Task<IActionResult> AddSchoolTeacher(JsonElement request)
         {
+            var (tid, tname, _, tschool) = HttpContext.GetAuthTokenInfo();
             var client = _azureCosmos.GetCosmosClient();
             //參數取得
             if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
@@ -216,11 +225,13 @@ namespace TEAMModelOS.Controllers
             }
             try
             {
+                List<TmdInfo> ids = new List<TmdInfo>();
                 foreach (var obj in user_list.EnumerateArray())
                 {
                     obj.TryGetProperty("id", out JsonElement id);
                     obj.TryGetProperty("name", out JsonElement name);
                     obj.TryGetProperty("picture", out JsonElement picture);
+                    ids.Add(new TmdInfo { tmdid=$"{id}",tmdname=$"{name}" });
                     //老師個人資料
                     var tresponse = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemStreamAsync(id.ToString(), new PartitionKey("Base"));
                     if(tresponse.Status == 200)
@@ -290,6 +301,26 @@ namespace TEAMModelOS.Controllers
                         await client.GetContainer("TEAMModelOS", "School").CreateItemStreamAsync(stream, new PartitionKey($"Teacher-{school_code}"));
                     }
                 }
+                string bizcode = grant_type.GetString();
+                if (grant_type.GetString() == "join")
+                {
+                    bizcode = "request-join";
+                }
+                Notification notification = new Notification
+                {
+                    hubName = "hita",
+                    type = "msg",
+                    from = $"ies5:{school_code}",
+                    to = ids.Select(x => x.tmdid).ToList(),
+                    label = $"{bizcode}_school",
+                    body = new { biz = bizcode, tmdid = tid, tmdname = tname.ToString(), schoolcode = $"{school_code}", schoolname = $"{schname}", status = 1, time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }.ToJsonString(),
+                    expires = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeSeconds()
+                };
+                var url = _configuration.GetValue<string>("HaBookAuth:CoreService:sendnotification");
+                var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
+                var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
+                var location = _option.Location;
+                var code = await _notificationService.SendNotification(clientID, clientSecret, location, url, notification);
                 return Ok(new { });
             }
             catch(Exception ex)
@@ -304,12 +335,13 @@ namespace TEAMModelOS.Controllers
         /// <param name="request"></param>
         /// <returns></returns>
         [ProducesDefaultResponseType]
-        //[AuthToken(Roles = "admin")]
+        [AuthToken(Roles = "admin")]
         [HttpPost("upd-teacher-status")]
         public async Task<IActionResult> UpdSchoolTeacherStatus(JsonElement request)
         {
             try
             {
+                var (tid, tname, _, tschool) = HttpContext.GetAuthTokenInfo();
                 if (!request.TryGetProperty("grant_type", out JsonElement grant_type)) return BadRequest();
                 if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
                 if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
@@ -364,7 +396,26 @@ namespace TEAMModelOS.Controllers
                     };
                     var response = await client.GetContainer("TEAMModelOS", "School").CreateItemAsync(st, new PartitionKey($"Teacher-{school_code}"));
                 }
-
+                string bizcode = grant_type.GetString();
+                if (grant_type.GetString() == "join")
+                {
+                    bizcode = "request-join";
+                }
+                Notification notification = new Notification
+                {
+                    hubName = "hita",
+                    type = "msg",
+                    from = $"ies5:{school_code}",
+                    to =new List<string> { teacher.id },
+                    label = $"{bizcode}_school",
+                    body = new { biz = bizcode, tmdid = tid, tmdname =tname, schoolcode = $"{school_code}", schoolname = $"{schname}", status = 1, time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }.ToJsonString(),
+                    expires = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeSeconds()
+                };
+                var url = _configuration.GetValue<string>("HaBookAuth:CoreService:sendnotification");
+                var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
+                var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
+                var location = _option.Location;
+                var code = await _notificationService.SendNotification(clientID, clientSecret, location, url, notification);
                 return Ok(new { });
             }
             catch (Exception ex)
@@ -385,6 +436,7 @@ namespace TEAMModelOS.Controllers
         {
             try
             {
+                var (tid, tname, _, tschool) = HttpContext.GetAuthTokenInfo();
                 if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
                 if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
 
@@ -395,7 +447,34 @@ namespace TEAMModelOS.Controllers
                 await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacher, id.ToString(), new PartitionKey("Base"));
                 //移除學校表中的老師document
                 var sresponse = await client.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync(id.GetString(), new PartitionKey($"Teacher-{school_code}"));
-
+                //取得學校資訊
+                var schresponse = await client.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(school_code.ToString(), new PartitionKey("Base"));
+                string schname = string.Empty;
+                if (schresponse.Status == 200)
+                {
+                    using var schjson = await JsonDocument.ParseAsync(schresponse.ContentStream);
+                    schjson.RootElement.TryGetProperty("name", out JsonElement jsonschname);
+                    schname = jsonschname.ToString();
+                }
+                else
+                {
+                    return BadRequest();
+                }
+                Notification notification = new Notification
+                {
+                    hubName = "hita",
+                    type = "msg",
+                    from = $"ies5:{school_code}",
+                    to = new List<string> { teacher.id },
+                    label = $"remove_school",
+                    body = new { biz = "remove", tmdid = tid, tmdname = tname, schoolcode = $"{school_code}", schoolname = $"{schname}", status = 1, time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }.ToJsonString(),
+                    expires = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeSeconds()
+                };
+                var url = _configuration.GetValue<string>("HaBookAuth:CoreService:sendnotification");
+                var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
+                var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
+                var location = _option.Location;
+                var code = await _notificationService.SendNotification(clientID, clientSecret, location, url, notification);
                 return Ok(new { });
             }
             catch (Exception ex)

+ 47 - 19
TEAMModelOS/Controllers/Syllabus/SyllabusController.cs

@@ -151,7 +151,7 @@ namespace TEAMModelOS.Controllers
         }
 
         /*
-            {"code":"册别code:0baf00db-0768-4b62-a8f7-280f6bcebf71","scope":"school"}
+            {"volumeId":"册别id:0baf00db-0768-4b62-a8f7-280f6bcebf71","scope":"school","volumeCode":"册别分区键"}
          */
         /// <summary>
         /// 查找课纲 
@@ -163,33 +163,55 @@ namespace TEAMModelOS.Controllers
         public async Task<IActionResult> Find(JsonElement request)
         {
             var client = _azureCosmos.GetCosmosClient();
-            if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
+            if (!request.TryGetProperty("volumeId", out JsonElement volumeId)) return BadRequest();
+            if (!request.TryGetProperty("volumeCode", out JsonElement volumeCode)) return BadRequest();
             if (!request.TryGetProperty("scope", out JsonElement scope)) return BadRequest();
-           
+            Volume volume = null;
             List<SyllabusTreeNode> treeNodes = new List<SyllabusTreeNode>();
+            List<SyllabusTreeNode> redt = new List<SyllabusTreeNode>();
             try {
                 if (scope.ToString().Equals("school"))
                 {
-                    await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<Syllabus>(queryText: $"select value(c) from c ",
-                    requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Syllabus-{code}") }))
+                      volume = await client.GetContainer("TEAMModelOS", "School").ReadItemAsync<Volume>($"{volumeId}", new PartitionKey($"{volumeCode}"));
+                      await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<Syllabus>(queryText: $"select value(c) from c ",
+                    requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Syllabus-{volumeId}") }))
                     {
                         List<SyllabusTree> trees = SyllabusService.ListToTree(item.children);
                         SyllabusTreeNode tree = new SyllabusTreeNode() { id = item.id, scope =item.scope, trees = trees ,volumeId=item.volumeId,auth=item.auth};
                         treeNodes.Add(tree);
                     }
-                    
                 }
                 else
                 {
+                    volume = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemAsync<Volume>($"{volumeId}", new PartitionKey($"{volumeCode}"));
                     await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<Syllabus>(queryText: $"select value(c) from c ",
-                     requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Syllabus-{code}") }))
+                     requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Syllabus-{volumeId}") }))
                     {
                         List<SyllabusTree> trees = SyllabusService.ListToTree(item.children);
                         SyllabusTreeNode tree = new SyllabusTreeNode() { id = item.id, scope = item.scope, trees = trees, volumeId = item.volumeId, auth = item.auth };
                         treeNodes.Add(tree);
                     }
                 }
-                return Ok(new { tree= treeNodes });
+                if (volume.syllabusIds.IsNotEmpty())
+                {
+                    volume.syllabusIds.ForEach(x =>
+                    {
+                        for (int index = 0; index < treeNodes.Count; index++)
+                        {
+                            if (treeNodes[index].id == x)
+                            {
+                                redt.Add(treeNodes[index]);
+                                treeNodes.RemoveAt(index);
+                            }
+                        }
+                    });
+                    redt.AddRange(treeNodes);
+                    return Ok(new { tree = redt });
+                }
+                else {
+                    return Ok(new { tree = treeNodes });
+                }
+               
             } catch (Exception ex) {
                 await _dingDing.SendBotMsg($"OS,{_option.Location},common/syllabus/find-id\n{ex.Message}{ex.StackTrace}", GroupNames.成都开发測試群組);
                 return Ok(new { tree= treeNodes });
@@ -209,19 +231,25 @@ namespace TEAMModelOS.Controllers
         [HttpPost("delete")]
         public async Task<IActionResult> Delete(JsonElement request)
         {
-            if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
-            if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
-            if (!request.TryGetProperty("scope", out JsonElement scope)) return BadRequest();
-            var client = _azureCosmos.GetCosmosClient();
-            if (scope.ToString().Equals("school"))
+            try
             {
-                var response = await client.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync(id.ToString(), new PartitionKey($"Syllabus-{code}"));
-                return Ok(new { code = response.Status });
+                if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
+                if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
+                if (!request.TryGetProperty("scope", out JsonElement scope)) return BadRequest();
+                var client = _azureCosmos.GetCosmosClient();
+                if (scope.ToString().Equals("school"))
+                {
+                    var response = await client.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync(id.ToString(), new PartitionKey($"Syllabus-{code}"));
+                    return Ok(new { code = response.Status });
+                }
+                else
+                {
+                    var response = await client.GetContainer("TEAMModelOS", "Teacher").DeleteItemStreamAsync(id.ToString(), new PartitionKey($"Syllabus-{code}"));
+                    return Ok(new { code = response.Status });
+                }
             }
-            else
-            {
-                var response = await client.GetContainer("TEAMModelOS", "Teacher").DeleteItemStreamAsync(id.ToString(), new PartitionKey($"Syllabus-{code}"));
-                return Ok(new { code = response.Status });
+            catch {
+                return Ok(new { code = 404});
             }
         }
     }

+ 43 - 3
TEAMModelOS/Controllers/Teacher/InitController.cs

@@ -19,6 +19,9 @@ using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.Filter;
 using TEAMModelOS.SDK.Models.Cosmos;
 using HTEXLib.COMM.Helpers;
+using TEAMModelOS.SDK.Models.Service;
+using Microsoft.Extensions.Configuration;
+using System.Net.Http;
 
 namespace TEAMModelOS.Controllers
 {
@@ -33,13 +36,16 @@ namespace TEAMModelOS.Controllers
         private readonly AzureStorageFactory _azureStorage;
         private readonly DingDing _dingDing;
         private readonly Option _option;
-
-        public InitController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, DingDing dingDing, IOptionsSnapshot<Option> option)
+        private readonly IConfiguration _configuration; 
+        private readonly NotificationService _notificationService;
+        public InitController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, DingDing dingDing, IOptionsSnapshot<Option> option, IConfiguration configuration, NotificationService notificationService)
         {
             _azureCosmos = azureCosmos;
             _azureStorage = azureStorage;
             _dingDing = dingDing;
             _option = option?.Value;
+            _configuration = configuration;
+            _notificationService = notificationService;
         }
 
         //TODO 此API需處理對應前端返回的相關數據
@@ -403,6 +409,8 @@ namespace TEAMModelOS.Controllers
                 if (!requert.TryGetProperty("grant_type", out JsonElement grant_type)) return BadRequest();  //"invite":學校邀請 "request":老師申請 "join":"成為學校老師",leave 离开,cancel 取消。
                 if (!requert.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
                 if (!requert.TryGetProperty("school_name", out JsonElement school_name)) return BadRequest();
+                ///当邀请某个老师加入学校则需要知道是谁邀请的
+                //if (!requert.TryGetProperty("school_admin", out JsonElement school_admin)) return BadRequest();
                 string authtoken = HttpContext.GetXAuth("AuthToken");
                 if (string.IsNullOrEmpty(authtoken)) return BadRequest();
                 var jwt = new JwtSecurityToken(authtoken);
@@ -411,7 +419,6 @@ namespace TEAMModelOS.Controllers
                 var Claims = jwt.Payload.Claims;
                 jwt.Payload.TryGetValue("name", out object name);
                 jwt.Payload.TryGetValue("picture", out object picture);
-
                 var client = _azureCosmos.GetCosmosClient();
                 //在老師表找出老師,處理該學校狀態 (老師基本資料應該要存在)
                 Teacher teacher = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemAsync<Teacher>(id, new PartitionKey("Base"));
@@ -441,6 +448,7 @@ namespace TEAMModelOS.Controllers
                     return Ok(new { stauts = 1 });
                 }
                 else {
+                    
                     await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacher, id, new PartitionKey("Base"));
                     //在學校表處理該學校教師帳號的狀態
                     var sresponse = await client.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(id, new PartitionKey($"Teacher-{school_code}"));
@@ -467,6 +475,38 @@ namespace TEAMModelOS.Controllers
                         };
                         var response = await client.GetContainer("TEAMModelOS", "School").CreateItemAsync(st, new PartitionKey($"Teacher-{school_code}"));
                     }
+                    Notification notification = null;
+                    
+                    List<SchoolTeacher> teachers = new List<SchoolTeacher>();
+                    var queryslt = $"SELECT  value(c)  FROM c join A1 in c.roles   where  A1 in ('admin')";
+                    await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<SchoolTeacher>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{school_code}") }))
+                    {
+                        teachers.Add(item);
+                    }
+                    if (teachers.IsNotEmpty()) {
+                        string code = grant_type.GetString();
+                        if (grant_type.GetString() == "join") {
+                            code = "invite-join";
+                        }
+                            notification = new Notification
+                        {
+                            hubName = "hita",
+                            type = "msg",
+                            from = $"ies5:{school_code}",
+                            to = teachers.Select(x => x.id).ToList(),
+                            label = $"{code}_school",
+                            body = new {biz= code, tmdid= id ,tmdname= name.ToString(), schoolcode=$"{school_code}", schoolname=$"{school_name}", status =1,time= DateTimeOffset.UtcNow .ToUnixTimeMilliseconds()}.ToJsonString(),
+                            expires= DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeSeconds()
+                        };
+                    }
+                    
+                    if (notification != null) {
+                        var url= _configuration.GetValue<string>("HaBookAuth:CoreService:sendnotification");
+                        var clientID= _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
+                        var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
+                        var location = _option.Location;
+                        var code = await _notificationService .SendNotification(clientID, clientSecret, location, url, notification);
+                    }
                     return Ok(new { stauts = 1 });
                 }
             }

+ 2 - 2
TEAMModelOS/Startup.cs

@@ -25,6 +25,7 @@ using TEAMModelOS.SDK;
 using TEAMModelOS.SDK.Context.Attributes.Azure;
 using TEAMModelOS.SDK.Context.Configuration;
 using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK.Models.Service;
 using VueCliMiddleware;
 
 namespace TEAMModelOS
@@ -99,14 +100,13 @@ namespace TEAMModelOS
             services.AddSnowflakeId(Convert.ToInt64(Configuration.GetValue<string>("Option:LocationNum")), 1);
             services.AddHttpClient();
             services.AddHttpClient<DingDing>();
+            services.AddHttpClient<NotificationService>();
             services.AddMemoryCache();
             services.AddSpaStaticFiles(opt => opt.RootPath = "ClientApp/dist");
             services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.IgnoreNullValues = false; });
-
             //HttpContextAccessor,并用来访问HttpContext。(提供組件或非控制器服務存取HttpContext)
             services.AddHttpContextAccessor();
             services.Configure<Option>(options => Configuration.GetSection("Option").Bind(options));
-           
             //注入word 標籤解析
             string path = $"{ environment.ContentRootPath}/JsonFile/Core";
             services.AddHtexTranslator(path);

+ 6 - 1
TEAMModelOS/appsettings.Development.json

@@ -42,7 +42,12 @@
       "userinfo": "https://api2.teammodel.cn/Oauth2/GetUserInfos"
     },
     "CoreService": {
-      "deviceinfo": "https://api2.teammodel.cn/oauth2/getdeviceinfos"
+      "clientID": "c7317f88-7cea-4e48-ac57-a16071f7b884",
+      "clientSecret": "kguxh:V.PLmxBdaI@jnrTrDSth]A3346",
+      "deviceinfo": "https://api2.teammodel.cn/oauth2/getdeviceinfos",
+      "sendnotification": "https://api2.teammodel.net/service/sendnotification",
+      "getnotification": "https://api2.teammodel.net/service/getnotification",
+      "delnotification": "https://api2.teammodel.net/service/delnotification"
     }
   }
 }

+ 4 - 1
TEAMModelOS/appsettings.json

@@ -43,7 +43,10 @@
       "userinfo": "https://api2.teammodel.cn/Oauth2/GetUserInfos"
     },
     "CoreService": {
-      "deviceinfo": "https://api2.teammodel.cn/oauth2/getdeviceinfos"
+      "deviceinfo": "https://api2.teammodel.cn/oauth2/getdeviceinfos",
+      "sendnotification": "https://api2.teammodel.net/service/sendnotification",
+      "getnotification": "https://api2.teammodel.net/service/getnotification",
+      "delnotification": "https://api2.teammodel.net/service/delnotification"
     }
   }
 }