瀏覽代碼

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

zhouj1203@hotmail.com 1 年之前
父節點
當前提交
4a806fc34d

+ 4 - 2
TEAMModelOS.SDK/Models/Service/GroupListService.cs

@@ -288,7 +288,8 @@ namespace TEAMModelOS.SDK
                             lang = lang,
                             lang = lang,
                             qrcodeExpire=item.qrcodeExpire,
                             qrcodeExpire=item.qrcodeExpire,
                             groupName= item.name,
                             groupName= item.name,
-                            applyTime=now
+                            applyTime=now,
+                            seatNo=seatNo
                         };
                         };
                         string key = $"GroupList:GroupWaitingList:{item.scope}:{item.id}";
                         string key = $"GroupList:GroupWaitingList:{item.scope}:{item.id}";
                         string filed = !string.IsNullOrWhiteSpace(school) ? $"{school}_{userid}" : userid;
                         string filed = !string.IsNullOrWhiteSpace(school) ? $"{school}_{userid}" : userid;
@@ -308,7 +309,7 @@ namespace TEAMModelOS.SDK
                     //状态=5 ,人数已满
                     //状态=5 ,人数已满
                     return (5, item, null);
                     return (5, item, null);
                 }
                 }
-                data = JoinList(item, userid, type, school,year);
+                data = JoinList(item, userid, type, school,year,seatNo);
                 //TODO 需要考虑已经通过别的途径加入名单,但是缓存数据仍记录数据的问题。 还要考虑手动加入过的。或者在获取名未加入名单成员的临时缓存数据的时候过滤已经加入的。
                 //TODO 需要考虑已经通过别的途径加入名单,但是缓存数据仍记录数据的问题。 还要考虑手动加入过的。或者在获取名未加入名单成员的临时缓存数据的时候过滤已经加入的。
                 break;
                 break;
             }
             }
@@ -341,6 +342,7 @@ namespace TEAMModelOS.SDK
             /// 申请状态,-1 申请中,0 通过,1 拒绝,2 已过期。 
             /// 申请状态,-1 申请中,0 通过,1 拒绝,2 已过期。 
             /// </summary>
             /// </summary>
             public int status { get; set; } = -1;
             public int status { get; set; } = -1;
+            public int seatNo { get; set; }
            public List<IdName> courses  { get; set; }= new List<IdName>();
            public List<IdName> courses  { get; set; }= new List<IdName>();
         }
         }
         public static (int status, GroupList stuList, Member member) JoinList(GroupList stuList, string userid, int type, string school, int year, int seatNo = 0)
         public static (int status, GroupList stuList, Member member) JoinList(GroupList stuList, string userid, int type, string school, int year, int seatNo = 0)

+ 16 - 13
TEAMModelOS/ClientApp/src/api/common.js

@@ -3,64 +3,67 @@ export default {
     /*
     /*
      *查询名单列表
      *查询名单列表
      */
      */
-     getGroupList: function (data) {
+    getGroupList: function (data) {
         return post('/grouplist/get-grouplists', data)
         return post('/grouplist/get-grouplists', data)
     },
     },
     /*
     /*
      *查询名单详细信息
      *查询名单详细信息
      */
      */
-     getGroupInfo: function (data) {
+    getGroupInfo: function (data) {
         return post('/grouplist/get-grouplist-idcode', data)
         return post('/grouplist/get-grouplist-idcode', data)
     },
     },
     /*
     /*
      *保存更新名单信息
      *保存更新名单信息
      */
      */
-     upsertGroupInfo: function (data) {
+    upsertGroupInfo: function (data) {
         return post('/grouplist/upsert-grouplist', data)
         return post('/grouplist/upsert-grouplist', data)
     },
     },
     /*
     /*
      *根据条件查询名单列表和成员信息
      *根据条件查询名单列表和成员信息
      */
      */
-     getGroupListInfo: function (data) {
+    getGroupListInfo: function (data) {
         return post('/grouplist/get-grouplists-members', data)
         return post('/grouplist/get-grouplists-members', data)
     },
     },
     /*
     /*
      *根据名单id查询名单详细信息
      *根据名单id查询名单详细信息
      */
      */
-     getGroupListByIds: function (data) {
+    getGroupListByIds: function (data) {
         return post('/grouplist/get-members-listids', data)
         return post('/grouplist/get-members-listids', data)
     },
     },
+    getGroupNosByIds: function (data) {
+        return post('/grouplist/get-grouplist-nos', data)
+    },
     /*
     /*
      *查询活动发布对象
      *查询活动发布对象
      */
      */
-     getActivityTarget: function (data) {
+    getActivityTarget: function (data) {
         return post('/grouplist/get-activity-grouplist', data)
         return post('/grouplist/get-activity-grouplist', data)
     },
     },
     /*
     /*
      *删除名单(单纯的名单操作,主要用于教学班、教研组管理)
      *删除名单(单纯的名单操作,主要用于教学班、教研组管理)
      */
      */
-     deleteGroupList: function (data) {
+    deleteGroupList: function (data) {
         return post('/grouplist/delete-grouplist', data)
         return post('/grouplist/delete-grouplist', data)
     },
     },
     /*
     /*
      *删除个人名单以及课程关联关系(用于个名单和课程的关联关系的管理)
      *删除个人名单以及课程关联关系(用于个名单和课程的关联关系的管理)
      */
      */
-     deleteStulistRelCus: function (data) {
+    deleteStulistRelCus: function (data) {
         return post('/school/course/del-stulist-rmv-rel-course', data)
         return post('/school/course/del-stulist-rmv-rel-course', data)
     },
     },
     /*
     /*
      *查询行政班名单学生完整信息
      *查询行政班名单学生完整信息
      */
      */
-     getClassStudent: function (data) {
+    getClassStudent: function (data) {
         return post('/grouplist/get-classstudents-idcode', data)
         return post('/grouplist/get-classstudents-idcode', data)
     },
     },
-	/* 查询组长信息 */
-	getGroupLeaderTags: function (data) {
+    /* 查询组长信息 */
+    getGroupLeaderTags: function (data) {
         return post('/grouplist/get-grouplist-tags', data)
         return post('/grouplist/get-grouplist-tags', data)
     },
     },
     /* 生成随机码 */
     /* 生成随机码 */
-	getServerRandom: function (data) {
+    getServerRandom: function (data) {
         return post('core/random-code', data)
         return post('core/random-code', data)
     },
     },
-    
+
 }
 }

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

