Forráskód Böngészése

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

zhouj1203@hotmail.com 1 éve
szülő
commit
8e506f1542

+ 1 - 1
TEAMModelOS.FunctionV4/ServiceBus/ActiveTaskTopic.cs

@@ -1552,7 +1552,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                                     }
 
                                 }
-                                // await BIStats.SetTypeAddStats(client, _dingDing,lessonRecord.school, "Less", 0, 1, lessonRecord.clientInteractionCount);//BI统计增/减量
+                                await BIStats.SetTypeAddStats(client, _dingDing,lessonRecord.school, "Less", 0, 1, lessonRecord.clientInteractionCount);//BI统计增/减量
                                 break;
                             //更新 时间线
                             case "up-TimeLine":

+ 156 - 142
TEAMModelOS.SDK/Models/Cosmos/Common/Activity.cs

@@ -339,6 +339,10 @@ namespace TEAMModelOS.SDK.Models
         /// periodAndSubject同时匹配学科和学段
         /// </summary>
         public string distribute { get; set; }
+        /// <summary>
+        /// 是否需要进行细项评分0,不需要对细项评分 1 需要对细项评分,细项评分自动计为总分。
+        /// </summary>
+        public int scoreDetail { get; set; } = 0;
     }
     public class RuleConfig {
         public string id { get; set; }
@@ -390,6 +394,10 @@ namespace TEAMModelOS.SDK.Models
         /// periodAndSubject同时匹配学科和学段
         /// </summary>
         public string distribute { get; set; }
+        /// <summary>
+        /// 是否需要进行细项评分0,不需要对细项评分 1 需要对细项评分,细项评分自动计为总分。
+        /// </summary>
+        public int scoreDetail { get; set; }
 
     }
     public class RuleConfigTree : RuleConfig
@@ -471,148 +479,6 @@ namespace TEAMModelOS.SDK.Models
         /// </summary>
         public List<string> item { get; set; } = new List<string>();
     }
-    /// <summary>
-    /// 在线培训模块的数据结构
-    /// </summary>
-    public class Training : CosmosEntity
-    {
-        //id  活动id,
-        public Training()
-        {
-            code = "Training";
-            pk = "Training";
-        }
-        /// <summary>
-        ///  //参加活动获得积分。根据最后评委打分,0-100的比例获得积分,不足一分按一分计算。
-        /// </summary>
-        public int balance { get; set; }
-        /// <summary>
-        /// "online", "submit", "exam"模块
-        /// </summary>
-        public List<string> modules { get; set; } = new List<string>();
-        public List<TrainingAbility> abilities { get; set; } = new List<TrainingAbility>();
-        public TrainingOnline online { get; set; }
-        public TrainingSubmit submit { get; set; }
-        public TrainingExam exam { get; set; }
-        public long stime { get; set; }
-        public long etime { get; set; }
-    }
-
-    public class TrainingOnline
-    {
-        /// <summary>
-        /// //最少学习多少积分 
-        /// </summary>
-        public int least { get; set; }
-        /// <summary>
-        ///  //至少修三个能力点
-        /// </summary>
-        public int limit { get; set; }
-    }
-    public class TrainingSubmit
-    {
-        public int balance { get; set; } //单个认证合格获得的积分0-5
-    }
-    public class TrainingExam
-    {
-        public int balance { get; set; } //单个认证合格获得的积分0-5
-    }
-    /// <summary>
-    /// 教研中心技能点
-    /// </summary>
-    public class TrainingAbility
-    {
-        /// <summary>
-        /// //学习完成获得积分,0-10
-        /// </summary>
-        public int balance { get; set; }
-        public string id { get; set; }
-        /// <summary>
-        ///新建字段 维度 对应 原来的 subjectId 学科
-        /// </summary>
-        public string dimension { get; set; }
-        /// <summary>
-        /// 标号 A1  A2 A3....
-        /// </summary>
-        public string no { get; set; }
-
-        /// <summary>
-        /// 册别name
-        /// </summary>
-        [Required(ErrorMessage = "{0} 必须填写")]
-        public string name { get; set; }
-        public List<AbilityTask> abilityTasks { get; set; } = new List<AbilityTask>();
-
-        public string desc { get; set; }
-        public string sug { get; set; }
-        public List<AbilityStds> stds { get; set; } = new List<AbilityStds>();
-        /// <summary>
-        ///自测练习题试卷 blob地址
-        /// </summary>
-        public string blob { get; set; }
-        /// <summary>
-        ///默认未设置0 必修1 通识2 选修3
-        /// </summary>
-        public int currency { get; set; }
-    }
-
-    public class Research : CosmosEntity
-    {
-        //id  活动id,
-        public Research()
-        {
-            code = "Research";
-            pk = "Research";
-        }
-        /// <summary>
-        ///  "sign", //关联HiTeach课例的时候,可自动关联。   "homework", //作业   "examLite", //评测   "vote", //投票  "survey" //问卷,  此处几个活动的id是 赛课大活动的id
-        /// </summary>
-        public List<string> modules { get; set; } = new List<string>();
-        public long stime { get; set; }
-        public long etime { get; set; }
-        public ResearchSign sign { get; set; }
-        public ResearchExamLite examLite { get; set; }
-        public ResearchHomework homework { get; set; }
-        public ResearchSurvey survey { get; set; }
-        public ResearchVote vote { get; set; }
-    }
-    public class ResearchSign
-    {
-        /// <summary>
-        /// //活动签到活得0-5
-        /// </summary>
-        public int balance { get; set; }
-    }
-    public class ResearchHomework
-    {
-        public List<string> type { get; set; } = new List<string>();
-
-        /// <summary>
-        /// //活动签到活得0-5
-        /// </summary>
-        public int balance { get; set; }
-    }
-    public class ResearchExamLite
-    {
-        /// <summary>
-        /// //完成评测练习,可获取0-5
-        /// </summary>
-        public int balance { get; set; }
-    }
-    public class ResearchVote
-    {
-        /// <summary>
-        /// //完成评测练习,可获取0-5
-        /// </summary>
-        public int balance { get; set; }
-    }
-    public class ResearchSurvey
-    {
-        /// <summary>
-        /// //完成评测练习,可获取0-5
-        /// </summary>
-        public int balance { get; set; }
-    }
 
     /// <summary>
     /// 本次活动的评审专家
@@ -859,6 +725,10 @@ namespace TEAMModelOS.SDK.Models
         /// </summary>
         public int status { get; set; } = -1;
         public double score { get; set; } = -1;
+        /// <summary>
+        /// 评分依据规则的细项得分,score 专家对细项的评分
+        /// </summary>
+        public List<RuleConfig> detailScore { get; set; }= new List<RuleConfig>();
 
     }
     
@@ -965,4 +835,148 @@ namespace TEAMModelOS.SDK.Models
         public string val { get; set; }
       
     }
+
+
+    /// <summary>
+    /// 在线培训模块的数据结构
+    /// </summary>
+    public class Training : CosmosEntity
+    {
+        //id  活动id,
+        public Training()
+        {
+            code = "Training";
+            pk = "Training";
+        }
+        /// <summary>
+        ///  //参加活动获得积分。根据最后评委打分,0-100的比例获得积分,不足一分按一分计算。
+        /// </summary>
+        public int balance { get; set; }
+        /// <summary>
+        /// "online", "submit", "exam"模块
+        /// </summary>
+        public List<string> modules { get; set; } = new List<string>();
+        public List<TrainingAbility> abilities { get; set; } = new List<TrainingAbility>();
+        public TrainingOnline online { get; set; }
+        public TrainingSubmit submit { get; set; }
+        public TrainingExam exam { get; set; }
+        public long stime { get; set; }
+        public long etime { get; set; }
+    }
+
+    public class TrainingOnline
+    {
+        /// <summary>
+        /// //最少学习多少积分 
+        /// </summary>
+        public int least { get; set; }
+        /// <summary>
+        ///  //至少修三个能力点
+        /// </summary>
+        public int limit { get; set; }
+    }
+    public class TrainingSubmit
+    {
+        public int balance { get; set; } //单个认证合格获得的积分0-5
+    }
+    public class TrainingExam
+    {
+        public int balance { get; set; } //单个认证合格获得的积分0-5
+    }
+    /// <summary>
+    /// 教研中心技能点
+    /// </summary>
+    public class TrainingAbility
+    {
+        /// <summary>
+        /// //学习完成获得积分,0-10
+        /// </summary>
+        public int balance { get; set; }
+        public string id { get; set; }
+        /// <summary>
+        ///新建字段 维度 对应 原来的 subjectId 学科
+        /// </summary>
+        public string dimension { get; set; }
+        /// <summary>
+        /// 标号 A1  A2 A3....
+        /// </summary>
+        public string no { get; set; }
+
+        /// <summary>
+        /// 册别name
+        /// </summary>
+        [Required(ErrorMessage = "{0} 必须填写")]
+        public string name { get; set; }
+        public List<AbilityTask> abilityTasks { get; set; } = new List<AbilityTask>();
+
+        public string desc { get; set; }
+        public string sug { get; set; }
+        public List<AbilityStds> stds { get; set; } = new List<AbilityStds>();
+        /// <summary>
+        ///自测练习题试卷 blob地址
+        /// </summary>
+        public string blob { get; set; }
+        /// <summary>
+        ///默认未设置0 必修1 通识2 选修3
+        /// </summary>
+        public int currency { get; set; }
+    }
+
+    public class Research : CosmosEntity
+    {
+        //id  活动id,
+        public Research()
+        {
+            code = "Research";
+            pk = "Research";
+        }
+        /// <summary>
+        ///  "sign", //关联HiTeach课例的时候,可自动关联。   "homework", //作业   "examLite", //评测   "vote", //投票  "survey" //问卷,  此处几个活动的id是 赛课大活动的id
+        /// </summary>
+        public List<string> modules { get; set; } = new List<string>();
+        public long stime { get; set; }
+        public long etime { get; set; }
+        public ResearchSign sign { get; set; }
+        public ResearchExamLite examLite { get; set; }
+        public ResearchHomework homework { get; set; }
+        public ResearchSurvey survey { get; set; }
+        public ResearchVote vote { get; set; }
+    }
+    public class ResearchSign
+    {
+        /// <summary>
+        /// //活动签到活得0-5
+        /// </summary>
+        public int balance { get; set; }
+    }
+    public class ResearchHomework
+    {
+        public List<string> type { get; set; } = new List<string>();
+
+        /// <summary>
+        /// //活动签到活得0-5
+        /// </summary>
+        public int balance { get; set; }
+    }
+    public class ResearchExamLite
+    {
+        /// <summary>
+        /// //完成评测练习,可获取0-5
+        /// </summary>
+        public int balance { get; set; }
+    }
+    public class ResearchVote
+    {
+        /// <summary>
+        /// //完成评测练习,可获取0-5
+        /// </summary>
+        public int balance { get; set; }
+    }
+    public class ResearchSurvey
+    {
+        /// <summary>
+        /// //完成评测练习,可获取0-5
+        /// </summary>
+        public int balance { get; set; }
+    }
 }