@@ -5,6 +5,7 @@ import config from '@/store/module/config'
 import { app } from '@/boot-app.js'
 import { app } from '@/boot-app.js'
 // 不需要携带access_token
 // 不需要携带access_token
 const NO_ACCESS_API = [
 const NO_ACCESS_API = [
+    '/grouplist/get-grouplist-nos',
     '/core/system-info',
     '/core/system-info',
     '/oauth2/login',
     '/oauth2/login',
     '/oauth2/token',
     '/oauth2/token',
@@ -34,6 +35,7 @@ const NO_ACCESS_API = [
 ]
 ]
 // 需要携带access_token 不需要携带auth-token
 // 需要携带access_token 不需要携带auth-token
 const NO_AUTH_API = [
 const NO_AUTH_API = [
+    '/grouplist/get-grouplist-nos',
     '/teacher/init/get-teacher-info',
     '/teacher/init/get-teacher-info',
     '/teacher/init/get-school-info',
     '/teacher/init/get-school-info',
     '/tmduser/init/get-tmduser-info',
     '/tmduser/init/get-tmduser-info',

+ 310 - 262
TEAMModelOS/ClientApp/src/view/joinclass/JoinClass.vue

@@ -1,64 +1,68 @@
 <template>
 <template>
-    <div class="join-wrap">
-        <div class="join-main-box">
-            <p class="join-title">
-                <span>
-                    {{$t('cusMgt.join.title')}}
-                </span>
-            </p>
-            <div v-show="isJoin || isRep" style="margin-bottom:50px">
-                <p class="join-title">
-                    <span>
-                        {{$t('cusMgt.join.title')}}
-                    </span>
-                </p>
-                <Icon type="md-checkmark-circle-outline" color="#19be6b" size="120" />
-                <p v-if="isJoin" class="success-tips">{{userName}}, {{$t('cusMgt.join.hasJoin')}}</p>
-                <p v-else-if="isRep" class="success-tips">{{$t('cusMgt.join.repJoin')}}</p>
-            </div>
-            <p v-show="!isJoin && !isRep" class="course-name">{{cusName || $t('cusMgt.join.errorInfo')}}</p>
-            <div style="width:fit-content;margin: auto;">
-                <p class="info-item" v-show="isJoin || isRep">
-                    <span class="info-lable">
-                        {{$t('cusMgt.join.cusLabel')}}
-                    </span>
-                    <span class="info-value">
-                        {{cusName}}
-                    </span>
-                </p>
-                <p class="info-item">
-                    <span class="info-lable">
-                        {{$t('cusMgt.join.teacherLabel')}}
-                    </span>
-                    <span class="info-value">
-                        {{tName}}
-                    </span>
-                </p>
-                <p class="info-item">
-                    <span class="info-lable">
-                        {{$t('cusMgt.join.listLabel')}}
-                    </span>
-                    <span class="info-value">
-                        {{listName}}
-                    </span>
-                </p>
-                <p class="info-item">
-                    <span class="info-lable">
-                        {{$t('settings.des')}}:
-                    </span>
-                    <span class="info-value">
-                        {{cusDesc}}
-                    </span>
-                </p>
-                <router-link v-show="(isJoin || isRep) && isPC" :to="{name:'login', query:{code,identity:'student'}}" style="margin-top:50px;display:block">{{$t('cusMgt.join.toTeammodel')}}</router-link>
-            </div>
-
-            <div class="join-btn" @click="joinList()" v-show="!isJoin && !isRep">
-                {{ hasSend ? $t('newCusMgt.joinTip1') : $t('cusMgt.join.joinBtn')}}
-            </div>
+	<div class="join-wrap">
+		<div class="join-main-box">
+			<p class="join-title">
+				<span>
+					{{ $t("cusMgt.join.title") }}
+				</span>
+			</p>
+			<div v-show="isJoin || isRep" style="margin-bottom: 50px">
+				<p class="join-title">
+					<span>
+						{{ $t("cusMgt.join.title") }}
+					</span>
+				</p>
+				<Icon type="md-checkmark-circle-outline" color="#19be6b" size="120" />
+				<p v-if="isJoin" class="success-tips">{{ userName }}, {{ $t("cusMgt.join.hasJoin") }}</p>
+				<p v-else-if="isRep" class="success-tips">{{ $t("cusMgt.join.repJoin") }}</p>
+			</div>
+			<p v-show="!isJoin && !isRep" class="course-name">{{ cusName || $t("cusMgt.join.errorInfo") }}</p>
+			<div style="width: fit-content; margin: auto">
+				<p class="info-item" v-show="isJoin || isRep">
+					<span class="info-lable">
+						{{ $t("cusMgt.join.cusLabel") }}
+					</span>
+					<span class="info-value">
+						{{ cusName }}
+					</span>
+				</p>
+				<p class="info-item">
+					<span class="info-lable">
+						{{ $t("cusMgt.join.teacherLabel") }}
+					</span>
+					<span class="info-value">
+						{{ tName }}
+					</span>
+				</p>
+				<p class="info-item">
+					<span class="info-lable">
+						{{ $t("cusMgt.join.listLabel") }}
+					</span>
+					<span class="info-value">
+						{{ listName }}
+					</span>
+				</p>
+				<p class="info-item">
+					<span class="info-lable"> {{ $t("settings.des") }}: </span>
+					<span class="info-value">
+						{{ cusDesc }}
+					</span>
+				</p>
+				<p class="info-item" v-if="allowPick && !isJoin && !isRep">
+					<span class="info-lable"> 座号: </span>
+					<Select v-model="pickIRS" style="width: 80px">
+						<Option v-for="item in irsArr" :value="item" :key="item">{{ item }}</Option>
+					</Select>
+				</p>
+				<p class="tips" v-if="allowPick && !isJoin && !isRep" style="margin-top: 20px; font-size: 12px">* 如果您自选的IRS座号已被占用,会由系统自动分配新的IRS编号</p>
+				<router-link v-show="(isJoin || isRep) && isPC" :to="{ name: 'login', query: { code, identity: 'student' } }" style="margin-top: 50px; display: block">{{ $t("cusMgt.join.toTeammodel") }}</router-link>
+			</div>
 
 
-        </div>
-        <!-- <div v-show="isJoin" class="join-main-box">
+			<div class="join-btn" @click="joinList()" v-show="!isJoin && !isRep">
+				{{ hasSend ? $t("newCusMgt.joinTip1") : $t("cusMgt.join.joinBtn") }}
+			</div>
+		</div>
+		<!-- <div v-show="isJoin" class="join-main-box">
             <p class="join-title">
             <p class="join-title">
                 <span>
                 <span>
                     {{$t('cusMgt.join.title')}}
                     {{$t('cusMgt.join.title')}}
@@ -67,211 +71,255 @@
             <Icon type="md-checkmark-circle-outline" color="#19be6b" size="120" />
             <Icon type="md-checkmark-circle-outline" color="#19be6b" size="120" />
             <p class="success-tips">{{$t('cusMgt.join.hasJoin')}}</p>
             <p class="success-tips">{{$t('cusMgt.join.hasJoin')}}</p>
         </div> -->
         </div> -->
-    </div>
+	</div>
 </template>
 </template>
 <script>
 <script>
-import jwtDecode from 'jwt-decode'
-import { mapGetters } from 'vuex'
-export default {
-    data() {
-        return {
-            hasSend:false,
-            isRep: false,
-            isPC: false,
-            isLogin: false,
-            isJoin: false,
-            tId: '',
-            tName: '',
-            listNo: '',
-            listName: '',
-            cusName: '',
-            cusDesc:'',
-            cusId:'',
-            code: '',
-            userId: '',
-            userName: '',
-            id_token: ''
-        }
-    },
-        created() {
-        console.log('当前站点配置信息', this.curSiteConfig)
-        this.tId = this.$route.query.tId //教师id
-        this.tName = this.$route.query.tName //教师姓名
-        this.listNo = this.$route.query.listNo   //名单id
-        this.listName = this.$route.query.listName //名单
-        this.cusName = this.$route.query.cusName  //课程名称
-        this.cusDesc = this.$route.query.cusDesc  //课程名称
-        this.cusId = this.$route.query.cusId  //课程名称
-        this.code = this.$route.query.code  //登录成功返回的code
-        if (!this.listNo) {
-            this.$Modal.error({
-                title: this.$t('cusMgt.join.errorTile'),
-                content: this.$t('cusMgt.join.errorContent')
-            })
-        } else if (this.code) {
-            //获取登录信息
-            let host = this.curSiteConfig.coreAPIUrl
-            let clientId = this.curSiteConfig.clientID
-            this.$api.service.getToken(host, {
-                grant_type: "authorization_code",
-                client_id: clientId,
-                code: this.code
-            }).then(
-                res => {
-                    if (!res.error) {
-                        this.id_token = res.id_token
-                        //记录登录状态,保证API请求验证通过
-                        localStorage.setItem("access_token", res.access_token)
-                        localStorage.setItem("id_token", res.id_token)
-                        localStorage.setItem("expires_in", res.expires_in)
-                        let tokenData = jwtDecode(res.id_token)
-                        if (tokenData) {
-                            this.userId = tokenData.sub
-                            this.userName = tokenData.name
-                            this.joinList()
-                        } else {
-                            this.$Message.error(this.$t('cusMgt.join.parseErr'))
-                        }
-                    } else {
-                        this.$Message.error(this.$t('cusMgt.join.getErr'))
-                    }
-                },
-                err => {
-                    this.$Message.error(this.$t('cusMgt.join.getErr'))
-                }
-            )
-
-        }
-        // 判断移动端还是PC端
-        this.isPC = !(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent))
-        console.log('pc', this.isPC)
-    },
-    computed: {
-        ...mapGetters({
-            curSiteConfig: 'config/getCurSiteConfig',
-        })
-    },
-    methods: {
-        joinList() {
-            console.error('xxx',this.id_token)
-            console.error('xxxxxx',localStorage.id_token)
-            if(!this.id_token && !localStorage.id_token){
-                this.toLogin()
-                return
-            }
-            this.$api.courseMgmt.qrCodeJoinList({
-                stuListNo: this.listNo,
-                courseId: this.cusId,
-                tmdId: this.userId,
-                id_token: this.id_token || localStorage.id_token,
-                school: undefined //加入行政班时需要传对应编码,个人名单不用传
-            }).then(
-                res => {
-                    if (!res.error) {
-                        this.hasSend = false
-                        if (res.status == 2) {//重复加入
-                            this.$Message.success(this.$t('cusMgt.join.repJoin'))
-                            this.isRep = true
-                        } else if (res.status == 1) {//参数异常
-                            this.$Message.success(this.$t('cusMgt.join.joinPErr'))
-                        } else if (res.status == 0) {//正常加入
-                            this.$Message.success(this.$t('cusMgt.join.joinOk'))
-                            this.isJoin = true
-                            sessionStorage.setItem('identity', 'student')
-                        } else if (res.status == 4) {// 个人名单未开放加入
-                            this.$Message.warning(this.$t('cusMgt.join.joinLock'))
-                        } else if (res.status == 6) {// 需要审核通过再加入
-                            this.$Message.warning(this.$t('newCusMgt.joinTip1'))
-                            this.hasSend = true
-                        } else if (res.status == 5) {// 人数已满,需要审核通过再加入
-                            this.$Message.warning(this.$t('newCusMgt.joinTip2'))
-                        } else if (res.status == 7) {// 二维码设置已经过期
-                            this.$Message.warning(this.$t('newCusMgt.joinTip3'))
-                        }
-                    } else {
-                        this.$Message.error(this.$t('cusMgt.join.joinErr'))
-                    }
-                },
-                err => {
-                    this.$Message.error(this.$t('cusMgt.join.joinErr'))
-                }
-            )
-        },
-        toLogin() {
-            // let type = process.env.NODE_ENV //product | development
-            let accUrl = this.curSiteConfig.accAPIUrl
-            let clientId = this.curSiteConfig.clientID
-            let callback = decodeURIComponent(window.location.href)
-            let state = this.$jsFn.getBtwRandom(1000, 9999)
-            let nonce = this.$jsFn.uuid()
-            let loginUrl = `${accUrl}/oauth2/authorize?response_type=code&client_id=${clientId}&state=${state}&nonce=${nonce}&redirect_uri=${encodeURIComponent(callback)}`
-            window.location.href = loginUrl
-        }
-    },
+	import jwtDecode from "jwt-decode";
+	import { mapGetters } from "vuex";
+	export default {
+		data() {
+			return {
+				irsArr: [],
+				pickIRS: 1,
+				allowPick: false,
+				hasSend: false,
+				isRep: false,
+				isPC: false,
+				isLogin: false,
+				isJoin: false,
+				tId: "",
+				tName: "",
+				listNo: "",
+				listName: "",
+				cusName: "",
+				cusDesc: "",
+				cusId: "",
+				code: "",
+				userId: "",
+				userName: "",
+				id_token: ""
+			};
+		},
+		created() {
+			console.log("当前站点配置信息", this.curSiteConfig);
+			this.tId = this.$route.query.tId; //教师id
+			this.tName = this.$route.query.tName; //教师姓名
+			this.listNo = this.$route.query.listNo; //名单id
+			this.listName = this.$route.query.listName; //名单
+			this.cusName = this.$route.query.cusName; //课程名称
+			this.cusDesc = this.$route.query.cusDesc; //课程名称
+			this.cusId = this.$route.query.cusId; //课程名称
+			this.code = this.$route.query.code; //登录成功返回的code
+			this.getGroupListDetails().then((r) => {
+				if (!this.listNo) {
+					this.$Modal.error({
+						title: this.$t("cusMgt.join.errorTile"),
+						content: this.$t("cusMgt.join.errorContent")
+					});
+				} else if (this.code) {
+					//获取登录信息
+					let host = this.curSiteConfig.coreAPIUrl;
+					let clientId = this.curSiteConfig.clientID;
+					this.$api.service
+						.getToken(host, {
+							grant_type: "authorization_code",
+							client_id: clientId,
+							code: this.code
+						})
+						.then(
+							(res) => {
+								if (!res.error) {
+									this.id_token = res.id_token;
+									//记录登录状态,保证API请求验证通过
+									localStorage.setItem("access_token", res.access_token);
+									localStorage.setItem("id_token", res.id_token);
+									localStorage.setItem("expires_in", res.expires_in);
+									let tokenData = jwtDecode(res.id_token);
+									if (tokenData) {
+										this.userId = tokenData.sub;
+										this.userName = tokenData.name;
+                                        if(!this.allowPick){
+    										this.joinList();
+                                        }
+									} else {
+										this.$Message.error(this.$t("cusMgt.join.parseErr"));
+									}
+								} else {
+									this.$Message.error(this.$t("cusMgt.join.getErr"));
+								}
+							},
+							(err) => {
+								this.$Message.error(this.$t("cusMgt.join.getErr"));
+							}
+						);
+				}
+			});
 
 
-}
+			// 判断移动端还是PC端
+			this.isPC = !/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent);
+			console.log("pc", this.isPC);
+		},
+		computed: {
+			...mapGetters({
+				curSiteConfig: "config/getCurSiteConfig"
+			})
+		},
+		methods: {
+			getGroupListDetails() {
+				return new Promise((r, j) => {
+					let req = {
+						stuListNo: this.listNo
+					};
+					this.$api.common.getGroupNosByIds(req).then(
+						(res) => {
+							this.allowPick = res.optNo === 1;
+							for (let i = 0; i < res.limitCount; i++) {
+								if (!res.nos.includes(i + 1 + "")) {
+									this.irsArr.push(i + 1);
+								}
+							}
+							this.pickIRS = this.irsArr[0];
+							r(200);
+						},
+						(err) => {
+							this.$Message.error(this.$t("ae.ae35"));
+                            j(500)
+						}
+					);
+				});
+			},
+			joinList() {
+				if (!this.id_token && !localStorage.id_token) {
+					this.toLogin();
+					return;
+				}
+				let userToken = this.id_token || localStorage.id_token;
+				let tokenData = jwtDecode(userToken);
+				this.userName = tokenData.name;
+				this.userId = tokenData.sub;
+				this.$api.courseMgmt
+					.qrCodeJoinList({
+						stuListNo: this.listNo,
+						courseId: this.cusId,
+						tmdId: this.userId,
+						seatNo: this.pickIRS,
+						id_token: userToken,
+						school: undefined //加入行政班时需要传对应编码,个人名单不用传
+					})
+					.then(
+						(res) => {
+							if (!res.error) {
+								this.hasSend = false;
+								if (res.status == 2) {
+									//重复加入
+									this.$Message.success(this.$t("cusMgt.join.repJoin"));
+									this.isRep = true;
+								} else if (res.status == 1) {
+									//参数异常
+									this.$Message.success(this.$t("cusMgt.join.joinPErr"));
+								} else if (res.status == 0) {
+									let userIRS = res.stuList.members.find((i) => i.id === this.userId).irs;
+									//正常加入
+									this.$Message.success(this.$t("cusMgt.join.joinOk") + ", IRS: " + userIRS);
+									this.isJoin = true;
+									sessionStorage.setItem("identity", "student");
+								} else if (res.status == 4) {
+									// 个人名单未开放加入
+									this.$Message.warning(this.$t("cusMgt.join.joinLock"));
+								} else if (res.status == 6) {
+									// 需要审核通过再加入
+									this.$Message.warning(this.$t("newCusMgt.joinTip1"));
+									this.hasSend = true;
+								} else if (res.status == 5) {
+									// 人数已满,需要审核通过再加入
+									this.$Message.warning(this.$t("newCusMgt.joinTip2"));
+								} else if (res.status == 7) {
+									// 二维码设置已经过期
+									this.$Message.warning(this.$t("newCusMgt.joinTip3"));
+								}
+							} else {
+								this.$Message.error(this.$t("cusMgt.join.joinErr"));
+							}
+						},
+						(err) => {
+							this.$Message.error(this.$t("cusMgt.join.joinErr"));
+						}
+					);
+			},
+			toLogin() {
+				// let type = process.env.NODE_ENV //product | development
+				let accUrl = this.curSiteConfig.accAPIUrl;
+				let clientId = this.curSiteConfig.clientID;
+				let callback = decodeURIComponent(window.location.href);
+				let state = this.$jsFn.getBtwRandom(1000, 9999);
+				let nonce = this.$jsFn.uuid();
+				let loginUrl = `${accUrl}/oauth2/authorize?response_type=code&client_id=${clientId}&state=${state}&nonce=${nonce}&redirect_uri=${encodeURIComponent(callback)}`;
+				window.location.href = loginUrl;
+			}
+		}
+	};
 </script>
 </script>
 <style scoped lang="less">
 <style scoped lang="less">
-.success-tips {
-    color: white;
-    font-size: 16px;
-    margin-top: 20px;
-}
-.join-wrap {
-    display: flex;
-    flex-direction: column;
-    justify-content: space-evenly;
-    align-items: center;
-    width: 100%;
-    height: 100%;
-    background-image: url("../../assets/image/bak_light.jpg");
-}
-.join-btn {
-    cursor: pointer;
-    width: 100%;
-    margin: auto;
-    margin-top: 60px;
-    text-align: center;
-    border: 1px solid rgba(25, 190, 107, 0.5);
-    // color: rgba(25, 190, 107, 1);
-    color: white;
-    padding: 4px 30px;
-    border-radius: 5px;
-    font-size: 16px;
-    letter-spacing: 2px;
-    font-weight: 400;
-    user-select: none;
-    background: rgba(25, 190, 107, 0.5);
-}
-.course-name {
-    color: white;
-    margin-bottom: 15px;
-    font-size: 30px;
-    // font-family: cursive;
-}
-.join-main-box {
-    max-width: 90%;
-    width: fit-content;
-    text-align: center;
-    .info-item {
-        margin-top: 20px;
-        font-size: 15px;
-        width: fit-content;
-    }
-    .info-lable {
-        color: #a5a5a5;
-    }
-    .info-value {
-        color: #eeeeee;
-    }
-}
-.join-title {
-    position: absolute;
-    top: 15px;
-    text-align: center;
-    width: 100%;
-    left: 0px;
-    border-bottom: 1px dashed;
-    padding-bottom: 8px;
-}
-</style>
+	.success-tips {
+		color: white;
+		font-size: 16px;
+		margin-top: 20px;
+	}
+	.join-wrap {
+		display: flex;
+		flex-direction: column;
+		justify-content: space-evenly;
+		align-items: center;
+		width: 100%;
+		height: 100%;
+		background-image: url("../../assets/image/bak_light.jpg");
+	}
+	.join-btn {
+		cursor: pointer;
+		width: 100%;
+		margin: auto;
+		margin-top: 60px;
+		text-align: center;
+		border: 1px solid rgba(25, 190, 107, 0.5);
+		// color: rgba(25, 190, 107, 1);
+		color: white;
+		padding: 4px 30px;
+		border-radius: 5px;
+		font-size: 16px;
+		letter-spacing: 2px;
+		font-weight: 400;
+		user-select: none;
+		background: rgba(25, 190, 107, 0.5);
+	}
+	.course-name {
+		color: white;
+		margin-bottom: 15px;
+		font-size: 30px;
+		// font-family: cursive;
+	}
+	.join-main-box {
+		max-width: 90%;
+		width: fit-content;
+		text-align: center;
+		.info-item {
+			margin-top: 20px;
+			font-size: 15px;
+			width: fit-content;
+		}
+		.info-lable {
+			color: #a5a5a5;
+		}
+		.info-value {
+			color: #eeeeee;
+		}
+	}
+	.join-title {
+		position: absolute;
+		top: 15px;
+		text-align: center;
+		width: 100%;
+		left: 0px;
+		border-bottom: 1px dashed;
+		padding-bottom: 8px;
+	}
+</style>

+ 39 - 0
TEAMModelOS/ClientApp/src/view/mycourse/MyCourse.vue

@@ -146,6 +146,13 @@
 						<InputNumber :max="999" :min="0" size="small" v-model="qrSetting.limitCount" style="width:70px;margin-left: 5px;" @on-blur="onSettingChange"></InputNumber>
 						<InputNumber :max="999" :min="0" size="small" v-model="qrSetting.limitCount" style="width:70px;margin-left: 5px;" @on-blur="onSettingChange"></InputNumber>
 					</span>
 					</span>
 				</p>
 				</p>
+				<p style="display: flex;justify-content:space-between;margin:15px 0">
+					<span>
+						<!-- {{ $t('newCusMgt.agreePickIRS') }} -->
+						允许自选座号
+						<i-switch v-model="agreePick" size="small" @on-change="agreePickIRS" />
+					</span>
+				</p>
 				<p style="display: flex;justify-content:space-between;margin:15px 0" v-if="agreeJoin">
 				<p style="display: flex;justify-content:space-between;margin:15px 0" v-if="agreeJoin">
 					<span>
 					<span>
 						{{ $t('newCusMgt.review') }}
 						{{ $t('newCusMgt.review') }}
@@ -411,6 +418,7 @@
 				fIds: [],
 				fIds: [],
 				stuListNo: "",
 				stuListNo: "",
 				agreeJoin: true,
 				agreeJoin: true,
+				agreePick: true,
 				qrConfig: {
 				qrConfig: {
 					title: this.$t("cusMgt.qrCodeLabel"),
 					title: this.$t("cusMgt.qrCodeLabel"),
 					url: "", //二维码内容 不用转短网址,组件内部处理
 					url: "", //二维码内容 不用转短网址,组件内部处理
@@ -619,11 +627,13 @@
 							(res) => {
 							(res) => {
 								this.qrSetting = {
 								this.qrSetting = {
 									review: res.list.review ? true : false,
 									review: res.list.review ? true : false,
+									allowPick: res.list.optNo ? true : false,
 									limitCount: res.list.limitCount,
 									limitCount: res.list.limitCount,
 									qrcodeDays: res.list.qrcodeDays || 1,
 									qrcodeDays: res.list.qrcodeDays || 1,
 									qrcodeExpire:res.list.qrcodeExpire
 									qrcodeExpire:res.list.qrcodeExpire
 								}
 								}
 								this.teaClassList[this.curClassIndex].review = res.list.review
 								this.teaClassList[this.curClassIndex].review = res.list.review
+								this.teaClassList[this.curClassIndex].optNo = res.list.optNo
 								this.teaClassList[this.curClassIndex].limitCount = res.list.limitCount
 								this.teaClassList[this.curClassIndex].limitCount = res.list.limitCount
 								this.teaClassList[this.curClassIndex].qrcodeDays = res.list.qrcodeDays
 								this.teaClassList[this.curClassIndex].qrcodeDays = res.list.qrcodeDays
 								this.teaClassList[this.curClassIndex].qrcodeExpire = res.list.qrcodeExpire
 								this.teaClassList[this.curClassIndex].qrcodeExpire = res.list.qrcodeExpire
@@ -800,6 +810,31 @@
 						});
 						});
 				}
 				}
 			},
 			},