+ 139 - 8
TEAMModelOS.SDK/Models/Service/Common/ActivityService.cs

@@ -19,7 +19,32 @@ namespace TEAMModelOS.SDK
 {
     public static class ActivityService
     {
+        /// <summary>
+        /// 分配作品
+        /// </summary>
+        /// <param name="_azureCosmos"></param>
+        /// <param name="activity"></param>
+        /// <returns></returns>
 
+        public static async Task AllocationTask(AzureCosmosFactory _azureCosmos, Activity activity,List<string> periodSubjectKeys)
+        {
+            //检查报名,学段和科目是否匹配
+            string enrollSQL = "select value  c from c where c.pk='ActivityEnroll'";
+            var resultActivityEnroll=  await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetList<ActivityEnroll>(enrollSQL, $"ActivityEnroll-{activity.id}");
+            List<ActivityEnroll> activityEnrollsInvalid = new List<ActivityEnroll>();
+            foreach (ActivityEnroll enroll in resultActivityEnroll.list) 
+            {
+                var period =  enroll.contest?.enrollInfos?.Find(z => z.code.Equals("period"));
+                var subject= enroll.contest?.enrollInfos?.Find(z => z.code.Equals("subject"));
+                string periodSubjectKey = $"{period?.val}-{subject.val}";
+                if (periodSubjectKeys.Contains(periodSubjectKey))
+                {
+                }
+                else { 
+                    
+                }
+            }
+        }
         /// <summary>
         /// 删除活动关联的数据
         /// </summary>
@@ -193,7 +218,7 @@ namespace TEAMModelOS.SDK
             return activities;
         }
 
-        public static async Task<ReviewRule> UpsertReviewRule(ReviewRuleTree reviewRuleTree,Activity activity,AzureCosmosFactory _azureCosmos) 
+        public static async Task<(ReviewRule reviewRule, int invalidCode, string msg)> UpsertReviewRule(ReviewRuleTree reviewRuleTree,Activity activity, Contest contest, AzureCosmosFactory _azureCosmos) 
         {
             var nodes = new List<RuleConfig>();
             nodes= TreeToList(reviewRuleTree.trees, nodes);
@@ -209,15 +234,121 @@ namespace TEAMModelOS.SDK
                 sourceName=activity.name,
                 taskCount=reviewRuleTree.taskCount,
                 scoreRule=reviewRuleTree.scoreRule,
-                distribute=reviewRuleTree.distribute
+                distribute=reviewRuleTree.distribute,
+                scoreDetail=reviewRuleTree.scoreDetail,
             };
-            await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS,Constant.Normal).UpsertItemAsync(reviewRule,new Azure.Cosmos.PartitionKey(reviewRule.code));
-            if (reviewRuleTree.upsertAsTemplate==1) {
-                reviewRule.code="ReviewRule-template";
-                reviewRule.type="template";
-             await   _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).UpsertItemAsync(reviewRule,new Azure.Cosmos.PartitionKey(reviewRule.code));
+            //代码顺序不能动
+            var checkReult = CheckReviewRule(reviewRule, contest);
+            if (checkReult.invalidCode==200) {
+                await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).UpsertItemAsync(reviewRule, new Azure.Cosmos.PartitionKey(reviewRule.code));
+                if (reviewRuleTree.upsertAsTemplate==1)
+                {
+                    reviewRule.code="ReviewRule-template";
+                    reviewRule.type="template";
+                    await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).UpsertItemAsync(reviewRule, new Azure.Cosmos.PartitionKey(reviewRule.code));
+                }
+            }
+            return (reviewRule,checkReult.invalidCode,checkReult.msg);
+        }
+
+        public static (int invalidCode, string msg) CheckReviewRule(ReviewRule reviewRule,Contest contest)
+        {
+            int invalidCode = -1;
+            string msg = "";
+            if (reviewRule.taskCount<=0) {
+                invalidCode=28;
+                msg="作品分配次数至少一次!";
+            }
+            if (reviewRule.taskCount==1  && reviewRule.scoreRule.Equals("only"))
+            {
+                invalidCode=200;
+            }
+            else
+            {
+                invalidCode=21;//分配次数一次,必须匹配only
+                msg="分配次数1次,必须匹配【默认统分】规则";
+            }
+            if (reviewRule.taskCount>=2  && (reviewRule.scoreRule.Equals("avg")||reviewRule.scoreRule.Equals("top")))
+            {
+                invalidCode=200;
+            }
+            else {
+                invalidCode=22;//分配次数2次,必须匹配avg,top
+                msg="分配次数2次,必须匹配【按平均分】,【按最高分】";
+            }
+            if (reviewRule.taskCount>=3  && (reviewRule.scoreRule.Equals("avg")||reviewRule.scoreRule.Equals("top")||reviewRule.scoreRule.Equals("rmLowAvg")||reviewRule.scoreRule.Equals("rmTopAvg")))
+            {
+                invalidCode=200;
+            }
+            else
+            {
+                invalidCode=23;//分配次数2次,必须匹配avg,top,rmLowAvg,rmTopAvg
+                msg="分配次数3次,必须匹配必须匹配【按平均分】,【按最高分】,【去掉最低分的平均分】,【去掉最高分的平均分】";
+            }
+            if (reviewRule.taskCount>=4  && (reviewRule.scoreRule.Equals("avg")||reviewRule.scoreRule.Equals("top")||reviewRule.scoreRule.Equals("rmLowAvg")||reviewRule.scoreRule.Equals("rmTopAvg")||reviewRule.scoreRule.Equals("rmLowTopAvg")))
+            {
+                invalidCode=200;
+            }
+            else
+            {
+                invalidCode=24;//分配次数2次,必须匹配avg,top,rmLowAvg,rmTopAvg,rmLowTopAvg
+                msg="分配次数4次,必须匹配必须匹配【按平均分】,【按最高分】,【去掉最低分的平均分】,【去掉最高分的平均分】,【去掉最高分和最低分的平均分】";
+            }
+            if (invalidCode==200) {
+                if (!string.IsNullOrWhiteSpace(reviewRule.distribute))
+                {
+                    if (reviewRule.distribute.Equals("period"))
+                    {
+                        var period = contest.sign?.fields?.Find(z => z.field.Equals("period"));
+                        if (period!= null)
+                        {
+                            invalidCode = 200;
+                        }
+                        else
+                        {
+                            invalidCode=25;
+                            msg="作品分配匹配规则为【学段】,但报名填写表单未配置。";
+                        }
+                    }
+                    else if (reviewRule.distribute.Equals("subject"))
+                    {
+                        var subject = contest.sign?.fields?.Find(z => z.field.Equals("subject"));
+                        if (subject!= null)
+                        {
+                            invalidCode = 200;
+                        }
+                        else
+                        {
+                            invalidCode=26;
+                            msg="作品分配匹配规则为【学科】,但报名填写表单未配置。";
+                        }
+                    }
+                    else if (reviewRule.distribute.Equals("periodAndSubject"))
+                    {
+                        var period = contest.sign?.fields?.Find(z => z.field.Equals("period"));
+                        var subject = contest.sign?.fields?.Find(z => z.field.Equals("subject"));
+                        if (subject!= null && period!= null)
+                        {
+                            invalidCode = 200;
+                        }
+                        else
+                        {
+                            invalidCode=27;
+                            msg="作品分配匹配规则为【学段,学科】,但报名填写表单未配置。";
+                        }
+                    }
+                    else {
+                        invalidCode=30;
+                        msg=$"作品分配匹配规则未识别【{reviewRule.distribute}】。";
+                    }
+                }
+                else {
+                    invalidCode=29;
+                    msg="作品分配匹配规则不能为空。";
+                }
+                
             }
-            return reviewRule;
+            return (invalidCode,msg);
         }
 
         public static List<RuleConfig> TreeToList(List<RuleConfigTree> trees, List<RuleConfig> nodes) {

+ 76 - 4
TEAMModelOS.TEST/Program.cs

@@ -11,11 +11,40 @@ namespace TEAMModelOS.TEST
     {
         static void Main(string[] args)
         {
+            // 作品数据
+            var works = new List<Work>
+        {
+            new Work { WorkId = "1", Subject = "语文" },
+            new Work { WorkId = "2", Subject = "数学" },
+            new Work { WorkId = "3", Subject = "英语" },
+            new Work { WorkId = "4", Subject = "数学" },
+            new Work { WorkId = "5", Subject = "英语" },
+            new Work { WorkId = "6", Subject = "语文" },
+            new Work { WorkId = "7", Subject = "语文" }
+        };
+
+            // 专家数据
+            var experts = new List<ExpertS>
+        {
+            //new ExpertS { ExpertId = "a", Subjects = new List<string> { "语文", "数学" ,"英语"} },
+            new ExpertS { ExpertId = "b", Subjects = new List<string> { "数学" } },
+            //new ExpertS { ExpertId = "c", Subjects = new List<string> { "英语", "英语" } },
+            new ExpertS { ExpertId = "d", Subjects = new List<string> { "数学" } },
+            new ExpertS { ExpertId = "e", Subjects = new List<string> { "英语", "数学" } },
+            //new ExpertS { ExpertId = "f", Subjects = new List<string> { "语文" } }
+        };
+
+            // 分配作品给专家
+            int N = 1; // 指定评审次数
+            var assignments = AssignWorksToExperts(works, experts, N);
+
+            // 输出结果
+            foreach (var assignment in assignments)
+            {
+                Console.WriteLine($"WorkId: {assignment.WorkId}, ExpertId: {assignment.ExpertId}");
+            }
 
-            //List<string> groupNames = new List<string>() { "組別4", "組別2", "組別3", "組別4", "組別1", "組別1" };
-            //groupNames =groupNames.OrderBy(x => x).ToList();
-            //var jsonAuth = System.IO.File.ReadAllText("C:\\Users\\CrazyIter\\Downloads\\492266088181141504\\ActivityInfo.json", Encoding.UTF8);
-            //var jsonData = jsonAuth.ToObject<LessonRecordActivityInfo>();
+             
             string path = "C:\\Users\\CrazyIter\\Downloads\\消费清单(2022-2023)\\bill";
             List<List<string>> inputArray = new List<List<string>>
             {  
@@ -39,6 +68,33 @@ namespace TEAMModelOS.TEST
                 Console.WriteLine($"id: {item.Id}, count: {item.Count}, pid: {item.Pid}");
             }
         }
+
+        static List<Assignment> AssignWorksToExperts(List<Work> works, List<ExpertS> experts, int N)
+        {
+            var assignments = new List<Assignment>();
+            foreach (var work in works)
+            {
+                for (int i = 0; i < N; i++)
+                {
+                    var availableExperts = experts
+                        .OrderBy(expert => assignments.Count(a => a.ExpertId == expert.ExpertId))
+                        .Where(expert => expert.Subjects.Contains(work.Subject) && !assignments.Any(a => a.WorkId == work.WorkId && a.ExpertId == expert.ExpertId))
+                        .ToList();
+                    if (availableExperts.Count > 0)
+                    {
+                        var selectedExpert = availableExperts.First();
+                        assignments.Add(new Assignment { WorkId = work.WorkId, ExpertId = selectedExpert.ExpertId });
+                    }
+                    else
+                    {
+                        Console.WriteLine($"No available expert for WorkId {work.WorkId} and subject {work.Subject} in attempt {i + 1}.");
+                        break;
+                    }
+                }
+            }
+
+            return assignments;
+        }
         static List<ClassifiedItem> ClassifyHierarchy(List<List<string>> inputArray)
         {
             Dictionary<string, int> hierarchyCount = new Dictionary<string, int>();
@@ -89,7 +145,23 @@ namespace TEAMModelOS.TEST
             public string Pid { get; set; }
         }
     }
+    class Work
+    {
+        public string WorkId { get; set; }
+        public string Subject { get; set; }
+    }
 
+    class ExpertS
+    {
+        public string ExpertId { get; set; }
+        public List<string> Subjects { get; set; }
+    }
+
+    class Assignment
+    {
+        public string WorkId { get; set; }
+        public string ExpertId { get; set; }
+    }
     public class LessonBase {
         public string? id { get; set; }
         public string? duration { get; set; }

+ 2 - 2
TEAMModelOS/ClientApp/src/view/login/page/Student.less

@@ -182,7 +182,7 @@
 }
 .left-box {
     padding: 0px 70px;
-    height: 400px;
+    height: 431px;
     display: flex;
     align-items: center;
     justify-content: center;
@@ -193,7 +193,7 @@
     padding: 40px 120px;
     // box-shadow: -2px 0px 15px 0px rgba(75, 113, 136, 0.9);
     display: flex;
-    height: 400px;
+    height: 431px;
     min-width: 600px;
 }
 .student-login-img {

+ 24 - 0
TEAMModelOS/ClientApp/src/view/login/page/StudentMobile.vue

@@ -96,6 +96,19 @@
                         <div class="errlable">{{ schoolErrText }}</div>
                         <Checkbox v-model="isRememberForm" style="color:#fff;float:right">{{ $t('login.title.rememberPsw') }}</Checkbox>
                     </Form>
+                    <!-- 三方登录 -->
+                    <template v-if="srvAdr == 'Global'">
+                        <Divider class="login-divider">{{$t('login.communy.title')}}</Divider>
+                        <div class="other-login-box" style="margin-top:0px">
+                            <!-- 教育雲 -->
+                            <div class="other-login-item" @click="oauthLogin('educloudtwl')">
+                                <v-icon iconClass="educloudtw" class="icon-educlowudtw"></v-icon>
+                                <p class="other-login-text">
+                                {{$t('login.communy.educloudtw')}}
+                                </p>
+                            </div>
+                        </div>
+                    </template>
                 </TabPane>
                 <!-- 醍摩豆表单登录 -->
                 <TabPane :label="$t('login.title.IDLogin')" name="name2">
@@ -136,22 +149,32 @@
                                   <Icon custom="iconfont icon-dingding" class="other-login-icon" />
                                   <p class="other-login-text">DingDing</p>
                               </div> -->
+                        <!-- FB -->
                         <div class="other-login-item" @click="oauthLogin('facebook')" v-if="srvAdr == 'Global'">
                             <Icon type="logo-facebook" class="other-login-icon" />
                             <p class="other-login-text">
                               {{$t('login.communy.fb')}}
                             </p>
                         </div>
+                        <!-- Google -->
                         <div class="other-login-item" @click="oauthLogin('google')" v-if="srvAdr == 'Global'">
                             <Icon custom="iconfont icon-google" class="other-login-icon" />
                             <p class="other-login-text">
                               {{$t('login.communy.google')}}
                             </p>
                         </div>
+                        <!-- wechat -->
                         <div style="margin:auto" class="other-login-item" v-if="srvAdr == 'China'" @click="oauthLogin('wechat')">
                             <Icon custom="iconfont icon-wechat" class="other-login-icon" />
                             <p class="other-login-text">{{$t('login.communy.wechat')}}</p>
                         </div>
+                        <!-- 教育雲 -->
+                        <div class="other-login-item" @click="oauthLogin('educloudtw')" v-if="srvAdr == 'Global'">
+                            <v-icon iconClass="educloudtw" class="icon-educlowudtw"></v-icon>
+                            <p class="other-login-text">
+                            {{$t('login.communy.educloudtw')}}
+                            </p>
+                        </div>
                     </div>
                 </TabPane>
             </Tabs>
@@ -163,6 +186,7 @@ import { EventSourcePolyfill } from 'event-source-polyfill';
 import QRCode from 'qrcodejs2'
 import { User } from '@/service/User'
 import { mapState, mapGetters } from 'vuex'
+import "@/icons/svg/educloudtw.svg";
 
 export default {
     data() {

+ 1 - 1
TEAMModelOS/ClientApp/src/view/signupActivity/createActivity.vue

@@ -36,7 +36,7 @@
                         <FormItem label="承办" class="unit-box">
                             <div v-for="(item, index) in createData.cb" :key="index">
                                 <Input v-special-char v-model="createData.cb[index]" placeholder="请输入承办单位名称" style="width: calc(100% - 30px);" />
-                                <Icon type="md-trash" size="20" color="#6e6e6e" v-show="index" @click="editUnit('delete', 'zb', index)" />
+                                <Icon type="md-trash" size="20" color="#6e6e6e" v-show="index" @click="editUnit('delete', 'cb', index)" />
                             </div>
                             <Icon type="md-add-circle" size="20" color="#6e6e6e" @click="editUnit('add', 'cb')" />
                         </FormItem>

+ 362 - 0
TEAMModelOS/ClientApp/src/view/signupActivity/infoComponent/editActInfo.vue

@@ -0,0 +1,362 @@
+<template>
+    <div>
+        <Loading v-show="isLoading"></Loading>
+        <Form :model="editInfo" :label-width="80" ref="editInfo" :rules="editDataRule">
+            <FormItem label="名称" prop="name">
+                <Input v-special-char v-model="editInfo.name" placeholder="请输入活动名称" />
+            </FormItem>
+            <FormItem label="主题" prop="subject">
+                <Input v-special-char v-model="editInfo.subject" placeholder="清输入活动主题" />
+            </FormItem>
+            <FormItem label="简介">
+                <Input v-special-char type="textarea" v-model="editInfo.description" placeholder="请输入活动简介" />
+            </FormItem>
+            <FormItem label="地点">
+                <Input v-special-char v-model="editInfo.address" placeholder="请输入活动地点" />
+            </FormItem>
+            <FormItem label="时间" prop="stime">
+                <DatePicker v-model="actAllTime[0]" :editable="false" @on-change="getCreateTime" type="datetimerange" :options="beforeToday" format="yyyy-MM-dd HH:mm" :placeholder="$t('train.create.timeHolder')" style="width: 500px"></DatePicker>
+            </FormItem>
+            <FormItem label="主办" class="unit-box">
+                <div v-for="(item, index) in editInfo.zb" :key="index">
+                    <Input v-special-char v-model="editInfo.zb[index]" placeholder="请输入主办单位名称" style="width: calc(100% - 30px);" />
+                    <Icon type="md-trash" size="20" color="#6e6e6e" v-show="index" @click="editUnit('delete', 'zb', index)" />
+                </div>
+                <Icon type="md-add-circle" size="20" color="#6e6e6e" @click="editUnit('add', 'zb')" />
+            </FormItem>
+            <FormItem label="承办" class="unit-box">
+                <div v-for="(item, index) in editInfo.cb" :key="index">
+                    <Input v-special-char v-model="editInfo.cb[index]" placeholder="请输入承办单位名称" style="width: calc(100% - 30px);" />
+                    <Icon type="md-trash" size="20" color="#6e6e6e" v-show="index" @click="editUnit('delete', 'cb', index)" />
+                </div>
+                <Icon type="md-add-circle" size="20" color="#6e6e6e" @click="editUnit('add', 'cb')" />
+            </FormItem>
+            <FormItem label="免责声明">
+                <Input v-special-char type="textarea" v-model="editInfo.mzsm" placeholder="请输入免责申明..." />
+            </FormItem>
+            <FormItem label="封面">
+                <Upload v-show="!posterFile.url" multiple type="drag" action="" :before-upload="customUpload" :show-upload-list="false" accept=".jpg, .jpeg, .png, .gif">
+                    <div style="padding: 20px 0">
+                        <Icon type="ios-cloud-upload" size="52" style="color: #3399ff"></Icon>
+                        <p>上传主题图片</p>
+                    </div>
+                </Upload>
+                <img v-if="posterFile.url" :src="posterFile.url" alt="" width="500">
+                <p v-show="posterFile.url" class="repeat-upload" @click="repeatUpload">{{$t('td.td58')}}</p>
+            </FormItem>
+            <FormItem label="附件">
+                <Upload multiple type="drag" action="" :before-upload="customUploadFile" :show-upload-list="false" :accept="accept">
+                    <div style="padding: 20px 0">
+                        <Icon type="ios-cloud-upload" size="52" style="color: #3399ff"></Icon>
+                        <p>上传活动文件</p>
+                    </div>
+                </Upload>
+                <template v-if="attachment.length">
+                    <div style="margin-top: 20px;">
+                        <div :key="index" v-for="(item, index) in attachment" class="uploadHm">
+                            <span style="float: right;margin-right: 55px;" v-show="!isUpload">
+                                <Icon type="md-close" size="18" @click="delUpload(index, item)" />
+                            </span>
+                            <div @click="changeUp(index)" style="width: 100%;">
+                                {{ item.name }}
+                                <!-- <Progress :percent="item.progress" status="active" stroke-color="#7CB7F5" /> -->
+                            </div>
+                        </div>
+                    </div>
+                </template>
+            </FormItem>
+            <Button type="success" long @click="saveInfo()">保存</Button>
+        </Form>
+    </div>
+</template>
+
+<script>
+import BlobTool from "@/utils/blobTool.js"
+export default {
+    props: {
+        actInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        editModal: {
+            type: Boolean,
+            default: false,
+        },
+    },
+    data () {
+        return {
+            isLoading: false,
+            editInfo: {stime: null,},
+            actAllTime: [['', '']],
+            accept: '',
+            posterFile: { //封面文件
+                url: '',
+                file: undefined,
+            },
+            isUpload: false,
+            beforeToday: {},
+            attachment: [], //附件
+            attachDel: [], //编辑下删除原有的
+            attachAdd: [], //编辑下新增的
+            editDataRule: {
+                name: [{ required: true, message: '活动名称不能为空', trigger: 'blur' }],
+                subject: [{ required: true, message: '活动主题不能为空', trigger: 'blur' }],
+                stime: [{ required: true, type: 'number', message: '请选择活动时间', trigger: 'blur' }],
+            },
+        }
+    },
+    created () {
+    },
+    computed: {
+        isArea() {
+            return localStorage.getItem('platform') === 'area'
+        },
+        areaId() {
+            return sessionStorage.getItem('areaId')
+        },
+        sasData() {
+            let info = {
+                url: this.$store.state.user.userProfile.blob_uri.split('/' + this.$store.state.userInfo.TEAMModelId)[0],
+                sas: ''
+            }
+            if(this.isArea) {
+                info.sas = this.$store.state.user.userProfile.areas.find(item => {
+                    return item.areaId === this.areaId
+                }).sas
+            } else {
+                info.sas = this.$store.state.user.schoolProfile.blob_sas
+            }
+            return info
+        },
+    },
+    watch: {
+        editModal: {
+            handler(n, o) {
+                if(n) {
+                    this.editInfo = {
+                        name: this.actInfo.name,
+                        subject: this.actInfo.subject,
+                        description: this.actInfo.description,
+                        address: this.actInfo.address,
+                        stime: this.actInfo.stime,
+                        etime: this.actInfo.etime,
+                        poster: this.actInfo.poster.includes('CoverImage.jpg') ? this.actInfo.poster : '',
+                        attachment: this._.cloneDeep(this.actInfo.attachment),
+                        zb: this._.cloneDeep(this.actInfo.zb),
+                        cb: this._.cloneDeep(this.actInfo.cb),
+                        mzsm: this.actInfo.mzsm,
+                    }
+                    this.actAllTime = [[]]
+                    // 处理时间显示
+                    this.actAllTime[0].push(this.$tools.formatTime(this.actInfo.stime, 'yyyy-MM-dd hh:mm'))
+                    this.actAllTime[0].push(this.$tools.formatTime(this.actInfo.etime, 'yyyy-MM-dd hh:mm'))
+                    // 基本信息回显
+                    this.posterFile = {
+                        url: this.editInfo.poster,
+                        file: undefined,
+                    }
+                    this.attachment = this._.cloneDeep(this.actInfo.attachment)
+                    let that = this
+                    this.beforeToday = {
+                        disabledDate (date) {
+                            return date && date.valueOf() < that.actInfo.stime
+                        }
+                    }
+                }
+            },
+        },
+    },
+    methods: {
+        // 获取大活动的进行时间
+        getCreateTime(data) {
+            console.log('52fg6regge65e', data);
+            this.actAllTime[0] = data.filter(item => {
+                return item
+            })
+            this.editInfo.stime = this.actAllTime[0].length > 1 ? (new Date(this.actAllTime[0][0])).getTime() : null
+            this.editInfo.etime = this.actAllTime[0].length > 1 ? (new Date(this.actAllTime[0][1])).getTime() : null
+        },
+        editUnit(type, unit, index) {
+            if(type === 'add') {
+                this.editInfo[unit].push('')
+            } else if(type === 'delete') {
+                this.editInfo[unit].splice(index, 1)
+            }
+        },
+        // 文件展示
+        customUpload(file) {
+            // 点击发布活动时,上传到blob,再回传blob地址
+            let newFile = {
+                file: new File([file], "CoverImage.jpg", {type: file.type}),
+                name: 'CoverImage.jpg', //封面固定名称
+                size: file.size,
+                type: file.type,
+                progress: 0,
+            }
+            const reader = new FileReader()
+            reader.readAsDataURL(file)
+            reader.onload = () => {
+                const _base64 = reader.result
+                this.posterFile = {
+                    url: _base64,
+                    file: newFile
+                }
+            }
+            return false
+        },
+        //重新上传
+        repeatUpload() {
+            this.posterFile = {
+                url: '',
+                file: undefined
+            }
+        },
+        customUploadFile(file) {
+            if(this.attachment.find(item => item.name === file.name)) {
+                this.$Message.warning('存在相同文件,请重新上传!')
+                return
+            }
+            let newFile = {
+                file,
+                name: file.name,
+                size: file.size,
+                type: file.type,
+                progress: 0,
+            }
+            this.attachAdd.push(newFile)
+            this.attachment.push(newFile)
+        },
+        // 删除选择的文件
+        delUpload(index, data) {
+            // 编辑状态下,删除已经保存在blob的文件需加入数组
+            if(this.isEdit && data.blob) {
+                this.attachDel.push(data)
+            } else {
+                // 新增的需从 attachAdd 删除
+                let delIndex = this.attachAdd.findIndex(item => {
+                    return item.name === data.name
+                })
+                if(delIndex != -1) this.attachAdd.splice(delIndex, 1)
+            }
+            this.attachment.splice(index, 1)
+        },
+        async saveInfo() {
+            this.isLoading = true
+            let saving = true
+            this.$refs['editInfo'].validate((valid) => {
+                if (!valid) {
+                    this.$Message.error('请完善信息!');
+                    saving = false
+                }
+            })
+            if(saving) {
+                console.log(11111111);
+                let files = this.posterFile.file ? [this.posterFile.file].concat(this.attachAdd) : this.attachAdd
+                if(this.attachDel.length) {
+                    let delType = await this.deleteBlob(this.attachDel)
+                    if(!delType) {
+                        this.$Message.warning('保存失败!')
+                        return
+                    }
+                
+                }
+                // 2.3 上传 封面 + 附件
+                let fileBlob = await this.uploadBlob(files)
+                // 2023.11.10 处理编辑状态下没有保存已有且未被删除的文件,只保存了当前新增的
+                this.editInfo.attachment = this.attachment.map(item => {
+                    if(item.hash) {
+                        return item
+                    } else {
+                        let newFile = fileBlob.find(files => {
+                            return files.name === item.name
+                        })
+                        newFile.hash = this.$tools.convertFileMD5ToString(newFile.md5)
+                        newFile.cnt = this.isArea ? this.areaId : this.$store.state.userInfo.schoolCode
+                        return newFile
+                    }
+                })
+                if(this.posterFile.file) {
+                    this.editInfo.poster = fileBlob[0].url
+                } else {
+                    // 编辑状态下的poster 拼接了sas,保存时需去掉
+                    this.editInfo.poster = this.posterFile.url.slice(0, this.posterFile.url.lastIndexOf('?'))
+                }
+                console.log(2222222222);
+
+
+
+                let params = {
+                    grant_type: 'update-activity-base',
+                    activityId: this.actInfo.id,
+                    activityUpdate: this.editInfo
+                }
+                this.$api.areaActivity.manageAct(params).then(res => {
+                    console.log(3333333333);
+                    if(res.code === 200) {
+                        this.$Message.success("保存成功")
+                        this.$emit('saveInfo', res?.activity)
+                    } else {
+                        this.$Message.warning("保存失败")
+                    }
+                }).finally(() => {
+                    this.isLoading = false
+                })
+            } else {
+                this.isLoading = false
+            }
+        },
+        deleteBlob(files) {
+            return new Promise((r, j) => {
+                //初始化Blob
+                let code = this.isArea ? this.areaId : this.$store.state.userInfo.schoolCode
+                let scope = this.isArea ? 'area' : 'school'
+                let blobTool = new BlobTool(this.sasData.url, code, "?" + this.sasData.sas, scope)
+                let fileUrl = files.map(item => {
+                    return item.blob
+                })
+                console.log(fileUrl);
+                blobTool.deleteBlobBatch(fileUrl).then(() => {
+                    r(true)
+                }).catch(() => {
+                    j(false)
+                })
+                r(true)
+            })
+        },
+        uploadBlob(fileList) {
+            return new Promise((r, j) => {
+                // 先将封面、附件保存在blob: (区级id/学校编码)/activity/活动id/attachment/文件
+                // 获取初始化Blob需要的数据
+                let code = this.isArea ? this.areaId : this.$store.state.userInfo.schoolCode
+                let scope = this.isArea ? 'area' : 'school'
+                let path = `activity/${this.editInfo.id}/attachment`
+                //初始化Blob
+                let blobTool = new BlobTool(this.sasData.url, code, "?" + this.sasData.sas, scope)
+                let promiseArr = []
+                console.log(fileList);
+                fileList.map(item => {
+                    promiseArr.push(new Promise((r, j) => {
+                        blobTool.upload(item.file, {path, checkSize: false}, {
+                            onProgress: (e) => {
+                                item.progress = parseInt(e.loadedBytes * 100 / item.size)
+                            }
+                        }).then(res => {
+                            r(res)
+                        })
+                    }))
+                })
+                Promise.all(promiseArr).then(res => {
+                    r(res)
+                }).catch(e => {
+                    j(e)
+                })
+            })
+        },
+    },
+}
+</script>
+
+<style lang="less">
+</style>

+ 367 - 0
TEAMModelOS/ClientApp/src/view/signupActivity/infoComponent/editContest.vue

@@ -0,0 +1,367 @@
+<template>
+    <div class="edit-contest">
+        <Form :model="signInfo" :label-width="80" ref="signInfo" :rules="editDataRule">
+            <FormItem label="报名人数">
+                <InputNumber :min="limitMin" v-model="signInfo.limit" />
+                <span>
+                    <Icon type="md-alert" color="#ffad16" size="17" />
+                    0:默认不限制
+                </span>
+            </FormItem>
+            <FormItem label="报名时间" prop="signEtime">
+                <DatePicker v-model="actAllTime[0]" :editable="false" @on-change="data => getCreateTime(data, 0)" type="datetimerange" :options="beforeToday" format="yyyy-MM-dd HH:mm" :placeholder="$t('train.create.timeHolder')" style="width: 500px"></DatePicker>
+            </FormItem>
+        </Form>
+        <template v-if="conModules.includes('upload')">
+            <Form :model="uploadInfo" :label-width="80" ref="uploadInfo" :rules="editDataRule">
+                <FormItem label="上传时间" prop="uploadEtime">
+                    <DatePicker v-model="actAllTime[1]" ref="workPicker" :editable="false" @on-change="data => getCreateTime(data, 1)" type="datetimerange" :options="beforeToday" format="yyyy-MM-dd HH:mm" :placeholder="$t('train.create.timeHolder')" style="width: 500px"></DatePicker>
+                </FormItem>
+                <FormItem label="文件类型" v-if="uploadInfo?.fileType" prop="fileType">
+                    <div class="tab-check">
+                        <div v-for="(item, index) in fileType" :key="index" class="file-box">
+                            <Checkbox v-model="item.isCheck">
+                                {{ item.label }}
+                            </Checkbox>
+                            <div v-if="item.isCheck">
+                                <Select v-model="formatType[index]" multiple size="small" style="width: 400px" placeholder="不限格式">
+                                    <Option v-for="(format, index) in item.format" :value="format" :key="index">{{ format }}</Option>
+                                </Select>
+                            </div>
+                        </div>
+                    </div>
+                </FormItem>
+                <FormItem label="文件说明">
+                    <Input v-special-char type="textarea" v-model="uploadInfo.updateDesc" placeholder="请输入作品要求..." />
+                </FormItem>
+            </Form>
+        </template>
+        <template v-if="conModules.includes('review')">
+            <Form :model="reviewInfo" :label-width="80" ref="reviewInfo" :rules="editDataRule">
+                <FormItem label="评审时间" prop="reviewEtime">
+                    <DatePicker v-model="actAllTime[2]" ref="reviewPicker" :editable="false" @on-change="data => getCreateTime(data, 2)" type="datetimerange" :options="beforeToday" format="yyyy-MM-dd HH:mm" :placeholder="$t('train.create.timeHolder')" style="width: 500px"></DatePicker>
+                </FormItem>
+            </Form>
+        </template>
+        <template v-if="conModules.includes('score')">
+            <Form :model="scoreInfo" :label-width="80" ref="scoreInfo" :rules="editDataRule">
+                <FormItem label="公示时间" prop="scoreEtime">
+                    <DatePicker v-model="actAllTime[3]" ref="scorePicker" :editable="false" @on-change="data => getCreateTime(data, 3)" type="datetimerange" :options="beforeToday" format="yyyy-MM-dd HH:mm" :placeholder="$t('train.create.timeHolder')" style="width: 500px"></DatePicker>
+                </FormItem>
+            </Form>
+        </template>
+        <Button type="success" long @click="saveInfo()">保存</Button>
+    </div>
+</template>
+
+<script>
+export default {
+    props: {
+        actInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        contestInfo: {
+            type: Object,
+            defualt: () => {
+                return {}
+            }
+        },
+        editModal: {
+            type: Boolean,
+            default: false,
+        },
+    },
+    data () {
+        const validateFileType = (rule, value, callback) => {
+            if(this.fileType.find(item => item.isCheck)) {
+                callback()
+            } else {
+                callback(new Error('Please enter your password'))
+            }
+        }
+        const validateDate = (rule, value, callback) => {
+            console.log(rule, value);
+            if(value != '' && value != null && value != undefined) {
+                callback()
+            } else {
+                callback(new Error('请选择时间'))
+            }
+        }
+        return {
+            signInfo: {},
+            uploadInfo: {},
+            reviewInfo: {},
+            scoreInfo: {},
+            actAllTime: [[], [], [], []],
+            editDataRule: {
+                signEtime: [{ required: true, type: 'number', message: '请选择活动时间', trigger: 'blur', validator: validateDate }],
+                uploadEtime: [{ required: true, type: 'number', message: '请选择活动时间', trigger: 'blur', validator: validateDate }],
+                reviewEtime: [{ required: true, type: 'number', message: '请选择活动时间', trigger: 'blur', validator: validateDate }],
+                scoreEtime: [{ required: true, type: 'number', message: '请选择活动时间', trigger: 'blur', validator: validateDate }],
+                fileType: [{ required: true, message: '请选择填报信息', trigger: 'blur', validator: validateFileType }],
+            },
+            fileType: [
+                {
+                    value: 'documentType',
+                    label: '文档类',
+                    isCheck: false,
+                    format: ['doc', 'docx', 'txt', 'ppt', 'pptx', 'xls', 'xlsx', 'pdf']
+                },
+                {
+                    value: 'audio',
+                    label: '音频类',
+                    isCheck: false,
+                    format: ['mp3', 'wav', 'wma']
+                },
+                {
+                    value: 'video',
+                    label: '视频类',
+                    isCheck: false,
+                    format: ['mp4', 'webm']
+                },
+                {
+                    value: 'img',
+                    label: '图片类',
+                    isCheck: false,
+                    format: ['jpg', 'jpeg', 'png', 'gif']
+                },
+                {
+                    value: 'winrar',
+                    label: '压缩包类',
+                    isCheck: false,
+                    format: ['zip', 'rar', '7zip']
+                },
+            ],
+            formatType: [[], [], [], [], []],
+            beforeToday: {},
+            conModules: [],
+            limitMin: 0,
+        }
+    },
+    watch: {
+        editModal: {
+            handler(n, o) {
+                if(n) {
+                    this.conModules = this.contestInfo.modules
+                    this.signInfo = {
+                        signStime: this.contestInfo.sign.stime,
+                        signEtime: this.contestInfo.sign.etime,
+                        limit: this.contestInfo.sign.limit,
+                    }
+                    this.actAllTime = [[], [], [], []]
+                    this.actAllTime[0].push(this.$tools.formatTime(this.contestInfo.sign.stime, 'yyyy-MM-dd hh:mm'))
+                    this.actAllTime[0].push(this.$tools.formatTime(this.contestInfo.sign.etime, 'yyyy-MM-dd hh:mm'))
+                    if(this.contestInfo.modules.includes('upload')) {
+                        this.uploadInfo = {
+                            uploadStime: this.contestInfo.upload.stime,
+                            uploadEtime: this.contestInfo.upload.etime,
+                            updateDesc: this.contestInfo.upload.desc,
+                        }
+                        if(this.contestInfo.upload.type === 'file') {
+                            this.uploadInfo.fileType = this._.cloneDeep(this.contestInfo.upload.fileType)
+                            this.uploadInfo.fileType.forEach(item => {
+                                this.fileType.forEach((file, index) => {
+                                    if(file.format.includes(item)) {
+                                        file.isCheck = true
+                                        this.formatType[index].push(item)
+                                    }
+                                })
+                            })
+                            this.formatType = this.formatType.map((item, index) => {
+                                item = item.length === this.fileType[index].format.length ? [] : item
+                                return item
+                            })
+                        }
+                        this.actAllTime[1].push(this.$tools.formatTime(this.contestInfo.upload.stime, 'yyyy-MM-dd hh:mm'))
+                        this.actAllTime[1].push(this.$tools.formatTime(this.contestInfo.upload.etime, 'yyyy-MM-dd hh:mm'))
+                    }
+                    if(this.contestInfo.modules.includes('review')) {
+                        this.reviewInfo = {
+                            reviewStime: this.contestInfo.review.stime,
+                            reviewEtime: this.contestInfo.review.etime
+                        }
+                        this.actAllTime[2].push(this.$tools.formatTime(this.contestInfo.review.stime, 'yyyy-MM-dd hh:mm'))
+                        this.actAllTime[2].push(this.$tools.formatTime(this.contestInfo.review.etime, 'yyyy-MM-dd hh:mm'))
+                    }
+                    if(this.contestInfo.modules.includes('score')) {
+                        this.scoreInfo = {
+                            scoreStime: this.contestInfo.score.stime,
+                            scoreEtime: this.contestInfo.score.etime
+                        }
+                        this.actAllTime[3].push(this.$tools.formatTime(this.contestInfo.score.stime, 'yyyy-MM-dd hh:mm'))
+                        this.actAllTime[3].push(this.$tools.formatTime(this.contestInfo.score.etime, 'yyyy-MM-dd hh:mm'))
+                    }
+                    // 处理时间显示
+                    let that = this
+                    this.beforeToday = {
+                        disabledDate (date) {
+                            return (date && date.valueOf() < that.actInfo.stime) || (date && date.valueOf() > that.actInfo.etime)
+                        }
+                    }
+                }
+            },
+        },
+        'signInfo.signStime': {
+            handler(n, o) {
+                /* if(this.conModules.includes('upload')) this.$refs.workPicker.handleClear()
+                if(this.conModules.includes('review')) this.$refs.reviewPicker.handleClear()
+                if(this.conModules.includes('score')) this.$refs.scorePicker.handleClear()
+                this.actAllTime = [[], [], [], []] */
+            }
+        },
+    },
+    methods: {
+        getCreateTime(data, index) {
+            console.log(data);
+            let times = data.filter(item => {
+                return item
+            })
+            switch (index) {
+                case 0:
+                    this.signInfo.signStime = times.length > 1 ? (new Date(times[0])).getTime() : null
+                    this.signInfo.signEtime = times.length > 1 ? (new Date(times[1])).getTime() : null
+                    this.actAllTime = [[], [], [], []]
+                    this.actAllTime[0] = times
+                    break
+                case 1:
+                    if(times.length > 1 && !this.signInfo.signEtime) {
+                        // this.$refs.workPicker.handleClear()
+                        this.$Message.warning('请先设置报名时间')
+                        this.actAllTime.splice(1, 1, [])
+                    }
+                    if(times.length > 1 && (new Date(times[1])).getTime() < this.signInfo.signEtime) {
+                        this.$Message.warning('上传结束时间不能早于报名时间')
+                        this.actAllTime.splice(1, 1, [])
+                    }
+                    this.uploadInfo.uploadStime = times.length > 1 ? (new Date(times[0])).getTime() : null
+                    this.uploadInfo.uploadEtime = times.length > 1 ? (new Date(times[1])).getTime() : null
+                    
+                    this.actAllTime.splice(2, 1, [])
+                    this.actAllTime.splice(3, 1, [])
+                    break
+                case 2:
+                    if(times.length > 1 && (!this.signInfo.signEtime || !this.uploadInfo.uploadEtime)) {
+                        this.$Message.warning('请先设置报名时间、上传时间')
+                        this.actAllTime.splice(2, 1, [])
+                    }
+                    if(times.length > 1 && (new Date(times[0])).getTime() < this.signInfo.signEtime || (new Date(times[0])).getTime() < this.uploadInfo.uploadEtime) {
+                        this.$Message.warning('评审必须在报名、上传结束后进行')
+                        this.actAllTime.splice(2, 1, [])
+                    }
+                    this.reviewInfo.reviewStime = times.length > 1 ? (new Date(times[0])).getTime() : null
+                    this.reviewInfo.reviewEtime = times.length > 1 ? (new Date(times[1])).getTime() : null
+                    this.actAllTime.splice(3, 1, [])
+                    break
+                case 3:
+                    if(times.length > 1 && !this.reviewInfo.reviewEtime) {
+                        this.$Message.warning('请先设置评审时间')
+                        this.actAllTime.splice(3, 1, [])
+                    }
+                    if(times.length > 1 && (new Date(times[0])).getTime() < this.reviewInfo.reviewEtime) {
+                        this.$Message.warning('公示必须在评审结束后进行')
+                        this.actAllTime.splice(3, 1, [])
+                    }
+                    this.scoreInfo.scoreStime = times.length > 1 ? (new Date(times[0])).getTime() : null
+                    this.scoreInfo.scoreEtime = times.length > 1 ? (new Date(times[1])).getTime() : null
+                    break
+                default:
+                    break
+            }
+        },
+        saveInfo() {
+            let saving = true
+            this.$refs['signInfo'].validate((valid) => {
+                console.log(valid);
+                if (!valid) {
+                    saving = false
+                }
+            })
+            this.$refs['uploadInfo'].validate((valid) => {
+                console.log(valid);
+                if (!valid) {
+                    saving = false
+                }
+            })
+            this.$refs['reviewInfo'].validate((valid) => {
+                console.log(valid);
+                if (!valid) {
+                    saving = false
+                }
+            })
+            this.$refs['scoreInfo'].validate((valid) => {
+                console.log(valid);
+                if (!valid) {
+                    saving = false
+                }
+            })
+            if(!saving) {
+                this.$Message.error('请完善信息!');
+                return
+            }
+            let params = {
+                grant_type: 'update-contest-base',
+                activityId: this.actInfo.id,
+                contestUpdate: {}
+            }
+            params.contestUpdate = {
+                signStime: this.signInfo.signStime,
+                signEtime: this.signInfo.signEtime,
+                signStime: this.signInfo.signStime,
+                limit: this.signInfo.limit,
+                uploadStime: this.uploadInfo.uploadStime,
+                uploadEtime: this.uploadInfo.uploadEtime,
+                updateDesc: this.uploadInfo.updateDesc,
+            }
+            if(this.contestInfo.upload.type === 'file') {
+                params.contestUpdate.fileType = []
+                this.fileType.forEach((item, index) => {
+                    if(item.isCheck) {
+                        if(this.formatType[index].length) {
+                            params.contestUpdate.fileType = params.contestUpdate.fileType.concat(this.formatType[index])
+                        } else {
+                            params.contestUpdate.fileType = params.contestUpdate.fileType.concat(item.format)
+                        }
+                    }
+                })
+            }
+            if(this.conModules.includes('review')) {
+                params.contestUpdate.reviewStime = this.reviewInfo.reviewStime
+                params.contestUpdate.reviewEtime = this.reviewInfo.reviewEtime
+            }
+            if(this.conModules.includes('score')) {
+                params.contestUpdate.scoreStime = this.scoreInfo.scoreStime
+                params.contestUpdate.scoreEtime = this.scoreInfo.scoreEtime
+            }
+            this.$api.areaActivity.manageAct(params).then(res => {
+                if(res.code === 200) {
+                    this.$Message.success("保存成功")
+                    this.$emit('saveContest', res?.activity)
+                } else {
+                    this.$Message.warning("保存失败")
+                }
+            }).finally(() => {
+                // this.isLoading = false
+            })
+        },
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.edit-contest {
+    
+    .tab-check {
+        margin-bottom: 15px;
+        .file-box {
+            display: flex;
+
+            &>label {
+                width: 100px;
+            }
+        }
+    }
+}
+</style>

+ 60 - 11
TEAMModelOS/ClientApp/src/view/signupActivity/infoGoing.vue

@@ -14,7 +14,7 @@
                     {{ actInfo.name }}
                 </p>
                 <div class="btn-box" v-show="isArea || actInfo.scope === 'school'">
-                    <Button type="primary" size="small" @click="editAct()" style="margin-right: 10px;">编辑</Button>
+                    <Button type="primary" size="small" @click="editModal = true" style="margin-right: 10px;">编辑</Button>
                     <Button type="warning" size="small" @click="delAct()">删除</Button>
                 </div>
                 <!-- <div class="btn-box">
@@ -66,7 +66,10 @@
                             </div>
                         </div>
                         <div class="sk-info" v-if="contestInfo">
-                            <div class="info-title">赛课活动</div>
+                            <div class="info-title">
+                                <span>赛课活动</span>
+                                <Icon type="md-create" size="16" style="margin-left: 10px;" @click="editConModal = true" />
+                            </div>
                             <p>
                                 报名时间:
                                 <span>{{ contestInfo.sign.startTime }} - {{ contestInfo.sign.endTime }}</span>
@@ -123,8 +126,15 @@
         <Modal v-model="schoolModal" title="参与本次活动的学校">
             <Table :columns="schoolCloumns" :data="schoolList"></Table>
         </Modal>
-        <Modal v-model="editModal" title="编辑活动基本信息">
-            
+        <Modal v-model="editArtModal" title="编辑活动基本信息" width="40" :footer-hide="true">
+            <vuescroll>
+                <editActInfo :actInfo="actInfo" :editModal="editArtModal" @saveInfo="saveInfo"></editActInfo>
+            </vuescroll>
+        </Modal>
+        <Modal v-model="editConModal" title="编辑赛课活动信息" width="40" :footer-hide="true">
+            <vuescroll>
+                <editContest :actInfo="actInfo" :contestInfo="contestInfo" :editModal="editConModal" @saveInfo="saveContest"></editContest>
+            </vuescroll>
         </Modal>
     </div>
 </template>
@@ -132,10 +142,14 @@
 <script>
 import PersonalPhoto from '@/components/public/personalPhoto/Index.vue'
 import skContent from './infoComponent/skContent.vue'
+import editActInfo from './infoComponent/editActInfo.vue'
+import editContest from './infoComponent/editContest.vue'
 export default {
     components: {
         PersonalPhoto,
-        skContent
+        skContent,
+        editActInfo,
+        editContest,
     },
     data () {
         return {
@@ -191,11 +205,13 @@ export default {
                 },
             ],
             schoolList: [],
-            editModal: false,
-            editInfo: {},
+            editArtModal: false,
+            editConModal: false,
+            actInfo: undefined,
         }
     },
     created () {
+        this.actInfo = this.$route.params.info
         if(!this.actInfo) {
             this.$router.go(-1)
         }
@@ -211,9 +227,6 @@ export default {
         this.getActInfo()
     },
     computed: {
-        actInfo() {
-            return this.$route.params.info
-        },
         isArea() {
             return localStorage.getItem('platform') === 'area'
         },
@@ -228,7 +241,6 @@ export default {
             }
             this.$api.areaActivity.manageAct(params).then(res => {
                 if(res.code === 200) {
-                    this.editInfo = this.actInfo
                     res.contest.modules.forEach(item => {
                         res.contest[item].startTime = this.$tools.formatTime(res.contest[item].stime, 'yyyy-MM-dd')
                         res.contest[item].endTime = this.$tools.formatTime(res.contest[item].etime, 'yyyy-MM-dd')
@@ -307,6 +319,36 @@ export default {
         async onDownload(item) {
             this.$tools.doDownloadByUrl(item.url, item.name)
         },
+        saveInfo(info) {
+            let sas = this.actInfo.sas
+            this.actInfo = info
+            this.actInfo.startTime = this.$tools.formatTime(this.actInfo.stime, 'yyyy-MM-dd')
+            this.actInfo.endTime = this.$tools.formatTime(this.actInfo.etime, 'yyyy-MM-dd')
+            this.actInfo.sas = sas
+            this.actInfo.poster = !info.poster ? require('@/assets/image/no-poster-cn1.png') : `${info.poster}?${sas}`
+            this.actInfo.attachment.forEach(attach => {
+                if(attach) {
+                    attach.url = `${attach.url}?${sas}`
+                }
+            })
+            this.editModal = false
+        },
+        saveContest(info) {
+            /* this.contestInfo.sign.stime = info.signStime
+            this.contestInfo.sign.stime = info.signEtime
+            this.contestInfo.sign.startTime = this.$tools.formatTime(info.signStime, 'yyyy-MM-dd')
+            this.contestInfo.sign.endTime = this.$tools.formatTime(info.signEtime, 'yyyy-MM-dd')
+            this.contestInfo.sign.limit = info.limit
+            this.contestInfo.sign.stime = info.signStime
+            this.contestInfo.sign.stime = info.signEtime
+            this.contestInfo.sign.startTime = this.$tools.formatTime(info.signStime, 'yyyy-MM-dd')
+            this.contestInfo.sign.endTime = this.$tools.formatTime(info.signEtime, 'yyyy-MM-dd')
+            this.contestInfo.sign.stime = info.signStime
+            this.contestInfo.sign.stime = info.signEtime
+            this.contestInfo.sign.startTime = this.$tools.formatTime(info.signStime, 'yyyy-MM-dd')
+            this.contestInfo.sign.endTime = this.$tools.formatTime(info.signEtime, 'yyyy-MM-dd') */
+            this.editConModal = false
+        },
     }
 }
 </script>
@@ -377,6 +419,13 @@ export default {
             padding: 10px;
             margin-bottom: 10px;
             margin-top: 25px;
+
+            .ivu-icon{
+                cursor: pointer;
+                &:hover {
+                    color: #049f04;
+                }
+            }
         }
 
         .attach-name {

+ 144 - 12
TEAMModelOS/Controllers/Common/ActivityController.cs

@@ -702,9 +702,13 @@ namespace TEAMModelOS.Controllers
                                                                 return Ok(new { code = 4, msg = "评审未配置" });
                                                             }
                                                             ReviewRuleTree ruleTree = _reviewConfig.ToObject<ReviewRuleTree>();
-                                                            var reviewRule = await ActivityService.UpsertReviewRule(ruleTree, activity, _azureCosmos);
-                                                            contest.review.ruleId = reviewRule.id;
-                                                            contest.review.ruleName = reviewRule.name;
+                                                            var reviewRuleResult = await ActivityService.UpsertReviewRule(ruleTree, activity,contest, _azureCosmos);
+                                                            if (reviewRuleResult.invalidCode!=200) 
+                                                            {
+                                                                return Ok(new {code = reviewRuleResult.invalidCode,msg = reviewRuleResult.msg });
+                                                            }
+                                                            contest.review.ruleId =reviewRuleResult. reviewRule.id;
+                                                            contest.review.ruleName = reviewRuleResult.reviewRule.name;
                                                         }
                                                         await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Common).UpsertItemAsync(contest, new PartitionKey(contest.code));
                                                         break;
@@ -954,16 +958,26 @@ namespace TEAMModelOS.Controllers
                             if (!string.IsNullOrWhiteSpace(ruleTree.id))
                             {
                                 Activity activity = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Common).ReadItemAsync<Activity>(ruleTree.id, new PartitionKey("Activity"));
-                                var reviewRuleDB = await ActivityService.UpsertReviewRule(ruleTree, activity, _azureCosmos);
-                                var tree = ActivityService.ListToTree(reviewRuleDB.configs);
+                                Contest contest = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Common).ReadItemAsync<Contest>(ruleTree.id, new PartitionKey("Contest"));
+                                var reviewRuleResult = await ActivityService.UpsertReviewRule(ruleTree, activity,contest, _azureCosmos);
+
+                                if (reviewRuleResult.invalidCode!=200)
+                                {
+                                    return Ok(new { code = reviewRuleResult.invalidCode, msg = reviewRuleResult.msg });
+                                }
+                                var tree = ActivityService.ListToTree(reviewRuleResult.reviewRule.configs);
                                 var reviewRule = new ReviewRuleTree
                                 {
-                                    id=reviewRuleDB.id,
-                                    name= reviewRuleDB.name,
-                                    owner= reviewRuleDB.owner,
-                                    sourceName= reviewRuleDB.sourceName,
+                                    id=reviewRuleResult.reviewRule.id,
+                                    name= reviewRuleResult.reviewRule.name,
+                                    owner= reviewRuleResult.reviewRule.owner,
+                                    sourceName= reviewRuleResult.reviewRule.sourceName,
                                     trees=tree,
-                                    desc=reviewRuleDB.desc
+                                    desc=reviewRuleResult.reviewRule.desc,
+                                    distribute=reviewRuleResult.reviewRule.distribute,
+                                    scoreDetail=reviewRuleResult.reviewRule.scoreDetail,
+                                    scoreRule=reviewRuleResult.reviewRule.scoreRule,
+                                    taskCount=reviewRuleResult.reviewRule.taskCount,
                                 };
                                 return Ok(new { reviewRule });
                             }
@@ -1689,13 +1703,131 @@ namespace TEAMModelOS.Controllers
                     //分配评审作品任务-检查
                     case bool when $"{grant_type}".Equals("allocation-task-check", StringComparison.OrdinalIgnoreCase): {
                             if (!request.TryGetProperty("activityId", out JsonElement _activityId)) return BadRequest();
-
+                            Azure.Response responseReviewRule =  await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).ReadItemStreamAsync(_activityId.GetString(), new PartitionKey("ReviewRule-disposable"));
+                            if (responseReviewRule.Status==200) {
+                                ReviewRule reviewRule = JsonDocument.Parse(responseReviewRule.Content).RootElement.ToObject<ReviewRule>();
+                                Azure.Response responseContest = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Common).ReadItemStreamAsync(_activityId.GetString(), new PartitionKey("Contest"));
+                                if (responseContest.Status==200) 
+                                {
+                                    Contest contest = JsonDocument.Parse(responseContest.Content).RootElement.ToObject<Contest>();
+                                    var result = ActivityService.CheckReviewRule(reviewRule, contest);
+                                    if (result.invalidCode==200)
+                                    {
+                                        Azure.Response responseActivityExpert = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemStreamAsync($"{_activityId}", new PartitionKey("ActivityExpert"));
+                                        if (responseActivityExpert.Status==200) {
+                                            ActivityExpert activityExpert = JsonDocument.Parse(responseActivityExpert.Content).RootElement.ToObject<ActivityExpert>();
+                                            List<string> periodSubjectKey = new List<string>();
+                                            List<dynamic> distributeInvalid = new List<dynamic>();
+                                            switch (reviewRule.distribute)
+                                            { 
+                                                case "period":
+                                                    {
+                                                        var fieldPeriod = contest.sign.fields.Find(z => z.field.Equals("period"));
+                                                        if (fieldPeriod== null)
+                                                        {
+                                                            return Ok(new { code = 2, msg = "评审规则匹配学段,但赛课表单未配置学段。" });
+                                                        }
+                                                        else
+                                                        {
+                                                            foreach (string item in fieldPeriod.item) {
+                                                                periodSubjectKey.Add($"{item}-");
+                                                                var periodExperts= activityExpert.experts.Where(z => z.modules.Contains("Contest") &&   z.subjects!=null  && z.subjects.Where(v => !string.IsNullOrWhiteSpace(v.period)).Select(c => c.period).Contains(item));
+                                                                if (periodExperts.Count()<reviewRule.taskCount) 
+                                                                {
+                                                                    distributeInvalid.Add(new {name =item, expertCount= periodExperts.Count(), needCount = reviewRule.taskCount});
+                                                                }
+                                                            }
+                                                        }
+                                                        if (distributeInvalid.Count>0)
+                                                        {
+                                                            return Ok(new { code = 4, msg = "学段匹配的专家数量不足。", distributeInvalid });
+                                                        }
+                                                        return Ok();
+                                                    } 
+                                                case "subject":
+                                                    {
+                                                        var fieldSubject = contest.sign.fields.Find(z => z.field.Equals("subject"));
+                                                        if (fieldSubject== null)
+                                                        {
+                                                            return Ok(new { code = 3, msg = "评审规则匹配学科,但赛课表单未配置学科。" });
+                                                        }
+                                                        else
+                                                        {
+                                                            foreach (string item in fieldSubject.item)
+                                                            {
+                                                                periodSubjectKey.Add($"-{item}");
+                                                                var subjectExperts = activityExpert.experts.Where(z => z.modules.Contains("Contest") && z.subjects!=null  && z.subjects.Where(v => !string.IsNullOrWhiteSpace(v.subject)).Select(c => c.subject).Contains(item));
+                                                                if (subjectExperts.Count()<reviewRule.taskCount)
+                                                                {
+                                                                    distributeInvalid.Add(new { name = item, expertCount = subjectExperts.Count(), needCount = reviewRule.taskCount });
+                                                                }
+                                                             
+                                                            }
+                                                        }
+                                                        if (distributeInvalid.Count>0)
+                                                        {
+                                                            return Ok(new { code = 5, msg = "学科匹配的专家数量不足。", distributeInvalid });
+                                                        }
+                                                        return Ok();
+                                                    }
+                                                    
+                                                case "periodAndSubject":
+                                                    {
+                                                        var fieldPeriod = contest.sign.fields.Find(z => z.field.Equals("period"));
+                                                        if (fieldPeriod== null)
+                                                        {
+                                                            return Ok(new { code = 2, msg = "评审规则匹配学段和学科,但赛课表单未配置学段。" });
+                                                        }
+                                                        var fieldSubject = contest.sign.fields.Find(z => z.field.Equals("subject"));
+                                                        if (fieldSubject== null)
+                                                        {
+                                                            return Ok(new { code = 3, msg = "评审规则匹配学段和学科,但赛课表单未配置学科。" });
+                                                        }
+                                                        foreach (var period in fieldPeriod.item) {
+                                                            foreach (var subject in fieldSubject.item)
+                                                            {
+                                                                periodSubjectKey.Add($"{period}-{subject}");
+                                                                var periodSubjectExperts = activityExpert.experts.FindAll(z => z.modules.Contains("Contest") && z.subjects!=null  && z.subjects.Where(v => !string.IsNullOrWhiteSpace(v.period) && !string.IsNullOrWhiteSpace(v.subject))
+                                                                        .Select(c => $"{c.period}-{c.subject}").Contains($"{period}-{subject}"));
+                                                                if (periodSubjectExperts.Count()<reviewRule.taskCount)
+                                                                {
+                                                                    distributeInvalid.Add(new { name = $"{period}-{subject}", expertCount = periodSubjectExperts.Count(), needCount = reviewRule.taskCount });
+                                                                }
+                                                            }
+                                                        }
+                                                        if (distributeInvalid.Count>0)
+                                                        {
+                                                            return Ok(new { code = 6, msg = "学段和学科匹配的专家数量不足。", distributeInvalid });
+                                                        }
+                                                        return Ok();
+                                                    }
+                                                default:
+                                                    //distribute=none
+                                                    {
+                                                        if (activityExpert.experts.Count() >=reviewRule.taskCount)
+                                                        {
+                                                            return Ok();
+                                                        }
+                                                        else
+                                                        {
+                                                            return Ok(new { code = 1, msg = "评审专家人数不应少于作品分配次数。" });
+                                                        }
+                                                    }
+                                            }
+                                        }
+                                    }
+                                    else {
+                                        return Ok(new { code = 200, msg = result.msg });
+                                    }
+                                }
+                                //专家人数>=每个作品分配次数,同一作品才不会多次被分配到分配给同一个专家。
+                            }
                             break;
                         }
                     //分配评审作品任务-自动
                     case bool when $"{grant_type}".Equals("allocation-task-auto", StringComparison.OrdinalIgnoreCase):
                         {
-                            //专家人数>=每个作品分配次数,同一才不会多次被分配到分配给同一个专家。
+                          
                             break;
                         }
                     //分配评审作品任务-手动

+ 8 - 0
TEAMModelOS/Controllers/School/ArtReviewController.cs

@@ -181,7 +181,9 @@ namespace TEAMModelOS.Controllers
         [AuthToken(Roles = "teacher,admin")]
         [HttpPost("review")]
 
+# if !DEBUG
         [Authorize(Roles = "IES")]
+#endif
 
         public async Task<IActionResult> Review(JsonElement request)
         {
@@ -286,6 +288,12 @@ namespace TEAMModelOS.Controllers
                             if (ids.Any())
                             {
                                 string query = $" select value c from c where c.id in({string.Join(",", ids.Select(x => $"'{x}'"))}) ";
+
+                                //var result=   await client.GetContainer(Constant.TEAMModelOS, Constant.Student).GetList<StudentArtResult>(query, $"ArtResult-{_artId}", continuationToken, pageCount);
+                                //if (result.list.IsNotEmpty()) {
+                                //    results.AddRange(result.list);
+                                //    continuationToken=result.continuationToken;
+                                //}
                                 await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Student).GetItemQueryStreamIterator
                                     (queryText: query, continuationToken: continuationToken, requestOptions: new QueryRequestOptions { MaxItemCount = pageCount, PartitionKey = new PartitionKey($"ArtResult-{_artId}") }))
                                 {

+ 1 - 1
TEAMModelOS/Controllers/Teacher/InitController.cs

@@ -882,7 +882,7 @@ namespace TEAMModelOS.Controllers
                 var container = _azureStorage.GetBlobContainerClient(school_code_blob);
                 await container.CreateIfNotExistsAsync(PublicAccessType.None); //嘗試創建School容器,如存在則不做任何事,保障容器一定存在
                 bool permissionsUpd = false;
-                permissions.ForEach(z => { if (z.Contains("-upd")) { permissionsUpd=true; } });
+                permissions.ForEach(z => { if (!string.IsNullOrWhiteSpace(z) && z.Contains("-upd")) { permissionsUpd=true; } });
                 var (blob_uri, blob_sas) = (roles.Contains("admin") || permissionsUpd ) ? _azureStorage.GetBlobContainerSAS(school_code_blob, BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Delete) : _azureStorage.GetBlobContainerSAS(school_code_blob, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Write);
                 ///https://teammodelstorage.blob.core.chinacloudapi.cn/teammodelos
                 var (osblob_uri, osblob_sas) = roles.Contains("area") ? _azureStorage.GetBlobContainerSAS("teammodelos", BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Delete) : _azureStorage.GetBlobContainerSAS("teammodelos", BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List);