+			/* 允许自选座号 */
+			agreePickIRS(status) {
+				let stulist = this.courseGroupList.find((item) => {
+					return item.id == this.teaClassList[this.curClassIndex].groupId;
+				});
+				if (stulist) {
+					stulist.optNo = status ? 1 : 0;
+					let params = stulist;
+					this.listLoading = true;
+					this.$api.common
+						.upsertGroupInfo(params)
+						.then(
+							(res) => {
+								this.$Message.info(`${status ? this.$t("cusMgt.updOk") : this.$t("cusMgt.updOk")}`);
+								this.teaClassList[this.curClassIndex].optNo = stulist.optNo;
+							},
+							(err) => {
+								this.$Message.error(this.$t("cusMgt.updOk"));
+							}
+						)
+						.finally(() => {
+							this.listLoading = false;
+						});
+				}
+			},
 			async showQrCode(index) {
 			async showQrCode(index) {
 				console.log(this.courseInfo);
 				console.log(this.courseInfo);
 				console.log(this.teaClassList[index]);
 				console.log(this.teaClassList[index]);
@@ -1409,6 +1444,7 @@
 										item.listSchool = groupInfo ? groupInfo.school : undefined;
 										item.listSchool = groupInfo ? groupInfo.school : undefined;
 										item.joinLock = groupInfo?.joinLock;
 										item.joinLock = groupInfo?.joinLock;
 										item.review = groupInfo?.review;
 										item.review = groupInfo?.review;
+										item.optNo = groupInfo?.optNo;
 										item.qrcodeDays = groupInfo?.qrcodeDays;
 										item.qrcodeDays = groupInfo?.qrcodeDays;
 										item.qrcodeExpire = groupInfo?.qrcodeExpire;
 										item.qrcodeExpire = groupInfo?.qrcodeExpire;
 										item.limitCount = groupInfo?.limitCount;
 										item.limitCount = groupInfo?.limitCount;
@@ -1560,8 +1596,11 @@
 			selectClass(index) {
 			selectClass(index) {
 				this.curClassIndex = index;
 				this.curClassIndex = index;
 				this.agreeJoin = !this.teaClassList.length ? true : this.teaClassList[index].joinLock ? true : false;
 				this.agreeJoin = !this.teaClassList.length ? true : this.teaClassList[index].joinLock ? true : false;
+				this.agreePick = !this.teaClassList.length ? true : this.teaClassList[index].optNo ? true : false;
 				this.sltTeachers = []
 				this.sltTeachers = []
 				this.assistantsList = []
 				this.assistantsList = []
+				console.error(this.teaClassList[index])
+				console.error(this.teaClassList[index].optNo)
 				this.$forceUpdate()
 				this.$forceUpdate()
 			},
 			},
 			// 保存名单
 			// 保存名单

+ 5 - 3
TEAMModelOS/Controllers/Both/GroupListController.cs

@@ -140,8 +140,10 @@ namespace TEAMModelOS.Controllers
             json.TryGetProperty("school", out JsonElement school);
             json.TryGetProperty("school", out JsonElement school);
             json.TryGetProperty("courseId", out JsonElement _courseId);
             json.TryGetProperty("courseId", out JsonElement _courseId);
             int seatNo = 0;
             int seatNo = 0;
-            if (json.TryGetProperty("seatNo", out JsonElement _seatNo)  &&  int.TryParse($"{_seatNo}", out seatNo)) { 
-
+            if (json.TryGetProperty("seatNo", out JsonElement _seatNo)  ) {
+                if (int.TryParse($"{_seatNo}", out int __seatNo)) {
+                    seatNo=__seatNo;
+                }
             }
             }
             var client = _azureCosmos.GetCosmosClient();
             var client = _azureCosmos.GetCosmosClient();
             json.TryGetProperty("id_token", out JsonElement id_token);
             json.TryGetProperty("id_token", out JsonElement id_token);
@@ -1372,7 +1374,7 @@ namespace TEAMModelOS.Controllers
                                     {
                                     {
                                         foreach (var t in takes)
                                         foreach (var t in takes)
                                         {
                                         {
-                                            GroupListService.JoinList(list, t.userid, t.type, t.school, t.year);
+                                            GroupListService.JoinList(list, t.userid, t.type, t.school, t.year,t.seatNo);
                                             string stuKey = !string.IsNullOrWhiteSpace(school) ? $"GroupList:StudentWaitinglist:{school}_{t.userid}" : $"GroupList:StudentWaitinglist:{t.userid}";
                                             string stuKey = !string.IsNullOrWhiteSpace(school) ? $"GroupList:StudentWaitinglist:{school}_{t.userid}" : $"GroupList:StudentWaitinglist:{t.userid}";
                                             string stuFiled = $"{list.scope}:{list.id}";
                                             string stuFiled = $"{list.scope}:{list.id}";
                                             var data = await _azureRedis.GetRedisClient(8).HashGetAsync(stuKey, stuFiled);
                                             var data = await _azureRedis.GetRedisClient(8).HashGetAsync(stuKey, stuFiled);

+ 4 - 23
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -3732,7 +3732,7 @@ namespace TEAMModelOS.Controllers
             //    record = stuErrCnt.number;
             //    record = stuErrCnt.number;
             //}
             //}
             //取得CosmosDB該學生該科目現在錯題數
             //取得CosmosDB該學生該科目現在錯題數
-            int avaliable = 0; //現在取得的錯題數
+            HashSet<string> avaliableQidList = new HashSet<string>(); //最終去重後可取得的錯題ID
             List<string> itemIdList = new List<string>();
             List<string> itemIdList = new List<string>();
             var client = _azureCosmos.GetCosmosClient();
             var client = _azureCosmos.GetCosmosClient();
             string qryAdd = (!string.IsNullOrWhiteSpace(code)) ? $" AND c.school = '{code}' " : string.Empty;
             string qryAdd = (!string.IsNullOrWhiteSpace(code)) ? $" AND c.school = '{code}' " : string.Empty;
@@ -3748,26 +3748,7 @@ namespace TEAMModelOS.Controllers
                 pk = $"ErrorItems-{code}";
                 pk = $"ErrorItems-{code}";
             };
             };
 
 
-            //await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: qry, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{pk}") }))
-            //{
-            //    using var json = await JsonDocument.ParseAsync(item.ContentStream);
-            //    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
-            //    {
-            //        foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
-            //        {
-            //            List<string> itemIdListRow = obj.GetProperty("itemIds").ToObject<List<string>>();
-            //            itemIdList = itemIdList.Union(itemIdListRow).ToList();
-            //        }
-            //    }
-            //}
-
-            //avaliable = itemIdList.Count;
-
-            //return Ok(new { record, avaliable });
-
             Dictionary<string, List<string>> dict = new();
             Dictionary<string, List<string>> dict = new();
-
-
             await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: qry, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{pk}") }))
             await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: qry, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{pk}") }))
             {
             {
                 using var json = await JsonDocument.ParseAsync(item.ContentStream);
                 using var json = await JsonDocument.ParseAsync(item.ContentStream);
@@ -3800,7 +3781,7 @@ namespace TEAMModelOS.Controllers
                     {
                     {
                         if (_azureStorage.GetBlobContainerClient(schCode).GetBlobClient($"{blob}/{qid}.json").Exists())
                         if (_azureStorage.GetBlobContainerClient(schCode).GetBlobClient($"{blob}/{qid}.json").Exists())
                         {// 去除blob不存在項目
                         {// 去除blob不存在項目
-                            avaliable++;
+                            avaliableQidList.Add(qid);
                         }
                         }
                     }
                     }
                     
                     
@@ -3813,12 +3794,12 @@ namespace TEAMModelOS.Controllers
                     {
                     {
                         if (_azureStorage.GetBlobContainerClient(teacherid).GetBlobClient($"{blob}/{qid}.json").Exists())
                         if (_azureStorage.GetBlobContainerClient(teacherid).GetBlobClient($"{blob}/{qid}.json").Exists())
                         {
                         {
-                            avaliable++;
+                            avaliableQidList.Add(qid);
                         }
                         }
                     }
                     }
                 }
                 }
             }
             }
-            return Ok(new { record, avaliable });
+            return Ok(new { record, avaliable = avaliableQidList.Count });
         }
         }
 
 
         //取得錯題庫新增數
         //取得錯題庫新增數

+ 1 - 1
TEAMModelOS/TEAMModelOS.csproj

@@ -110,7 +110,7 @@
     <!-- Build Target: Restore NPM packages using npm -->
     <!-- Build Target: Restore NPM packages using npm -->
     <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
     <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
 
 
-    <Exec WorkingDirectory="$(SpaRoot)" Command="cnpm install" />
+    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
   </Target>
   </Target>
 
 
   <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
   <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">