瀏覽代碼

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

zhouj1203@hotmail.com 4 年之前
父節點
當前提交
2531f9c8b1
共有 100 個文件被更改,包括 4012 次插入1432 次删除
  1. 16 0
      Client/Client.csproj
  2. 67 0
      Client/Program.cs
  3. 143 0
      Client/SSE/ConnectedState.cs
  4. 55 0
      Client/SSE/ConnectingState.cs
  5. 37 0
      Client/SSE/DisconnectedState.cs
  6. 116 0
      Client/SSE/EventSource.cs
  7. 14 0
      Client/SSE/EventSourceState.cs
  8. 15 0
      Client/SSE/IConnectionState.cs
  9. 17 0
      Client/SSE/IServerResponse.cs
  10. 15 0
      Client/SSE/IWebRequester.cs
  11. 12 0
      Client/SSE/IWebRequesterFactory.cs
  12. 39 0
      Client/SSE/ServerResponse.cs
  13. 26 0
      Client/SSE/ServerSentEvent.cs
  14. 17 0
      Client/SSE/ServerSentEventReceivedEventArgs.cs
  15. 16 0
      Client/SSE/StateChangedEventArgs.cs
  16. 47 0
      Client/SSE/StringSplitter.cs
  17. 38 0
      Client/SSE/WatchDog.cs
  18. 30 0
      Client/SSE/WebRequester.cs
  19. 16 0
      Client/SSE/WebRequesterFactory.cs
  20. 26 23
      TEAMModelFunction/ActivityHttpTrigger.cs
  21. 232 218
      TEAMModelFunction/TriggerSurvey.cs
  22. 4 1
      TEAMModelOS.SDK/Models/Cosmos/Common/Inner/SyllabusTree.cs
  23. 182 8
      TEAMModelOS.SDK/Models/Cosmos/Common/ItemCond.cs
  24. 1 0
      TEAMModelOS.SDK/Models/Cosmos/Common/Syllabus.cs
  25. 55 56
      TEAMModelOS.SDK/Models/Service/ItemService.cs
  26. 1 0
      TEAMModelOS.SDK/TEAMModelOS.SDK.csproj
  27. 7 1
      TEAMModelOS.sln
  28. 1 1
      TEAMModelOS/ClientApp/.eslintrc.js
  29. 1 1
      TEAMModelOS/ClientApp/package.json
  30. 1 0
      TEAMModelOS/ClientApp/public/index.html
  31. 34 12
      TEAMModelOS/ClientApp/src/api/blob.js
  32. 2 0
      TEAMModelOS/ClientApp/src/api/index.js
  33. 7 0
      TEAMModelOS/ClientApp/src/api/openMgmt.js
  34. 12 0
      TEAMModelOS/ClientApp/src/api/service.js
  35. 3 0
      TEAMModelOS/ClientApp/src/api/syllabus.js
  36. 1 0
      TEAMModelOS/ClientApp/src/assets/icon_student.svg
  37. 1 0
      TEAMModelOS/ClientApp/src/assets/icon_teacher.svg
  38. 1 0
      TEAMModelOS/ClientApp/src/assets/ies5_logg.svg
  39. 1 1
      TEAMModelOS/ClientApp/src/assets/ies5_logo.svg
  40. 1 0
      TEAMModelOS/ClientApp/src/assets/ies5_logo_2.svg
  41. 二進制
      TEAMModelOS/ClientApp/src/assets/image/img_def.png
  42. 二進制
      TEAMModelOS/ClientApp/src/assets/image/video_def.png
  43. 1 0
      TEAMModelOS/ClientApp/src/assets/login/icon_student.svg
  44. 1 0
      TEAMModelOS/ClientApp/src/assets/login/icon_teacher.svg
  45. 1 0
      TEAMModelOS/ClientApp/src/assets/login/ies5_logo_2.svg
  46. 二進制
      TEAMModelOS/ClientApp/src/assets/login/login_bg.jpg
  47. 18 18
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/event-list.css
  48. 68 67
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/event-list.less
  49. 3 0
      TEAMModelOS/ClientApp/src/boot-app.js
  50. 2 2
      TEAMModelOS/ClientApp/src/common/BaseLayout.less
  51. 1 1
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  52. 3 2
      TEAMModelOS/ClientApp/src/common/BaseUserPoptip.vue
  53. 69 36
      TEAMModelOS/ClientApp/src/common/UploadModal.vue
  54. 34 20
      TEAMModelOS/ClientApp/src/components/public/frontEndMain/Index.less
  55. 27 15
      TEAMModelOS/ClientApp/src/components/public/frontEndMain/Index.vue
  56. 751 722
      TEAMModelOS/ClientApp/src/components/selflearn/NewChooseContent.vue
  57. 153 63
      TEAMModelOS/ClientApp/src/components/syllabus/DragTree.vue
  58. 17 17
      TEAMModelOS/ClientApp/src/components/syllabus/InviteTeacher.vue
  59. 5 1
      TEAMModelOS/ClientApp/src/locale/lang/en-US/cusMgt.js
  60. 6 1
      TEAMModelOS/ClientApp/src/locale/lang/en-US/index.js
  61. 44 12
      TEAMModelOS/ClientApp/src/locale/lang/en-US/login.js
  62. 4 2
      TEAMModelOS/ClientApp/src/locale/lang/en-US/settings.js
  63. 29 27
      TEAMModelOS/ClientApp/src/locale/lang/en-US/teachContent.js
  64. 28 0
      TEAMModelOS/ClientApp/src/locale/lang/en-US/user.js
  65. 39 0
      TEAMModelOS/ClientApp/src/locale/lang/en-US/utils.js
  66. 5 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/cusMgt.js
  67. 3 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/evaluation.js
  68. 4 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/index.js
  69. 51 19
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/login.js
  70. 4 2
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/settings.js
  71. 1 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/survey.js
  72. 46 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/syllabus.js
  73. 3 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/teachContent.js
  74. 28 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/user.js
  75. 5 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/cusMgt.js
  76. 3 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/evaluation.js
  77. 4 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/index.js
  78. 43 11
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/login.js
  79. 4 2
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/settings.js
  80. 1 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/survey.js
  81. 46 2
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/syllabus.js
  82. 3 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/teachContent.js
  83. 28 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/user.js
  84. 1 1
      TEAMModelOS/ClientApp/src/router/routes.js
  85. 2 2
      TEAMModelOS/ClientApp/src/static/Global.js
  86. 961 0
      TEAMModelOS/ClientApp/src/static/countryCodeData.js
  87. 0 5
      TEAMModelOS/ClientApp/src/utils/blobTool.js
  88. 4 0
      TEAMModelOS/ClientApp/src/utils/editorLangTw.js
  89. 1 1
      TEAMModelOS/ClientApp/src/utils/editorTools.js
  90. 6 0
      TEAMModelOS/ClientApp/src/utils/evTools.js
  91. 7 0
      TEAMModelOS/ClientApp/src/utils/js-fn.js
  92. 2 2
      TEAMModelOS/ClientApp/src/utils/kityformula.js
  93. 2 0
      TEAMModelOS/ClientApp/src/utils/public.js
  94. 1 2
      TEAMModelOS/ClientApp/src/view/answersheet/index.vue
  95. 89 24
      TEAMModelOS/ClientApp/src/view/evaluation/bank/ExerciseList.vue
  96. 2 5
      TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue
  97. 16 5
      TEAMModelOS/ClientApp/src/view/evaluation/bank/index.vue
  98. 14 5
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseRepair.vue
  99. 8 7
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue
  100. 0 0
      TEAMModelOS/ClientApp/src/view/homepage/HomePage.vue

+ 16 - 0
Client/Client.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.3" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
+    <PackageReference Include="Twilio" Version="5.55.0" />
+  </ItemGroup>
+
+</Project>

+ 67 - 0
Client/Program.cs

@@ -0,0 +1,67 @@
+using Client.SSE;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Http;
+using System.Security.Policy;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+
+
+namespace Client
+{
+    class Program
+    {    
+        static void Main(string[] args)
+        {
+            //SSE
+            try
+            {
+                var cts = new CancellationTokenSource();
+                var header = new Dictionary<string, string>() { { "X-Auth-Name", "IES5" } };
+                var sse = new EventSource(new Uri("https://localhost:5001/service/sse"), header, 5000);
+                //var sse = new EventSource(new Uri("https://api2.teammodel.net/service/sse"), header, 5000);
+
+                sse.StateChanged += Sse_StateChanged;
+                sse.EventReceived += Sse_EventReceived;
+                sse.Start(cts.Token);
+
+                //cts?.Cancel(); 中斷SSE Client連線
+            }
+            catch (TaskCanceledException ex)
+            {
+                Console.WriteLine(ex.Message);
+            }
+            catch (AggregateException ex)
+            {
+                Console.WriteLine(ex.Message);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.Message);
+            }
+            Console.ReadLine();
+        }
+
+        private static void Sse_EventReceived(object sender, ServerSentEventReceivedEventArgs e)
+        {
+            var mm = e.Message;
+            Console.WriteLine(mm.Data);
+        }
+
+
+        private static void Sse_StateChanged(object sender, StateChangedEventArgs e)
+        {
+            //CONNECTING 0 連線中
+            //OPEN 1 開啟
+            //CLOSED 2 關閉
+            var aa = e.State;
+
+            Console.WriteLine(aa.ToString());
+        }
+
+    }
+}

+ 143 - 0
Client/SSE/ConnectedState.cs

@@ -0,0 +1,143 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Net;
+using System.IO;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Diagnostics;
+
+namespace Client.SSE
+{
+    public class ConnectedState : IConnectionState
+    {    
+
+        private IWebRequesterFactory mWebRequesterFactory;
+        private ServerSentEvent mSse = null;
+        private string mRemainingText = string.Empty;   // the text that is not ended with a lineending char is saved for next call.
+        private IServerResponse mResponse;
+        private Dictionary<string, string> headers;
+
+        public EventSourceState State { get { return EventSourceState.OPEN; } }
+
+        public ConnectedState(IServerResponse response, IWebRequesterFactory webRequesterFactory, Dictionary<string, string> headers)
+        {
+            mResponse = response;
+            mWebRequesterFactory = webRequesterFactory;
+            this.headers = headers;
+        }
+
+        public Task<IConnectionState> Run(Action<ServerSentEvent> msgReceived, CancellationToken cancelToken, Dictionary<string, string> headers)
+        {   
+            Task<IConnectionState> t = new Task<IConnectionState>(() =>
+            {
+                //using (mResponse)
+                {
+                    //using (var stream = mResponse.GetResponseStream())
+                    var stream = mResponse.GetResponseStream();
+                    {
+                        byte[] buffer = new byte[1024 * 8];
+                        var taskRead = stream.ReadAsync(buffer, 0, buffer.Length, cancelToken);
+
+                        try
+                        {
+                            taskRead.Wait(cancelToken);
+                        }
+                        catch (Exception ex)
+                        {
+                            Trace.WriteLine(ex, "ConnectedState.Run");
+                        }
+                        if (!cancelToken.IsCancellationRequested)
+                        {
+                            int bytesRead = taskRead.Result;
+                            if (bytesRead > 0) // stream has not reached the end yet
+                            {
+                                //Console.WriteLine("ReadCallback {0} bytesRead", bytesRead);
+                                string text = Encoding.UTF8.GetString(buffer, 0, bytesRead);
+                                text = mRemainingText + text;
+                                string[] lines = StringSplitter.SplitIntoLines(text, out mRemainingText);
+                                foreach (string line in lines)
+                                {
+                                    if (cancelToken.IsCancellationRequested) break;
+
+                                    // Dispatch message if empty lne
+                                    if (string.IsNullOrEmpty(line.Trim()) && mSse != null)
+                                    {
+                                        Trace.WriteLine("SSE Message received");
+                                        msgReceived(mSse);
+                                        mSse = null;
+                                    }
+                                    else if (line.StartsWith(":"))
+                                    {
+                                        // This a comment, just log it.
+                                        Trace.WriteLine("SSE A comment was received: " + line);
+                                    }
+                                    else
+                                    {
+                                        string fieldName = String.Empty;
+                                        string fieldValue = String.Empty;
+                                        if (line.Contains(':'))
+                                        {
+                                            int index = line.IndexOf(':');
+                                            fieldName = line.Substring(0, index);
+                                            fieldValue = line.Substring(index + 1).TrimStart();
+                                        }
+                                        else
+                                            fieldName = line;
+
+                                        if (String.Compare(fieldName, "event", true) == 0)
+                                        {
+                                            mSse = mSse ?? new ServerSentEvent();
+                                            mSse.EventType = fieldValue;
+                                        }
+                                        else if (String.Compare(fieldName, "data", true) == 0)
+                                        {
+                                            mSse = mSse ?? new ServerSentEvent();
+                                            mSse.Data = fieldValue + '\n';
+                                        }
+                                        else if (String.Compare(fieldName, "id", true) == 0)
+                                        {
+                                            mSse = mSse ?? new ServerSentEvent();
+                                            mSse.LastEventId = fieldValue;
+                                        }
+                                        else if (String.Compare(fieldName, "retry", true) == 0)
+                                        {
+                                            int parsedRetry;
+                                            if (int.TryParse(fieldValue, out parsedRetry))
+                                            {
+                                                mSse = mSse ?? new ServerSentEvent();
+                                                mSse.Retry = parsedRetry;
+                                            }
+                                        }
+                                        else
+                                        {
+                                            // Ignore this, just log it
+                                            Trace.WriteLine("SSE A unknown line was received: " + line);
+                                        }
+                                    }
+                                }
+
+                                if (!cancelToken.IsCancellationRequested)
+                                    return this;
+                            }
+                            else // end of the stream reached
+                            {
+                                Trace.WriteLine("SSE No bytes read. End of stream.");
+                            }
+                        }
+
+                        //stream.Dispose()
+                        //stream.Close();
+                        //mResponse.Close();
+                        //mResponse.Dispose();
+                        return new DisconnectedState(mResponse.ResponseUri, mWebRequesterFactory, headers);
+                    }
+                }
+            });
+
+            t.Start();
+            return t;
+        }
+    }
+}

+ 55 - 0
Client/SSE/ConnectingState.cs

@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Net;
+using System.IO;
+using System.Threading;
+using System.Diagnostics;
+
+namespace Client.SSE
+{
+    public class ConnectingState : IConnectionState
+    {      
+
+        private Uri mUrl;
+        private IWebRequesterFactory mWebRequesterFactory;
+        private Dictionary<string, string> headers;
+
+        public EventSourceState State { get { return EventSourceState.CONNECTING; } }
+
+        public ConnectingState(Uri url, IWebRequesterFactory webRequesterFactory, Dictionary<string, string> headers)
+        {
+            if (url == null) throw new ArgumentNullException("Url cant be null");
+            if (webRequesterFactory == null) throw new ArgumentNullException("Factory cant be null");
+            mUrl = url;
+            mWebRequesterFactory = webRequesterFactory;
+            this.headers = headers;
+        }
+
+        public Task<IConnectionState> Run(Action<ServerSentEvent> donothing, CancellationToken cancelToken, Dictionary<string, string> headers)
+        {
+            IWebRequester requester = mWebRequesterFactory.Create();
+            var taskResp = requester.Get(mUrl, headers);
+
+            return taskResp.ContinueWith<IConnectionState>(tsk => 
+            {
+                if (tsk.Status == TaskStatus.RanToCompletion && !cancelToken.IsCancellationRequested)
+                {
+                    IServerResponse response = tsk.Result;
+                    if (response.StatusCode == HttpStatusCode.OK)
+                    {
+                        return new ConnectedState(response, mWebRequesterFactory, headers);
+                    }
+                    else
+                    {
+                        Trace.WriteLine("Failed to connect to: " + mUrl.ToString() + response ?? (" Http statuscode: " + response.StatusCode));
+                    }
+                }
+
+                return new DisconnectedState(mUrl, mWebRequesterFactory, headers);
+            });
+        }
+    }
+}

+ 37 - 0
Client/SSE/DisconnectedState.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public class DisconnectedState : IConnectionState 
+    {
+        private Uri mUrl;
+        private IWebRequesterFactory mWebRequesterFactory;
+        private Dictionary<string, string> headers;
+
+        public EventSourceState State
+        {
+            get { return EventSourceState.CLOSED; }
+        }
+
+        public DisconnectedState(Uri url, IWebRequesterFactory webRequesterFactory, Dictionary<string, string> headers)
+        {
+            if (url == null) throw new ArgumentNullException("Url cant be null");
+            mUrl = url;
+            mWebRequesterFactory = webRequesterFactory;
+            this.headers = headers;
+        }
+
+        public Task<IConnectionState> Run(Action<ServerSentEvent> donothing, CancellationToken cancelToken, Dictionary<string, string> headers)
+        {
+            if(cancelToken.IsCancellationRequested)
+                return Task.Factory.StartNew<IConnectionState>(() => { return new DisconnectedState(mUrl, mWebRequesterFactory, headers); });
+            else
+                return Task.Factory.StartNew<IConnectionState>(() => { return new ConnectingState(mUrl, mWebRequesterFactory, headers); });
+        }
+    }
+}

+ 116 - 0
Client/SSE/EventSource.cs

@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Threading;
+
+namespace Client.SSE
+{
+    public class EventSource
+    {   
+        public event EventHandler<StateChangedEventArgs> StateChanged;
+        public event EventHandler<ServerSentEventReceivedEventArgs> EventReceived;
+
+        public CancellationTokenSource CancellationToken { get; set; }
+
+        private IWebRequesterFactory _webRequesterFactory = new WebRequesterFactory();
+        private int _timeout = 0;
+        public Uri Url { get; private set; }
+        public EventSourceState State { get { return CurrentState.State; } }
+        public string LastEventId { get; private set; }
+        private IConnectionState mCurrentState = null;
+        private CancellationToken mStopToken;
+        private CancellationTokenSource mTokenSource = new CancellationTokenSource();
+        private Dictionary<string, string> _headers;
+
+        private IConnectionState CurrentState
+        {
+            get { return mCurrentState; }
+            set
+            {
+                if (!value.Equals(mCurrentState))
+                {
+                    StringBuilder sb = new StringBuilder("State changed from ");
+                    sb.Append(mCurrentState == null ? "Unknown" : mCurrentState.State.ToString());
+                    sb.Append(" to ");
+                    sb.Append(value == null ? "Unknown" : value.State.ToString());
+                    Trace.WriteLine(sb.ToString());
+                    mCurrentState = value;
+                    OnStateChanged(mCurrentState.State);
+                }
+            }
+        }
+
+        public EventSource(Uri url, int timeout)
+        {
+            Initialize(url, timeout);
+        }
+
+        public EventSource(Uri url, Dictionary<string, string> headers, int timeout)
+        {
+            _headers = headers;
+            Initialize(url, timeout);
+        }
+
+        /// <summary>
+        /// Constructor for testing purposes
+        /// </summary>
+        /// <param name="factory">The factory that generates the WebRequester to use.</param>
+        public EventSource(Uri url, IWebRequesterFactory factory)
+        {
+            _webRequesterFactory = factory;
+            Initialize(url, 0);
+        }
+
+        private void Initialize(Uri url, int timeout)
+        {
+            _timeout = timeout;
+            Url = url;
+            CurrentState = new DisconnectedState(Url, _webRequesterFactory, _headers);
+            Trace.WriteLine("SSE EventSource created for " + url.ToString());
+        }
+
+
+        /// <summary>
+        /// Start the EventSource. 
+        /// </summary>
+        /// <param name="stopToken">Cancel this token to stop the EventSource.</param>
+        public void Start(CancellationToken stopToken)
+        {
+            if (State == EventSourceState.CLOSED)
+            {
+                mStopToken = stopToken;
+                mTokenSource = CancellationTokenSource.CreateLinkedTokenSource(stopToken);
+                Run();
+            }
+        }
+
+        protected void Run()
+        {
+            if (mTokenSource.IsCancellationRequested && CurrentState.State == EventSourceState.CLOSED)
+                return;
+
+            mCurrentState.Run(this.OnEventReceived, mTokenSource.Token, _headers).ContinueWith(cs =>
+            {
+                CurrentState = cs.Result;
+                Run();
+            });
+        }
+
+        protected void OnEventReceived(ServerSentEvent sse)
+        {
+            if (EventReceived != null)
+            {
+                EventReceived(this, new ServerSentEventReceivedEventArgs(sse));
+            }
+        }
+
+        protected void OnStateChanged(EventSourceState newState)
+        {
+            if (StateChanged != null)
+            {
+                StateChanged(this, new StateChangedEventArgs(newState));
+            }
+        }
+    }
+}

+ 14 - 0
Client/SSE/EventSourceState.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Client.SSE
+{
+    public enum EventSourceState
+    {
+        CONNECTING,
+        OPEN,
+        CLOSED 
+    }
+}

+ 15 - 0
Client/SSE/IConnectionState.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public interface IConnectionState
+    {
+        EventSourceState State { get; }
+        Task<IConnectionState> Run(Action<ServerSentEvent> MsgReceivedCallback, CancellationToken cancelToken, Dictionary<string, string> headers);
+    }
+}

+ 17 - 0
Client/SSE/IServerResponse.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Client.SSE
+{
+    public interface IServerResponse
+    {
+        HttpStatusCode StatusCode { get; }
+
+        System.IO.Stream GetResponseStream();
+
+        Uri ResponseUri { get; }
+    }
+}

+ 15 - 0
Client/SSE/IWebRequester.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public interface IWebRequester
+    {
+        Task<IServerResponse> Get(Uri url, Dictionary<string, string> headers = null);
+
+    }
+}

+ 12 - 0
Client/SSE/IWebRequesterFactory.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Client.SSE
+{
+    public interface IWebRequesterFactory
+    {
+        IWebRequester Create();
+    }
+}

+ 39 - 0
Client/SSE/ServerResponse.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Client.SSE
+{
+    public class ServerResponse : IServerResponse
+    {
+        private System.Net.HttpWebResponse mHttpResponse;
+
+        public ServerResponse(System.Net.WebResponse webResponse)
+        {
+            this.mHttpResponse = webResponse as HttpWebResponse;
+        }
+
+        public HttpStatusCode StatusCode
+        {
+            get
+            {
+                return mHttpResponse.StatusCode;
+            }
+        }
+
+        public System.IO.Stream GetResponseStream()
+        {
+            return mHttpResponse.GetResponseStream();
+        }
+
+        public Uri ResponseUri
+        {
+            get
+            {
+                return mHttpResponse.ResponseUri;
+            }
+        }
+    }
+}

+ 26 - 0
Client/SSE/ServerSentEvent.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Client.SSE
+{
+    public class ServerSentEvent
+    {
+        public string LastEventId { get; set; }
+        public string EventType { get; set; }
+        public string Data { get; set; }
+        public int? Retry { get; set; }
+
+        public override string ToString()
+        {
+            StringBuilder sb = new StringBuilder();
+            sb.Append("EventType: ").Append(EventType).AppendLine();
+            sb.Append("Data: ").Append(Data).AppendLine();
+            sb.Append("LastEventId: ").Append(LastEventId).AppendLine();
+            if(Retry.HasValue)
+                sb.Append("Retry: ").Append(Retry.Value).AppendLine();
+            return sb.ToString();
+        }
+    }
+}

+ 17 - 0
Client/SSE/ServerSentEventReceivedEventArgs.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Client.SSE
+{
+    public class ServerSentEventReceivedEventArgs : EventArgs
+    {
+        public ServerSentEvent Message { get; private set; }
+        public ServerSentEventReceivedEventArgs(ServerSentEvent message)
+        {
+            Message = message;
+        }
+
+    }
+}

+ 16 - 0
Client/SSE/StateChangedEventArgs.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Client.SSE
+{
+    public class StateChangedEventArgs : EventArgs
+    {
+        public EventSourceState State { get; private set; }
+        public StateChangedEventArgs(EventSourceState state)
+        {
+            State = state;
+        }
+    }
+}

+ 47 - 0
Client/SSE/StringSplitter.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public class StringSplitter
+    {
+        public static string[] SplitIntoLines(string text, out string remainingText)
+        {
+            List<string> lines = new List<string>();
+
+            //bool endFound = false;
+            //bool searchingForFirstChar = true;
+            int lineLength = 0;
+            char previous = char.MinValue;
+            for (int i = 0; i < text.Length; i++)
+            {
+                char c = text[i];
+                if (c == '\n' || c == '\r')
+                {
+                    bool isCRLFPair = previous=='\r' && c == '\n';
+
+                    if (!isCRLFPair)
+                    {
+                        string line = text.Substring(i - lineLength, lineLength);
+                        lines.Add(line);
+                    }
+
+                    lineLength = 0;
+                }
+                else
+                {
+                    lineLength++;
+                }
+                previous = c;
+            }
+
+            // Save the last chars that is not followed by a lineending.
+            remainingText = text.Substring(text.Length - lineLength);
+
+            return lines.ToArray();
+        }
+    }
+}

+ 38 - 0
Client/SSE/WatchDog.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public class Watchdog
+    {
+        private long _timeout;
+        private Timer _timer;
+        public event EventHandler TimerExpired;
+
+        public void Start()
+        {
+            _timer = new Timer(new TimerCallback(OnTimerExpired), null, 0, _timeout);
+        }
+
+        public void Reset()
+        {
+            _timer.Change(0, _timeout);
+        }
+
+        private void OnTimerExpired(object State)
+        {
+            _timer.Change(Timeout.Infinite, Timeout.Infinite);
+            if (TimerExpired != null)
+                TimerExpired(this, new EventArgs());
+        }
+
+        public Watchdog(long timeout)
+        {
+            if (timeout < 1) throw new ArgumentOutOfRangeException("timeout", "timeout muste be greater than zero.");
+        }
+    }
+}

+ 30 - 0
Client/SSE/WebRequester.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public class WebRequester : IWebRequester
+    {
+        public Task<IServerResponse> Get(Uri url, Dictionary<string, string> headers = null)
+        {
+            var wreq = (HttpWebRequest)WebRequest.Create(url);
+            wreq.Method = "GET";
+            wreq.Proxy = null;
+
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    wreq.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            var taskResp = Task.Factory.FromAsync<WebResponse>(wreq.BeginGetResponse,
+                wreq.EndGetResponse, null).ContinueWith<IServerResponse>(t => new ServerResponse(t.Result));
+            return taskResp;
+
+        }
+    }
+}

+ 16 - 0
Client/SSE/WebRequesterFactory.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public class WebRequesterFactory : IWebRequesterFactory
+    {
+        public IWebRequester Create()
+        {
+            return new WebRequester();
+        }
+    }
+}

+ 26 - 23
TEAMModelFunction/ActivityHttpTrigger.cs

@@ -15,8 +15,8 @@ using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Helper.Common.CollectionHelper;
 using TEAMModelOS.SDK.Models.Cosmos;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
-using TEAMModelOS.TEAMModelFunction;
 using System.Linq;
+using TEAMModelOS.Services.Common;
 
 namespace TEAMModelFunction
 {
@@ -400,29 +400,32 @@ namespace TEAMModelFunction
             [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
             ILogger log)
         {
-            var client = _azureCosmos.GetCosmosClient();
-            List<ItemInfo> items = new List<ItemInfo>();
-            var queryslt = $"SELECT  value(c) FROM c ";
-            
-            await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<ItemInfo>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Item-hbcn") }))
-            {
-                
-                items.Add(item);
-            }
-            List<ItemCond> itemConds = new List<ItemCond>();
-            items.GroupBy(x => x.periodId).ToList().ForEach( x=> {
-                ItemCond cond = new ItemCond() { id=x.Key,code=$"ItemCond-hbcn" ,pk= "ItemCond" ,ttl=-1};
-                x.ToList().ForEach(y => {
-                   ItemService.CountItemCond(y, null, cond);
+            try {
+                var client = _azureCosmos.GetCosmosClient();
+                List<ItemInfo> items = new List<ItemInfo>();
+                var queryslt = $"SELECT  value(c) FROM c where  c.pid = null ";
+                await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<ItemInfo>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Item-hbcn") }))
+                {
+
+                    items.Add(item);
+                }
+                List<ItemCond> itemConds = new List<ItemCond>();
+                items.GroupBy(x => x.periodId).Select(y=>new {key= y.Key,list=y.ToList() }).ToList().ForEach(z => {
+                    ItemCond cond = new ItemCond() { id = z.key, code = $"ItemCond-hbcn", pk = "ItemCond", ttl = -1, count = 0, grades = new List<GradeCount>(), subjects = new List<SubjectCount>() };
+                    z.list.ForEach(y => {
+                        
+                        ItemService.CountItemCond(y, null, cond);
+                    });
+
+                    itemConds.Add(cond);
                 });
-               
-                itemConds.Add(cond);
-            });
-            itemConds.ForEach(async cond =>
-            {
-                await client.GetContainer("TEAMModelOS", "School").UpsertItemAsync<ItemCond>(cond, new PartitionKey(cond.code));
-            });
-            return new OkObjectResult(new { itemConds });
+                itemConds.ForEach(async cond =>
+                {
+                    await client.GetContainer("TEAMModelOS", "School").UpsertItemAsync<ItemCond>(cond, new PartitionKey(cond.code));
+                });
+                return new OkObjectResult(new { itemConds });
+            } catch (Exception ex) { await _dingDing.SendBotMsg($"TEAMModelFunction,ActivityHttpTrigger,fix-itemcond()\n{ex.Message}{ex.StackTrace}", GroupNames.醍摩豆服務運維群組); }
+            return new OkObjectResult(new { });
         }
         /// <summary>
         /// 设置问卷调查未初始化学生列表的业务

+ 232 - 218
TEAMModelFunction/TriggerSurvey.cs

@@ -25,260 +25,274 @@ namespace TEAMModelFunction
         public static async void Trigger( AzureServiceBusFactory _serviceBus, AzureStorageFactory _azureStorage, DingDing _dingDing,
                CosmosClient client, Document input, TriggerData tdata,AzureRedisFactory _azureRedis)
         {
-            if ((tdata.status != null && tdata.status.Value == 404) || tdata.ttl > 0)
-            {
-                return;
-            }
-            var adid = tdata.id;
-            var adcode = "";
-            string blobcntr = null;
-            if (tdata.scope == "school")
-            {
-                  adcode = $"Activity-{tdata.school}";
-                blobcntr = tdata.school;
-            }
-            else if (tdata.scope == "private"){
-                adcode = $"Activity-{tdata.creatorId}";
-                 blobcntr = tdata.creatorId;
-            }
-            Survey survey = await client.GetContainer("TEAMModelOS", "Common").ReadItemAsync<Survey>(input.Id, new Azure.Cosmos.PartitionKey($"{tdata.code}"));
-            List<ChangeRecord> changeRecords = await _azureStorage.FindListByDict<ChangeRecord>(new Dictionary<string, object>() { { "RowKey", input.Id }, { "PartitionKey", survey.progress } });
-            if (survey != null) {
-                switch (survey.progress)
+            try {
+                if ((tdata.status != null && tdata.status.Value == 404) || tdata.ttl > 0)
+                {
+                    return;
+                }
+                var adid = tdata.id;
+                var adcode = "";
+                string blobcntr = null;
+                if (tdata.scope == "school")
+                {
+                    adcode = $"Activity-{tdata.school}";
+                    blobcntr = tdata.school;
+                }
+                else if (tdata.scope == "private")
                 {
-                    case "pending":
-                        var messageSurvey = new ServiceBusMessage(new { id = input.Id, progress = "going", code = tdata.code }.ToJsonString());
-                        messageSurvey.ApplicationProperties.Add("name", "Survey");
-                        if (changeRecords.Count > 0)
-                        {
-                            await _serviceBus.GetServiceBusClient().cancelMessage(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), changeRecords[0].sequenceNumber);
-                            long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageSurvey, DateTimeOffset.FromUnixTimeMilliseconds(tdata.stime));
-                            changeRecords[0].sequenceNumber = start;
-                            await _azureStorage.SaveOrUpdate<ChangeRecord>(changeRecords[0]);
-                        }
-                        else
-                        {
-                            long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageSurvey, DateTimeOffset.FromUnixTimeMilliseconds(tdata.stime));
-                            ChangeRecord changeRecord = new ChangeRecord
+                    adcode = $"Activity-{tdata.creatorId}";
+                    blobcntr = tdata.creatorId;
+                }
+                Survey survey = await client.GetContainer("TEAMModelOS", "Common").ReadItemAsync<Survey>(input.Id, new Azure.Cosmos.PartitionKey($"{tdata.code}"));
+                List<ChangeRecord> changeRecords = await _azureStorage.FindListByDict<ChangeRecord>(new Dictionary<string, object>() { { "RowKey", input.Id }, { "PartitionKey", survey.progress } });
+                if (survey != null)
+                {
+                    switch (survey.progress)
+                    {
+                        case "pending":
+                            var messageSurvey = new ServiceBusMessage(new { id = input.Id, progress = "going", code = tdata.code }.ToJsonString());
+                            messageSurvey.ApplicationProperties.Add("name", "Survey");
+                            if (changeRecords.Count > 0)
                             {
-                                RowKey = input.Id,
-                                PartitionKey = "pending",
-                                sequenceNumber = start,
-                                msgId = messageSurvey.MessageId
-                            };
-                            await _azureStorage.Save<ChangeRecord>(changeRecord);
-                        }
-                        break;
-                    case "going":
-                        (List<string> tmdids,List<Students> students) =   await TriggerStuActivity. GetStuList(client, _dingDing, survey.classes, survey.school);
+                                await _serviceBus.GetServiceBusClient().cancelMessage(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), changeRecords[0].sequenceNumber);
+                                long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageSurvey, DateTimeOffset.FromUnixTimeMilliseconds(tdata.stime));
+                                changeRecords[0].sequenceNumber = start;
+                                await _azureStorage.SaveOrUpdate<ChangeRecord>(changeRecords[0]);
+                            }
+                            else
+                            {
+                                long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageSurvey, DateTimeOffset.FromUnixTimeMilliseconds(tdata.stime));
+                                ChangeRecord changeRecord = new ChangeRecord
+                                {
+                                    RowKey = input.Id,
+                                    PartitionKey = "pending",
+                                    sequenceNumber = start,
+                                    msgId = messageSurvey.MessageId
+                                };
+                                await _azureStorage.Save<ChangeRecord>(changeRecord);
+                            }
+                            break;
+                        case "going":
+                            (List<string> tmdids, List<Students> students) = await TriggerStuActivity.GetStuList(client, _dingDing, survey.classes, survey.school);
 #if DEBUG
-                        await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查{tdata.id}写入学生表作为活动列表!", GroupNames.成都开发測試群組);
+                            await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查{tdata.id}写入学生表作为活动列表!", GroupNames.成都开发測試群組);
 #endif
-                        List<StuActivity> stuActivities = new List<StuActivity>();
-                        List<StuActivity> tmdActivities = new List<StuActivity>();
-                        if (tmdids.IsNotEmpty()) {
-                            tmdids.ForEach(x=> {
-                                tmdActivities.Add(new StuActivity
-                                {
-                                    pk = "Activity",
-                                    id = survey.id,
-                                    code = $"Activity-{x}",
-                                    type = "Survey",
-                                    name = survey.name,
-                                    startTime = survey.startTime,
-                                    endTime = survey.endTime,
-                                    scode = survey.code,
-                                    scope = survey.scope,
-                                    school = survey.school,
-                                    creatorId = survey.creatorId,
-                                    subjects = new List<string> { "" },
-                                    blob = survey.blob,
-                                    owner = survey.owner
+                            List<StuActivity> stuActivities = new List<StuActivity>();
+                            List<StuActivity> tmdActivities = new List<StuActivity>();
+                            if (tmdids.IsNotEmpty())
+                            {
+                                tmdids.ForEach(x => {
+                                    tmdActivities.Add(new StuActivity
+                                    {
+                                        pk = "Activity",
+                                        id = survey.id,
+                                        code = $"Activity-{x}",
+                                        type = "Survey",
+                                        name = survey.name,
+                                        startTime = survey.startTime,
+                                        endTime = survey.endTime,
+                                        scode = survey.code,
+                                        scope = survey.scope,
+                                        school = survey.school,
+                                        creatorId = survey.creatorId,
+                                        subjects = new List<string> { "" },
+                                        blob = survey.blob,
+                                        owner = survey.owner
 
+                                    });
                                 });
-                            });
-                        }
-                        if (students.IsNotEmpty()) {
-                            students.ForEach(x => {
-                                stuActivities.Add(new StuActivity {
-                                    pk = "Activity",
-                                    id = survey.id,
-                                    code = $"Activity-{survey.school}-{x.id}",
-                                    type = "Survey",
-                                    name = survey.name,
-                                    startTime = survey.startTime,
-                                    endTime = survey.endTime,
-                                    scode = survey.code,
-                                    scope = survey.scope,
-                                    school = survey.school,
-                                    creatorId = survey.creatorId,
-                                    subjects = new List<string> { "" },
-                                    blob = survey.blob,
-                                    owner = survey.owner
+                            }
+                            if (students.IsNotEmpty())
+                            {
+                                students.ForEach(x => {
+                                    stuActivities.Add(new StuActivity
+                                    {
+                                        pk = "Activity",
+                                        id = survey.id,
+                                        code = $"Activity-{survey.school}-{x.id}",
+                                        type = "Survey",
+                                        name = survey.name,
+                                        startTime = survey.startTime,
+                                        endTime = survey.endTime,
+                                        scode = survey.code,
+                                        scope = survey.scope,
+                                        school = survey.school,
+                                        creatorId = survey.creatorId,
+                                        subjects = new List<string> { "" },
+                                        blob = survey.blob,
+                                        owner = survey.owner
+                                    });
                                 });
-                            });
-                        }
-                        await TriggerStuActivity.SaveStuActivity(client, _dingDing, stuActivities, tmdActivities);
-                        //向学生或醍摩豆账号发起通知
-                        #region
-                        Notice notice = new Notice()
-                        {
-                            creation = survey.startTime,
-                            expire = survey.endTime,
-                            creatorId = survey.creatorId,
-                            stuids = students,
-                            tmdids = tmdids,
-                            type = "notice",//问卷参加参加通知
-                            priority = "normal",
-                            //data = new { }.ToJsonString()
-                            msgId = survey.id,
-                            school = survey.school,
-                            scope = survey.scope,
-                            body = new Body { sid = survey.id, scode = survey.code, spk = survey.pk,  biztype = "survey-join" }
+                            }
+                            await TriggerStuActivity.SaveStuActivity(client, _dingDing, stuActivities, tmdActivities);
+                            //向学生或醍摩豆账号发起通知
+                            #region
+                            Notice notice = new Notice()
+                            {
+                                creation = survey.startTime,
+                                expire = survey.endTime,
+                                creatorId = survey.creatorId,
+                                stuids = students,
+                                tmdids = tmdids,
+                                type = "notice",//问卷参加参加通知
+                                priority = "normal",
+                                //data = new { }.ToJsonString()
+                                msgId = survey.id,
+                                school = survey.school,
+                                scope = survey.scope,
+                                body = new Body { sid = survey.id, scode = survey.code, spk = survey.pk, biztype = "survey-join" }
 
-                        };
-                        //var messageBlob = new ServiceBusMessage(notice.ToJsonString());
-                        //messageBlob.ApplicationProperties.Add("name", "Notice");
-                        //await _serviceBus.GetServiceBusClient().SendMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageBlob);
-                        #endregion
+                            };
+                            //var messageBlob = new ServiceBusMessage(notice.ToJsonString());
+                            //messageBlob.ApplicationProperties.Add("name", "Notice");
+                            //await _serviceBus.GetServiceBusClient().SendMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageBlob);
+                            #endregion
 #if DEBUG
-                        await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查{tdata.id}写入完成!", GroupNames.成都开发測試群組);
+                            await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查{tdata.id}写入完成!", GroupNames.成都开发測試群組);
 #endif
-                        var messageSurveyEnd = new ServiceBusMessage(new { id = input.Id, progress = "finish", code = tdata.code }.ToJsonString());
-                        messageSurveyEnd.ApplicationProperties.Add("name", "Survey");
-                        if (changeRecords.Count > 0)
-                        {
-                            long end = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageSurveyEnd, DateTimeOffset.FromUnixTimeMilliseconds(tdata.etime));
-                            await _serviceBus.GetServiceBusClient().cancelMessage(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), changeRecords[0].sequenceNumber);
-                            changeRecords[0].sequenceNumber = end;
-                            await _azureStorage.SaveOrUpdate<ChangeRecord>(changeRecords[0]);
-                        }
-                        else
-                        {
-                            long end = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageSurveyEnd, DateTimeOffset.FromUnixTimeMilliseconds(tdata.etime));
-                            ChangeRecord changeRecord = new ChangeRecord
+                            var messageSurveyEnd = new ServiceBusMessage(new { id = input.Id, progress = "finish", code = tdata.code }.ToJsonString());
+                            messageSurveyEnd.ApplicationProperties.Add("name", "Survey");
+                            if (changeRecords.Count > 0)
                             {
-                                RowKey = input.Id,
-                                PartitionKey = "going",
-                                sequenceNumber = end,
-                                msgId = messageSurveyEnd.MessageId
-                            };
-                            await _azureStorage.Save<ChangeRecord>(changeRecord);
-                        }
+                                long end = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageSurveyEnd, DateTimeOffset.FromUnixTimeMilliseconds(tdata.etime));
+                                await _serviceBus.GetServiceBusClient().cancelMessage(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), changeRecords[0].sequenceNumber);
+                                changeRecords[0].sequenceNumber = end;
+                                await _azureStorage.SaveOrUpdate<ChangeRecord>(changeRecords[0]);
+                            }
+                            else
+                            {
+                                long end = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync(Environment.GetEnvironmentVariable("Azure:ServiceBus:ActiveTask"), messageSurveyEnd, DateTimeOffset.FromUnixTimeMilliseconds(tdata.etime));
+                                ChangeRecord changeRecord = new ChangeRecord
+                                {
+                                    RowKey = input.Id,
+                                    PartitionKey = "going",
+                                    sequenceNumber = end,
+                                    msgId = messageSurveyEnd.MessageId
+                                };
+                                await _azureStorage.Save<ChangeRecord>(changeRecord);
+                            }
 #if DEBUG
-                        await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查{tdata.id}将于:{tdata.etime}完成并结算!", GroupNames.成都开发測試群組);
+                            await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查{tdata.id}将于:{tdata.etime}完成并结算!", GroupNames.成都开发測試群組);
 #endif
-                        break;
-                    case "finish":
+                            break;
+                        case "finish":
 #if DEBUG
-                        await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查{tdata.id}开始结算{tdata.etime}!", GroupNames.成都开发測試群組);
+                            await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查{tdata.id}开始结算{tdata.etime}!", GroupNames.成都开发測試群組);
 #endif
-                        var records = await _azureRedis.GetRedisClient(8).HashGetAllAsync($"Survey:Record:{survey.id}");
-                        var submits = await _azureRedis.GetRedisClient(8).SetMembersAsync($"Survey:Submit:{survey.id}");
-                        List<dynamic> recs = new List<dynamic>();
-                        foreach (var rcd in records)
-                        {
-                            var value = rcd.Value.ToString().ToObject<JsonElement>();
-                            recs.Add(new { index = rcd.Name.ToString(), ans = value });
-                        }
-
-                        List<dynamic> userids = new List<dynamic>();
-                        foreach (var submit in submits)
-                        {
-                            var value = submit.ToString();
-                            userids.Add(value);
-                        }
+                            var records = await _azureRedis.GetRedisClient(8).HashGetAllAsync($"Survey:Record:{survey.id}");
+                            var submits = await _azureRedis.GetRedisClient(8).SetMembersAsync($"Survey:Submit:{survey.id}");
+                            List<dynamic> recs = new List<dynamic>();
+                            foreach (var rcd in records)
+                            {
+                                var value = rcd.Value.ToString().ToObject<JsonElement>();
+                                recs.Add(new { index = rcd.Name.ToString(), ans = value });
+                            }
 
-                        List<QuestionRecord> questionRecords = new List<QuestionRecord>();
-                        //结算每道题的答题情况
-                        var ContainerClient =  _azureStorage.GetBlobContainerClient(blobcntr);
-                        //List<Task<string>> tasks = new List<Task<string>>();
-                        //获取
-                        try {
-                            List<string> items = await ContainerClient.List($"survey/{survey.id}/urecord");
-                            List<SurveyRecord> surveyRecords = new List<SurveyRecord>();
-                            foreach (string item in items)
+                            List<dynamic> userids = new List<dynamic>();
+                            foreach (var submit in submits)
                             {
-                                var Download = await _azureStorage.GetBlobContainerClient(blobcntr).GetBlobClient(item).DownloadAsync();
-                                var json = await JsonDocument.ParseAsync(Download.Value.Content);
-                                var Record = json.RootElement.ToObject<SurveyRecord>();
-                                surveyRecords.Add(Record);
+                                var value = submit.ToString();
+                                userids.Add(value);
                             }
+
+                            List<QuestionRecord> questionRecords = new List<QuestionRecord>();
+                            //结算每道题的答题情况
+                            var ContainerClient = _azureStorage.GetBlobContainerClient(blobcntr);
+                            //List<Task<string>> tasks = new List<Task<string>>();
+                            //获取
+                            try
+                            {
+                                List<string> items = await ContainerClient.List($"survey/{survey.id}/urecord");
+                                List<SurveyRecord> surveyRecords = new List<SurveyRecord>();
+                                foreach (string item in items)
+                                {
+                                    var Download = await _azureStorage.GetBlobContainerClient(blobcntr).GetBlobClient(item).DownloadAsync();
+                                    var json = await JsonDocument.ParseAsync(Download.Value.Content);
+                                    var Record = json.RootElement.ToObject<SurveyRecord>();
+                                    surveyRecords.Add(Record);
+                                }
 #if DEBUG
-                            await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查问题结算数据{surveyRecords.ToJsonString()}", GroupNames.成都开发測試群組);
+                                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查问题结算数据{surveyRecords.ToJsonString()}", GroupNames.成都开发測試群組);
 #endif
-                            for (int index = 0; index < survey.answers.Count; index++)
-                            {
-                                string url = $"{survey.id}/qrecord/{index}.json";
-                                QuestionRecord question = new QuestionRecord() { index = index };
-                                foreach (SurveyRecord record in surveyRecords)
+                                for (int index = 0; index < survey.answers.Count; index++)
                                 {
-                                    if (record.ans.Count == survey.answers.Count)
+                                    string url = $"{survey.id}/qrecord/{index}.json";
+                                    QuestionRecord question = new QuestionRecord() { index = index };
+                                    foreach (SurveyRecord record in surveyRecords)
                                     {
-                                        foreach (var an in record.ans[index])
+                                        if (record.ans.Count == survey.answers.Count)
                                         {
-                                            //
-                                            if (question.opt.ContainsKey(an))
+                                            foreach (var an in record.ans[index])
                                             {
-                                                if (question.opt[an] != null)
+                                                //
+                                                if (question.opt.ContainsKey(an))
                                                 {
-                                                    question.opt[an].Add(record.userid);
+                                                    if (question.opt[an] != null)
+                                                    {
+                                                        question.opt[an].Add(record.userid);
+                                                    }
+                                                    else
+                                                    {
+                                                        question.opt[an] = new HashSet<string>() { record.userid };
+                                                    }
                                                 }
                                                 else
                                                 {
-                                                    question.opt[an] = new HashSet<string>() { record.userid };
-                                                }
-                                            }
-                                            else
-                                            {
-                                                if (survey.answers[index].Contains(an))
-                                                {
-                                                    //如果是客观题code
-                                                    question.opt.Add(an, new HashSet<string> { record.userid });
-                                                }
-                                                else
-                                                {
-                                                    //如果不是客观code
-                                                    question.other[record.userid] = an;
+                                                    if (survey.answers[index].Contains(an))
+                                                    {
+                                                        //如果是客观题code
+                                                        question.opt.Add(an, new HashSet<string> { record.userid });
+                                                    }
+                                                    else
+                                                    {
+                                                        //如果不是客观code
+                                                        question.other[record.userid] = an;
+                                                    }
                                                 }
                                             }
                                         }
                                     }
+                                    questionRecords.Add(question);
+                                    //tasks.Add(  _azureStorage.UploadFileByContainer(blobcntr, question.ToJsonString(), "survey", url));
                                 }
-                                questionRecords.Add(question);
-                                //tasks.Add(  _azureStorage.UploadFileByContainer(blobcntr, question.ToJsonString(), "survey", url));
+                                // await Task.WhenAll(tasks);
                             }
-                           // await Task.WhenAll(tasks);
-                        } catch (Exception ex) {
-                            await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查问题结算异常{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
-                        }
-                        var cods = new { records = recs, userids, question= questionRecords };
-                        //问卷整体情况
-                        await _azureStorage.UploadFileByContainer(blobcntr, cods.ToJsonString(), "survey", $"{survey.id}/record.json");
-                        if (string.IsNullOrEmpty(survey.recordUrl))
-                        {
-                            survey.recordUrl = $"/survey/{survey.id}/record.json";
-                            await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync<Survey>(survey, survey.id, new Azure.Cosmos.PartitionKey(survey.code));
-                        }
-                        else {
-                            _azureRedis.GetRedisClient(8).KeyDelete($"Survey:Record:{survey.id}");
-                            _azureRedis.GetRedisClient(8).KeyDelete($"Survey:Submit:{survey.id}");
+                            catch (Exception ex)
+                            {
+                                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查问题结算异常{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+                            }
+                            var cods = new { records = recs, userids, question = questionRecords };
+                            //问卷整体情况
+                            await _azureStorage.UploadFileByContainer(blobcntr, cods.ToJsonString(), "survey", $"{survey.id}/record.json");
+                            if (string.IsNullOrEmpty(survey.recordUrl))
+                            {
+                                survey.recordUrl = $"/survey/{survey.id}/record.json";
+                                await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync<Survey>(survey, survey.id, new Azure.Cosmos.PartitionKey(survey.code));
+                            }
+                            else
+                            {
+                                _azureRedis.GetRedisClient(8).KeyDelete($"Survey:Record:{survey.id}");
+                                _azureRedis.GetRedisClient(8).KeyDelete($"Survey:Submit:{survey.id}");
+                                break;
+                            }
+
+                            //更新结束状态
+                            //data.progress = "finish";
+                            //if (survey.scope == "school")
+                            //{
+                            //    await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<ActivityData>(data, data.id, new Azure.Cosmos.PartitionKey(data.code));
+                            //}
+                            //else if (survey.scope == "private")
+                            //{
+                            //    await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<ActivityData>(data, data.id, new Azure.Cosmos.PartitionKey(data.code));
+                            //}
+
                             break;
-                        }
-                        
-                        //更新结束状态
-                        //data.progress = "finish";
-                        //if (survey.scope == "school")
-                        //{
-                        //    await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<ActivityData>(data, data.id, new Azure.Cosmos.PartitionKey(data.code));
-                        //}
-                        //else if (survey.scope == "private")
-                        //{
-                        //    await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<ActivityData>(data, data.id, new Azure.Cosmos.PartitionKey(data.code));
-                        //}
-                        
-                        break;
+                    }
                 }
+            } catch (Exception ex) {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}问卷调查{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+
             }
         }
     }

+ 4 - 1
TEAMModelOS.SDK/Models/Cosmos/Common/Inner/SyllabusTree.cs

@@ -23,7 +23,10 @@ namespace TEAMModelOS.SDK.Models
         /// 册别的id
         /// </summary>
         public string volumeId { get; set; }
-       // public string code { get; set; }
+        /// <summary>
+        /// 校本课纲时传入 学校编码,私人课纲时传入醍摩豆ID
+        /// </summary>
+        public string codeval { get; set; }
         public string scope { get; set; }
         public  List<SyllabusTree>  trees  { get; set; }
 

+ 182 - 8
TEAMModelOS.SDK/Models/Cosmos/Common/ItemCond.cs

@@ -5,20 +5,194 @@ using System.Text;
 namespace TEAMModelOS.SDK.Models
 {
     /// <summary>
-    /// 题目集合
+    /// 题目数量统计
     /// </summary>
     public class ItemCond : CosmosEntity
     {
-        public ItemCond() {
+        public ItemCond()
+        {
             pk = "ItemCond";
         }
+        public int count { get; set; }
+        public List<GradeCount> grades { get; set; } = new List<GradeCount>();
+
+        public List<SubjectCount> subjects { get; set; } = new List<SubjectCount>();
         //key  学段id-科目id
-        public Dictionary<string, List<CondCount>> conds { get; set; } = new Dictionary<string, List<CondCount>>();
+      //  public Dictionary<string, List<CondCount>> subjects { get; set; } = new Dictionary<string, List<CondCount>>();
+    }
+    //public class CondCount {
+    //    public KeyValuePair<string, int> grade { get; set; } = new KeyValuePair<string, int>();
+    //    public Dictionary<string, int> type { get; set; } = new Dictionary<string, int>();
+    //    public Dictionary<int, int> level { get; set; } = new Dictionary<int, int>();
+    //    public Dictionary<int, int> field { get; set; } = new Dictionary<int, int>();
+    //}
+
+    public class SubjectCount
+    {
+        public string id { get; set; }
+        public int count { get; set; }
+        public Dictionary<string, Dictionary<string, Dictionary<string, int>>> types { get; set; } = new Dictionary<string, Dictionary<string, Dictionary<string, int>>>()
+        {
+            {
+                "compose",new Dictionary<string, Dictionary<string, int>>
+                {
+                    { 
+                        "level",new Dictionary<string, int> 
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }
+                        } 
+                    },
+                    {
+                        "field",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }, { "6", 0 }
+                        }
+                    }
+                }
+            },
+            {
+                "single",new Dictionary<string, Dictionary<string, int>>
+                {
+                    {
+                        "level",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }
+                        }
+                    },
+                    {
+                        "field",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }, { "6", 0 }
+                        }
+                    }
+                }
+            },
+            {
+                "multiple",new Dictionary<string, Dictionary<string, int>>
+                {
+                    {
+                        "level",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }
+                        }
+                    },
+                    {
+                        "field",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }, { "6", 0 }
+                        }
+                    }
+                }
+            },
+            {
+                "sortmultiple",new Dictionary<string, Dictionary<string, int>>
+                {
+                    {
+                        "level",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }
+                        }
+                    },
+                    {
+                        "field",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }, { "6", 0 }
+                        }
+                    }
+                }
+            },
+            {
+                "judge",new Dictionary<string, Dictionary<string, int>>
+                {
+                    {
+                        "level",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }
+                        }
+                    },
+                    {
+                        "field",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }, { "6", 0 }
+                        }
+                    }
+                }
+            },
+            {
+                "complete",new Dictionary<string, Dictionary<string, int>>
+                {
+                    {
+                        "level",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }
+                        }
+                    },
+                    {
+                        "field",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }, { "6", 0 }
+                        }
+                    }
+                }
+            },
+            {
+                "subjective",new Dictionary<string, Dictionary<string, int>>
+                {
+                    {
+                        "level",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }
+                        }
+                    },
+                    {
+                        "field",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }, { "6", 0 }
+                        }
+                    }
+                }
+            },
+            {
+                "connector",new Dictionary<string, Dictionary<string, int>>
+                {
+                    {
+                        "level",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }
+                        }
+                    },
+                    {
+                        "field",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }, { "6", 0 }
+                        }
+                    }
+                }
+            },
+            {
+                "correct",new Dictionary<string, Dictionary<string, int>>
+                {
+                    {
+                        "level",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }
+                        }
+                    },
+                    {
+                        "field",new Dictionary<string, int>
+                        {
+                            { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }, { "6", 0 }
+                        }
+                    }
+                }
+            }
+        };
     }
-    public class CondCount {
-        public KeyValuePair<string, int> grade { get; set; } = new KeyValuePair<string, int>();
-        public Dictionary<string, int> type { get; set; } = new Dictionary<string, int>();
-        public Dictionary<int, int> level { get; set; } = new Dictionary<int, int>();
-        public Dictionary<int, int> field { get; set; } = new Dictionary<int, int>();
+
+
+    public class GradeCount
+    {
+        public string id { get; set; }
+        public int count { get; set; }
     }
 }

+ 1 - 0
TEAMModelOS.SDK/Models/Cosmos/Common/Syllabus.cs

@@ -20,6 +20,7 @@ namespace TEAMModelOS.SDK.Models
         [Required(ErrorMessage = "{0} 必须填写")]
         public List<Tnode> children { get; set; }
         public string volumeId { get; set; }
+        public string codeval { get; set; }
         public List<SyllabusAuth> auth { get; set; } = new List<SyllabusAuth>();
         public string scope { get; set; }
 }

+ 55 - 56
TEAMModelOS.SDK/Models/Service/ItemService.cs

@@ -4,7 +4,7 @@ using System.Linq;
 using System.Threading.Tasks;
 using TEAMModelOS.SDK.Models;
 
-namespace TEAMModelOS.TEAMModelFunction
+namespace TEAMModelOS.Services.Common
 {
     public static class ItemService
     {
@@ -14,13 +14,14 @@ namespace TEAMModelOS.TEAMModelFunction
         /// <param name="newItem"></param>
         /// <param name="odlItem"></param>
         /// <param name="cond"></param>
-        public static void CountItemCond(ItemInfo newItem, ItemInfo odlItem, ItemCond cond) {
+        public static void CountItemCond(ItemInfo newItem, ItemInfo odlItem, ItemCond cond)
+        {
             //检查两个对象是否是同一条记录
             if (newItem != null && odlItem == null)
             {
                 string newKey = $"{newItem.subjectId}";
                 List<string> grade = newItem.gradeIds;
-                UpdateItemCond(cond, true,newKey, grade, newItem.type, newItem.level, newItem.field.HasValue?newItem.field.Value:0);
+                UpdateItemCond(cond, true, newKey, grade, newItem.type, newItem.level, newItem.field.HasValue ? newItem.field.Value : 0);
             }
             else if (newItem != null && odlItem != null)
             {
@@ -43,16 +44,19 @@ namespace TEAMModelOS.TEAMModelFunction
                 List<string> oldGrade = odlItem.gradeIds;
                 UpdateItemCond(cond, false, oldKey, oldGrade, odlItem.type, odlItem.level, odlItem.field.HasValue ? odlItem.field.Value : 0);
             }
-            else {
-               // throw new Exception();
+            else
+            {
+                // throw new Exception();
             }
         }
         /// <summary>
         /// opt=false  减  true 增
+        /// key  科目id
+        /// 
         /// </summary>
         /// <param name="cond"></param>
         /// <param name="opt"></param>
-        public static void UpdateItemCond(ItemCond cond, bool opt,string key,List<string> grade,string type,int level,int field)
+        public static void UpdateItemCond(ItemCond cond, bool opt, string key, List<string> grade, string type, int level, int field)
         {
             int count = 0;
             if (opt)
@@ -64,64 +68,59 @@ namespace TEAMModelOS.TEAMModelFunction
                 //未计入的则默认0
                 count = -1;
             }
-            if (cond.conds.ContainsKey(key))
+            grade.ForEach(x => {
+                bool none = true;
+                for (int index = 0; index < cond.grades.Count; index++)
+                {
+                    if (x == cond.grades[index].id)
+                    {
+                        cond.grades[index].count = cond.grades[index].count + count;
+                        none = false;
+                        break;
+                    }
+                }
+                if (none)
+                {
+                    cond.grades.Add(new GradeCount { id = x, count = count });
+                }
+            });
+         
+            SubjectCount subject = cond.subjects.Where(x => x.id == key).FirstOrDefault();
+            if (subject == null)
             {
-                foreach (var x in grade)
+                cond.subjects.Add(new SubjectCount() { id=key});
+            }
+            cond.subjects.Where(x => x.id == key).ToList().ForEach(y => {
+               
+                if (!y.types.ContainsKey(type))
                 {
-                    var exCondCount= cond.conds[key].Where(y => y.grade.Key.Equals(x)).FirstOrDefault();
-                    if (exCondCount != null)
+                    var dict = new Dictionary<string, Dictionary<string, int>>
                     {
-                        exCondCount.grade= new KeyValuePair<string, int> (x, exCondCount.grade.Value + count);
-                        if (exCondCount.type.ContainsKey(type))
                         {
-                            exCondCount.type[type] = exCondCount.type[type] + count;
-                        }
-                        else {
-                            exCondCount.type.Add(type, count);
-                        }
-                        if (exCondCount.level.ContainsKey(level))
-                        {
-                            exCondCount.level[level] = exCondCount.level[level] + count;
-                        }
-                        else
+                            "level",new Dictionary<string, int>
+                            {
+                                { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }
+                            }
+                        },
                         {
-                            exCondCount.level.Add(level, count);
+                            "field",new Dictionary<string, int>
+                            {
+                                { "1",0},{ "2",0}, { "3", 0 }, { "4", 0 }, { "5", 0 }, { "6", 0 }
+                            }
                         }
-                        if (exCondCount.field.ContainsKey(field))
-                        {
-                            exCondCount.field[field] = exCondCount.field[field] + count;
-                        }
-                        else
-                        {
-                            exCondCount.field.Add(field, count);
-                        }
-                    }
-                    else {
-                        CondCount condCount = new CondCount
-                        {
-                            grade = new KeyValuePair<string, int>(x, count),
-                            type = new Dictionary<string, int> { { type, count } },
-                            field = new Dictionary<int, int> { { field, count } },
-                            level = new Dictionary<int, int> { { level, count } }
-                        };
-                        cond.conds[key].Add(condCount);
-                    }
-                }
-            }
-            else {
-                List<CondCount> conds = new List<CondCount>();
-                foreach (var x in grade) {
-                    CondCount condCount = new CondCount {
-                        grade = new KeyValuePair<string, int>(x, count),
-                        type = new Dictionary<string, int> { { type, count } },
-                        field = new Dictionary<int, int> { { field, count } },
-                        level= new Dictionary<int, int> { { level, count } }
                     };
-                    conds.Add(condCount);
+                    dict["level"][$"{level}"] = count;
+                    dict["field"][$"{field}"] = count;
+                    y.types.Add(type, dict);
                 }
-                cond.conds.Add(key, conds);
-            }
+                else
+                {
+                    y.types[type]["level"][$"{level}"] = y.types[type]["level"][$"{level}"] + count;
+                    y.types[type]["field"][$"{level}"] = y.types[type]["field"][$"{field}"] + count;
+                }
+                y.count = y.count + count;
+            });
+            cond.count = cond.subjects.Select(x => x.count).Sum();
         }
-
     }
 }

+ 1 - 0
TEAMModelOS.SDK/TEAMModelOS.SDK.csproj

@@ -21,6 +21,7 @@
     <PackageReference Include="ClouDASLibx" Version="1.2.7" />
     <PackageReference Include="DocumentFormat.OpenXml" Version="2.12.3" />
     <PackageReference Include="HtmlAgilityPack" Version="1.11.32" />
+    <PackageReference Include="Lib.AspNetCore.ServerSentEvents" Version="6.0.0" />
     <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.10" />
     <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
     <PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="5.0.4" />

+ 7 - 1
TEAMModelOS.sln

@@ -11,7 +11,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TEAMModelGrpc", "TEAMModelG
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TEAMModelFunction", "TEAMModelFunction\TEAMModelFunction.csproj", "{78470113-6261-4F9A-9EF3-E315F060813D}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TEAMModelAPI", "TEAMModelAPI\TEAMModelAPI.csproj", "{2146FEEC-7178-4141-A8C7-CBEBAEE404A6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TEAMModelAPI", "TEAMModelAPI\TEAMModelAPI.csproj", "{2146FEEC-7178-4141-A8C7-CBEBAEE404A6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{E1A7F6EA-319E-4582-A800-A04DEB8284D9}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -39,6 +41,10 @@ Global
 		{2146FEEC-7178-4141-A8C7-CBEBAEE404A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{2146FEEC-7178-4141-A8C7-CBEBAEE404A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{2146FEEC-7178-4141-A8C7-CBEBAEE404A6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E1A7F6EA-319E-4582-A800-A04DEB8284D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E1A7F6EA-319E-4582-A800-A04DEB8284D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E1A7F6EA-319E-4582-A800-A04DEB8284D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E1A7F6EA-319E-4582-A800-A04DEB8284D9}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 1 - 1
TEAMModelOS/ClientApp/.eslintrc.js

@@ -12,7 +12,7 @@ module.exports = {
   ],
   'rules': {
     'indent': 'off',
-    'no-console': 'off',
+    'no-console': 'on',
     'space-before-function-paren': [2, 'never'],
     'vue/no-parsing-error': [2, {
       'x-invalid-end-tag': false

+ 1 - 1
TEAMModelOS/ClientApp/package.json

@@ -140,7 +140,7 @@
 		"mini-css-extract-plugin": "^0.5.0",
 		"node-sass": "^4.14.1",
 		"optimize-css-assets-webpack-plugin": "^5.0.3",
-		"postcss": "^7.0.32",
+		"postcss": "^7.0.36",
 		"postcss-loader": "^3.0.0",
 		"sass-loader": "^7.1.0",
 		"style-loader": "^0.23.1",

+ 1 - 0
TEAMModelOS/ClientApp/public/index.html

@@ -29,6 +29,7 @@
 			let cloudSetting = localStorage.getItem('cloudSetting')
 			if (cloudSetting) {
 				cloudSetting = JSON.parse(cloudSetting)
+				document.documentElement.setAttribute('lang', cloudSetting.curLang || navigator.language);
 				if (cloudSetting.curTheme && cloudSetting.curTheme == 'light') {
 					document.getElementById('theme').href = '/theme/light-theme.css'
 				}

+ 34 - 12
TEAMModelOS/ClientApp/src/api/blob.js

@@ -55,12 +55,23 @@ export default {
     updateSize: function (data) {
         return post('/blob/update-blobsize', data)
     },
-    /**
-     * 上传文件之后保存文件的描述信息
-     * opt: add 更新
-     * opt: del 删除
-     **/
-    upsertBlobInfo: function (data) {
+    /*** 新增 编辑接口
+    {
+        "periodId": "",
+        "scope": "school",
+        "name": "hbcn",
+        "url": "video/xxx.png",
+        "opt": "add",
+    }
+     {
+        "scope": "school",
+        "name": "hbcn",
+        "opt": "del",
+        "id": "19ccce98-c524-4ea7-aabc-887d1391e551"
+    }
+     */
+
+    BlobInfoMgt: function (data) {
         return post('/blob/bloblog-opt', data)
     },
     /**
@@ -75,21 +86,32 @@ export default {
                     console.log(res)
                     let blobs = []
                     res.bloblogs.forEach(item => {
-                        let blobName = item.url
-                        let info = getExAndType(item.url)
                         let i = {
-                            url: host + '/' +container + '/' + item.url,
+                            url: host + '/' + container + '/' + item.url,
                             blob: '/' + item.url,
-                            name: blobName.substring(blobName.lastIndexOf('/') + 1),
+                            // name: blobName.substring(blobName.lastIndexOf('/') + 1),
                             size: item.size,
                             createTime: item.time,
-                            extension: info.ex,
-                            type: info.type,
+                            // extension: info.ex,
+                            // type: info.type,
                             periodId: item.periodId,
                             subjectId: item.subjectId,
                             gradeId: item.gradeId,
                             id: item.id
                         }
+                        if (item.type == 'res') {
+                            let b = item.url.replace('/index.json','.HTEX')
+                            i.type = 'res',
+                            i.extension = 'HTEX',
+                            i.name = b.substring(b.lastIndexOf('/') + 1)
+                        } else {
+                            let blobName = item.url
+                            let info = getExAndType(item.url)
+                            i.name = blobName.substring(blobName.lastIndexOf('/') + 1)
+                            i.extension = info.ex
+                            i.type = info.type
+                        }
+
                         blobs.push(i)
                     })
                     r(blobs)

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

@@ -15,6 +15,7 @@ import courseMgmt from './courseMgmt'
 import login from './login'
 import schoolList from './schoolList'
 import newEvaluation from './newEvaluation'
+import evaluation from './evaluation'
 import learnActivity from './learnActivity'
 import questionnaire from './questionnaire'
 import teachMgmt from './teachMgmt'
@@ -48,6 +49,7 @@ export default {
     blob,
     courseMgmt,
     newEvaluation,
+	evaluation,
     login,
     schoolList,
     teachMgmt,

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

@@ -43,5 +43,12 @@ export default{
      */
     delOpen: function (data) {
         return post('/open-api/delete-app', data)
+    },
+    /**
+     * 获取订阅通知
+     * @param {} data
+     */
+     getWebhook: function (data) {
+        return post('/open-api/get-webhook', data)
     }
 }

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

@@ -8,4 +8,16 @@ export default {
     getIdProfile: function (host,data) {
         return post(`${host}/oauth2/profile`, data)
     },
+    /* 发送短信验证码 */
+    sandMsgCode: function (host,data) {
+        return post(`${host}/service/sandsms/pin`, data)
+    },
+    /* 发送邮件验证码 */
+    sandMailCode: function (host,data) {
+        return post(`${host}/service/sandmail/pin`, data)
+    },
+    /* 裝置或服務取得金鑰及刷新金鑰 */
+    getToken: function (host,data) {
+        return post(`${host}/oauth2/token`, data)
+    },
 }

+ 3 - 0
TEAMModelOS/ClientApp/src/api/syllabus.js

@@ -31,6 +31,9 @@ export default {
 	ShareAgree:function(data) {
 	    return post('/teacher/share/agree-share', data)
 	},
+	CheckLink:function(data) {
+	    return post('/common/syllabus/check-link', data)
+	},
 	// 查找知识块数量
 	FindBlockCount: function (data) {
 		return post('/knowledges/find-count', data)

File diff suppressed because it is too large
+ 1 - 0
TEAMModelOS/ClientApp/src/assets/icon_student.svg


File diff suppressed because it is too large
+ 1 - 0
TEAMModelOS/ClientApp/src/assets/icon_teacher.svg


File diff suppressed because it is too large
+ 1 - 0
TEAMModelOS/ClientApp/src/assets/ies5_logg.svg


File diff suppressed because it is too large
+ 1 - 1
TEAMModelOS/ClientApp/src/assets/ies5_logo.svg


File diff suppressed because it is too large
+ 1 - 0
TEAMModelOS/ClientApp/src/assets/ies5_logo_2.svg


二進制
TEAMModelOS/ClientApp/src/assets/image/img_def.png


二進制
TEAMModelOS/ClientApp/src/assets/image/video_def.png


File diff suppressed because it is too large
+ 1 - 0
TEAMModelOS/ClientApp/src/assets/login/icon_student.svg


File diff suppressed because it is too large
+ 1 - 0
TEAMModelOS/ClientApp/src/assets/login/icon_teacher.svg


File diff suppressed because it is too large
+ 1 - 0
TEAMModelOS/ClientApp/src/assets/login/ies5_logo_2.svg


二進制
TEAMModelOS/ClientApp/src/assets/login/login_bg.jpg


+ 18 - 18
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/event-list.css

@@ -5,6 +5,7 @@
 /*篩選器區域*/
 .event-selector-block {
   padding: 17px 25px;
+  /*修改iviewTab控件*/
 }
 .event-selector-block .list-title {
   font-size: 18px;
@@ -88,14 +89,12 @@
   font-weight: 400;
   padding-left: 20px;
 }
-/*篩選器區域*/
-/*修改iviewTab控件*/
-.ivu-tabs-nav {
+.event-selector-block .ivu-tabs-nav {
   float: none !important;
   position: relative;
   text-align: center;
 }
-.ivu-tabs-tab {
+.event-selector-block .ivu-tabs-tab {
   width: auto;
   padding: 8px 20px;
   position: relative;
@@ -104,58 +103,59 @@
   font-weight: 600;
   margin: 0px 30px;
 }
-.ivu-tabs-tab:hover {
+.event-selector-block .ivu-tabs-tab:hover {
   color: #24b880 !important;
 }
-.ivu-tabs-tab.ivu-tabs-tab-active.ivu-tabs-tab-focused {
+.event-selector-block .ivu-tabs-tab.ivu-tabs-tab-active.ivu-tabs-tab-focused {
   color: #24b880 !important;
   width: auto;
   padding: 8px 20px;
   border-bottom: 7px solid #24b880 !important;
   text-align: center;
 }
-.ivu-tabs-ink-bar {
+.event-selector-block .ivu-tabs-ink-bar {
   height: 0px;
 }
-.ivu-tabs-tabpane {
+.event-selector-block .ivu-tabs-tabpane {
   margin-top: -16px;
   height: 830px;
   overflow: auto;
 }
-.event-list {
+.event-selector-block .event-list {
   z-index: 10;
   /*修改iviewTab控件*/
 }
-.event-list .list-block {
+.event-selector-block .event-list .list-block {
   border-top: 1px solid rgba(0, 0, 0, 0.1);
   overflow: auto;
 }
 @media screen and (max-width: 1366px) {
-  .event-list .icon-selector .icon-btn {
+  .event-selector-block .event-list .icon-selector .icon-btn {
     height: 40px;
     width: 40px;
   }
-  .event-list .icon-selector .icon-btn .innerIcon {
+  .event-selector-block .event-list .icon-selector .icon-btn .innerIcon {
     width: 22px;
     height: 22px;
     position: relative;
   }
-  .event-list .icon-selector .icon-btn .Icon-0,
-  .event-list .icon-selector .icon-btn .Icon-1,
-  .event-list .icon-selector .icon-btn .Icon-4 {
+  .event-selector-block .event-list .icon-selector .icon-btn .Icon-0,
+  .event-selector-block .event-list .icon-selector .icon-btn .Icon-1,
+  .event-selector-block .event-list .icon-selector .icon-btn .Icon-4 {
     top: 22%;
     left: 23%;
   }
-  .event-list .icon-selector .icon-btn .Icon-2 {
+  .event-selector-block .event-list .icon-selector .icon-btn .Icon-2 {
     top: 24%;
     left: 30%;
   }
-  .event-list .icon-selector .icon-btn .Icon-3 {
+  .event-selector-block .event-list .icon-selector .icon-btn .Icon-3 {
     top: 22%;
     left: 26%;
   }
-  .event-list .icon-selector .icon-btn .Icon-5 {
+  .event-selector-block .event-list .icon-selector .icon-btn .Icon-5 {
     top: 22%;
     left: 26%;
   }
 }
+/*篩選器區域*/

+ 68 - 67
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/event-list.less

@@ -95,85 +95,86 @@
       padding-left: 20px;
     }
   }
-}
 
-/*篩選器區域*/
+  /*修改iviewTab控件*/
+  .ivu-tabs-nav {
+    float: none !important;
+    position: relative;
+    text-align: center;
+  }
 
-/*修改iviewTab控件*/
-.ivu-tabs-nav {
-  float: none !important;
-  position: relative;
-  text-align: center;
-}
+  .ivu-tabs-tab {
+    width: auto;
+    padding: 8px 20px;
+    position: relative;
+    font-size: 14px;
+    color: #878787;
+    font-weight: 600;
+    margin: 0px 30px;
+    &:hover {
+      color: @primary !important;
+    }
+  }
 
-.ivu-tabs-tab {
-  width: auto;
-  padding: 8px 20px;
-  position: relative;
-  font-size: 14px;
-  color: #878787;
-  font-weight: 600;
-  margin: 0px 30px;
-  &:hover {
+  .ivu-tabs-tab.ivu-tabs-tab-active.ivu-tabs-tab-focused {
     color: @primary !important;
+    width: auto;
+    padding: 8px 20px;
+    border-bottom: 7px solid @primary !important;
+    text-align: center;
   }
-}
-
-.ivu-tabs-tab.ivu-tabs-tab-active.ivu-tabs-tab-focused {
-  color: @primary !important;
-  width: auto;
-  padding: 8px 20px;
-  border-bottom: 7px solid @primary !important;
-  text-align: center;
-}
-
-.ivu-tabs-ink-bar {
-  height: 0px;
-}
 
-.ivu-tabs-tabpane {
-  margin-top: -16px;
+  .ivu-tabs-ink-bar {
+    height: 0px;
+  }
 
-  height: 830px;
-  overflow: auto;
-}
-.event-list {
-  z-index: 10;
-  /*修改iviewTab控件*/
-  .list-block {
-    border-top: 1px solid @border;
+  .ivu-tabs-tabpane {
+    margin-top: -16px;
 
+    height: 830px;
     overflow: auto;
   }
-  @media screen and (max-width: 1366px) {
-    .icon-selector {
-      .icon-btn {
-        height: 40px;
-        width: 40px;
-        .innerIcon {
-          width: 22px;
-          height: 22px;
-          position: relative;
-        }
-        .Icon-0,
-        .Icon-1,
-        .Icon-4 {
-          top: 22%;
-          left: 23%;
-        }
-        .Icon-2 {
-          top: 24%;
-          left: 30%;
-        }
-        .Icon-3 {
-          top: 22%;
-          left: 26%;
-        }
-        .Icon-5 {
-          top: 22%;
-          left: 26%;
+  .event-list {
+    z-index: 10;
+    /*修改iviewTab控件*/
+    .list-block {
+      border-top: 1px solid @border;
+
+      overflow: auto;
+    }
+    @media screen and (max-width: 1366px) {
+      .icon-selector {
+        .icon-btn {
+          height: 40px;
+          width: 40px;
+          .innerIcon {
+            width: 22px;
+            height: 22px;
+            position: relative;
+          }
+          .Icon-0,
+          .Icon-1,
+          .Icon-4 {
+            top: 22%;
+            left: 23%;
+          }
+          .Icon-2 {
+            top: 24%;
+            left: 30%;
+          }
+          .Icon-3 {
+            top: 22%;
+            left: 26%;
+          }
+          .Icon-5 {
+            top: 22%;
+            left: 26%;
+          }
         }
       }
     }
   }
 }
+
+/*篩選器區域*/
+

+ 3 - 0
TEAMModelOS/ClientApp/src/boot-app.js

@@ -139,6 +139,9 @@ Vue.use(ViewUI, {
     i18n: (key, value) => i18n.t(key, value)
 })
 
+//head title
+document.title = i18n.t('headTitle')
+
 sync(store, router)
 
 const app = new Vue({

+ 2 - 2
TEAMModelOS/ClientApp/src/common/BaseLayout.less

@@ -56,7 +56,7 @@
 }
 
 .collapsed-logo-width {
-    height: 30px !important;
+    height: 36px !important;
     width: 90px !important;
     transition: width 0.2s, height 0.2s;
 }
@@ -69,7 +69,7 @@
 	margin-left: 20px;
 
     .unit-logo {
-        height: 30px;
+        height: 40px;
     }
 
     .unit-name {

+ 1 - 1
TEAMModelOS/ClientApp/src/common/BaseLayout.vue

@@ -3,7 +3,7 @@
         <!-- 头部菜单栏 -->
         <Header class="header">
             <div class="logo-wrap">
-                <img src="../assets/ies5.png" :class="isCollapsed ? 'collapsed-logo-width unit-logo':'collapsed-logo-width  unit-logo'" v-show="isShowLogo" />
+                <img src="../assets/login/ies5_logo_2.svg" :class="isCollapsed ? 'collapsed-logo-width unit-logo':'collapsed-logo-width  unit-logo'" v-show="isShowLogo" />
             </div>
             <div class="school-wrap">
                 <BaseSelectSchool></BaseSelectSchool>

+ 3 - 2
TEAMModelOS/ClientApp/src/common/BaseUserPoptip.vue

@@ -20,7 +20,7 @@
                     <DropdownItem class="user-info-wrap" @click.native="toUserCenter()">
                         <p>{{userInfo.username}}</p>
                         <p class="user-id">{{`ID: ${user.id}`}}</p>
-                        <Icon class="user-info-arrow" type="ios-arrow-forward" color="#1cc0f3" />
+                        <Icon class="user-info-arrow" type="ios-arrow-forward" color="#fff" />
                     </DropdownItem>
                     <DropdownItem class="drop-item" style="margin-top:8px" @click.native="onRoleSelect('student')">
                         <Icon type="md-swap" class="drop-item-icon" />
@@ -226,7 +226,8 @@ export default {
     margin-top: -25px;
 }
 .user-info-wrap {
-    /* background: #608f94; */
+    background: #608f94;
+    color: white;
     padding: 12px 16px;
     box-shadow: 0px 2px 5px #eee;
 }

+ 69 - 36
TEAMModelOS/ClientApp/src/common/UploadModal.vue

@@ -285,41 +285,64 @@ export default {
             //已完成上传操作,关闭对话框、emit
             if (this.isComplete) {
                 //保存Blob描述信息
-                //API设计可以优化,如果API参数为对象数据,这里可以避免for循环里面发起请求影响性能
+                //API设计可以优化,如果API参数为对象数据,这里可以避免for循环里面发起请求影响性能 (API已优化)
+                let requestData
                 if (this.routerScope == 'school') {
+                    requestData = {
+                        periodId: this.tags.period,
+                        gradeId: this.tags.gradeId.map(item => item + ''),
+                        subjectId: this.tags.subjectId,
+                        scope: 'school',
+                        name: this.$store.state.userInfo.schoolCode,
+                        url: [],
+                        opt: 'add'
+                    }
                     this.uploadedList.forEach(item => {
                         item.periodId = this.tags.period
                         item.gradeId = this.tags.gradeId.map(item => item + '')
                         item.subjectId = this.tags.subjectId
-                        let requestData = {
-                            periodId: this.tags.period,
-                            gradeId: this.tags.gradeId.map(item => item + ''),
-                            subjectId: this.tags.subjectId,
-                            scope: 'school',
-                            name: this.$store.state.userInfo.schoolCode,
-                            url: item.blob.substring(1, item.blob.length),
-                            opt: 'add'
-                        }
-                        this.$api.blob.upsertBlobInfo(requestData)
+                        requestData.url.push(item.blob.substring(1, item.blob.length))
                     })
                 }
                 //个人资源不用关联学段、学科、年级等信息
                 else {
+                    requestData = {
+                        periodId: [],
+                        gradeId: [],
+                        subjectId: [],
+                        scope: 'private',
+                        name: this.$store.state.userInfo.TEAMModelId,
+                        url: [],
+                        opt: 'add'
+                    }
                     this.uploadedList.forEach(item => {
-                        let requestData = {
-                            periodId: [],
-                            gradeId: [],
-                            subjectId: [],
-                            scope: 'private',
-                            name: this.$store.state.userInfo.TEAMModelId,
-                            url: item.blob.substring(1, item.blob.length),
-                            opt: 'add'
-                        }
-                        this.$api.blob.upsertBlobInfo(requestData)
+                        requestData.url.push(item.blob.substring(1, item.blob.length))
                     })
                 }
-                this.$emit("successData", this.uploadedList)
-                this.uploadedList = []
+                this.$api.blob.BlobInfoMgt(requestData).then(
+                    res => {
+                        if (!res.error) {
+                            //添加保存成功的id,返回直接删除可以获取到id
+                            this.uploadedList.forEach(item => {
+                                for (let log of res.bloblog) {
+                                    log.url = '/' + log.url
+                                    if (log.url == item.blob){
+                                        item.id = log.id
+                                        break
+                                    }
+                                }
+                            })
+                            this.$emit("successData", this.uploadedList)
+                            this.uploadedList = []
+                        } else {
+                            this.$Message.error('API error')
+                        }
+                    },
+                    err => {
+                        this.$Message.error('API error')
+                    }
+                )
+
             } else { //还未上传文件,则上传文件
                 this.textLoading = true
                 this.uploadedList.forEach((item, index) => {
@@ -357,10 +380,13 @@ export default {
                                 scope: this.routerScope
                             }).then(
                                 parseRes => {
+                                    console.log('res',res)
+                                    let filename = res.name.substring(0,res.name.lastIndexOf('.'))
+                                    console.log(filename)
                                     this.containerClient.deleteBlob(delBlob, res.size)
-                                    this.uploadedList[index].blob = `/res/${res.name}/index.json`
+                                    this.uploadedList[index].blob = `/res/${filename}/index.json`
                                     this.uploadedList[index].extension = 'HTEX'
-                                    this.uploadedList[index].name = res.name.replace('pptx', 'HTEX').replace('PPTX', 'HTEX')
+                                    this.uploadedList[index].name = filename + '.HTEX'
                                     this.uploadedList[index].type = 'res'
                                 },
                                 parseErr => {
@@ -433,18 +459,25 @@ export default {
         //处理视频封面
         async uploadPoater(file) {
             let n = file.name.substring(0, file.name.lastIndexOf('.'))
-            let dataUrl = await this.$jsFn.createVideoPoster(file.url + this.sasString, file.name, 0.1)
-            let f = this.$jsFn.dataURLtoFile(dataUrl, n + '.png')
-            this.containerClient.upload(f, 'thum').then(
-                res => {
-                    console.log(this.$t('updModal.posterOk'))
-                },
-                err => {
-                    console.log(this.$t('updModal.posterErr'))
-                }
-            ).finally(() => {
+            try {
+                let dataUrl = await this.$jsFn.createVideoPoster(file.url + this.sasString, file.name, 0.1)
+                console.log(dataUrl)
+                let f = this.$jsFn.dataURLtoFile(dataUrl, n + '.png')
+                this.containerClient.upload(f, 'thum').then(
+                    res => {
+                        console.log(this.$t('updModal.posterOk'))
+                    },
+                    err => {
+                        console.log(this.$t('updModal.posterErr'))
+                    }
+                ).finally(() => {
+                    this.counterReduce()
+                })
+            } catch {
+                this.$Message.warning(`${file.name}不支持截取封面和在线播放`)
                 this.counterReduce()
-            })
+            }
+
         }
     },
     created() {

+ 34 - 20
TEAMModelOS/ClientApp/src/components/public/frontEndMain/Index.less

@@ -9,34 +9,44 @@
     &-mark{
         position: fixed;
         left:30px;
-        top:30px;
+        top:20px;
     }
     &-schoolName{
         position: fixed;
         left: calc(50% - 300px);
         top: 40px;
         width: 100%;
-        max-width: 600px;
-        color: white;
-        font-size: 45px;
-        overflow: hidden;
-        white-space: nowrap;
-        text-overflow: ellipsis;
-        text-align: center;        
-    }
-    &-server{
         position: fixed;
-        right:30px;
-        top:30px;
-        h5 {
-            font-weight: 100;
-            margin-bottom: 5px;
-            color: #ffffff;
+        top: 0;
+        z-index: 100;
+        left: 0;
+        &-mark{
+            display: flex;
+            align-items: center;
+            .schoolName{
+                text-align: left;
+                color: #e0e0e0;
+                font-size: 19px;
+                overflow: hidden;
+                white-space: nowrap;
+                text-overflow: ellipsis;
+                margin-left: 20px;
+                letter-spacing: 1px;
+            }
         }
-    }
+        &-server{
+            display: flex;
+            flex-direction: column;
+            h5 {
+                font-weight: 100;
+                margin-bottom: 5px;
+                color: #ffffff;
+                letter-spacing: 1px;
+            }
+        }
+    }    
     &-body{
-        padding-top: 100px;
-        height: 95%;
+        height: 100%;
         display: -webkit-flex;
         display:         flex;
         -webkit-align-items: center;
@@ -45,7 +55,11 @@
                 justify-content: center;
     }
     &-footer{
-        height: 5%;
+        width: 100%;
+        position: fixed;
+        bottom: 0;
+        z-index: 100;
+        left: 0;
         display: flex;
         justify-content: center;
         align-items: center;

+ 27 - 15
TEAMModelOS/ClientApp/src/components/public/frontEndMain/Index.vue

@@ -1,12 +1,22 @@
 <style lang="less" scoped>
 @import "./Index.less";
 .footer-info-item {
-    color: white;
+    color: #b8b8b8;
     margin-right: 40px;
-    font-size: 15px;
+    font-size: 12px;
+}
+.login-title {
+    text-align: left;
+    color: #e0e0e0;
+    font-size: 19px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    margin-left: 20px;
+    letter-spacing: 1px;
+    display: inline-block;
 }
 </style>
-
 <style lang="less">
 .demo-spin-icon-load {
     animation: ani-demo-spin 1s linear infinite;
@@ -60,7 +70,10 @@
 <template>
     <div id="login" class="login" :style="{backgroundImage:bgImg}">
         <div class="login-mark">
-            <img width="125" src="@/assets/login/1-2.png">
+            <img height="42" src="@/assets/login/ies5_logo_2.svg">
+            <span class="login-title">
+                {{$t('system.title')}}
+            </span>
         </div>
         <div calss="login-schoolName">
             {{ schoolName }}
@@ -70,7 +83,6 @@
             <Dropdown trigger="click" @on-click="chgSerType">
                 <a href="javascript:void(0)">
                     <Button style="width:120px;">
-
                         <span>
                             {{ $t('login.serAdress.' + config.srvAdr) }}
                         </span>
@@ -90,14 +102,14 @@
         </div>
 
         <div class="login-footer">
-            <a class="footer-info-item" href="https://beian.miit.gov.cn/" target="_blank">蜀ICP备18027363号-1</a>
+            <a class="footer-info-item" href="https://beian.miit.gov.cn/" target="_blank">{{$t('login.beian1')}}</a>
             <a class="footer-info-item" v-if="srvAdr == 'China'" target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=51010402000615">
-                川公网安备 51010402000615
+                {{$t('login.beian2')}}
             </a>
-            <span class="footer-info-item">&copy; 2021 HABOOK Group 网奕中国 版权所有</span>
+            <span class="footer-info-item">&copy; {{$t('login.copyright')}}</span>
             <span class="footer-info-item">
                 <Icon custom="iconfont icon-earth" size="20" />
-                数据中心位置 {{srvAdr == "China" ? "中国" : "全球"}}
+                {{$t('login.title.ser')}} {{srvAdr == "China" ? $t('login.china') : $t('login.global')}}
             </span>
         </div>
     </div>
@@ -110,7 +122,7 @@ export default {
     data() {
         return {
             schoolName: '',
-            bgImg:''
+            bgImg: ''
         }
     },
     computed: {
@@ -154,23 +166,23 @@ export default {
         this.setLoginSchoolCode()
         // this.bgImg = `url(${require('@/assets/login/1-1.jpg')})`             
     },
-    mounted(){
+    mounted() {
 
     },
     watch: {
         $route: {
             handler(n, o) {
                 if (n.name == 'login') {
-                    this.bgImg = `url(${require('@/assets/login/1-1.jpg')})` 
+                    this.bgImg = `url(${require('@/assets/login/login_bg.jpg')})`
                 } else if (n.name == 'loginTeacher') {
-                    this.bgImg = `url(${require('@/assets/login/3-1.jpg')})` 
+                    this.bgImg = `url(${require('@/assets/login/3-1.jpg')})`
                 } else {
-                    this.bgImg = `url(${require('@/assets/login/2-2.jpg')})` 
+                    this.bgImg = `url(${require('@/assets/login/2-2.jpg')})`
                 }
                 console.log(n)
             },
             deep: true,
-            immediate:true
+            immediate: true
         }
     }
 }

File diff suppressed because it is too large
+ 751 - 722
TEAMModelOS/ClientApp/src/components/selflearn/NewChooseContent.vue


+ 153 - 63
TEAMModelOS/ClientApp/src/components/syllabus/DragTree.vue

@@ -43,10 +43,10 @@
 		
 		<!-- 新增或者编辑弹窗 -->
 		<Modal v-model="chapterCopyModal" width="500" footer-hide class="tree-modal add-volume-modal choose-content-modal">
-			<div class="modal-header" slot="header">复制节点</div>
+			<div class="modal-header" slot="header">{{ $t('syllabus.tree.copyNode') }}</div>
 			<div class="modal-content">
-				<p class="node-title">当前章节: <span class="node-name">{{ curChapter.title }}</span></p>
-				<p class="node-title" style="display: inline-block;">目标册别:</p>
+				<p class="node-title">{{ $t('syllabus.tree.curChapter') }}: <span class="node-name">{{ curChapter.title }}</span></p>
+				<p class="node-title" style="display: inline-block;">{{ $t('syllabus.tree.targetV') }}:</p>
 				<Select v-model="copyTargetVolume" style="width: 300px;">
 					<Option v-for="(item,index) in volumeList" :value="index" :key="index">{{item.name}}</Option>
 				</Select>
@@ -60,16 +60,8 @@
 <script>
 	import '@/utils/Math.uuid'
 	import BlobTool from '@/utils/blobTool.js'
-	import BaseResource from '@/view/syllabus/newSyllabus/operation/BaseResource'
-	import BaseKnowledge from '@/view/syllabus/newSyllabus/operation/BaseKnowledge'
-	import BaseQuestionList from '@/common/BaseQuestionList'
 	export default {
 		props: ['volume', 'treeData', 'editable'],
-		components: {
-			BaseResource,
-			BaseKnowledge,
-			BaseQuestionList
-		},
 		data() {
 			return {
 				isFalse: false,
@@ -179,16 +171,16 @@
 			onIgnoreShare(data){
 				this.$Modal.confirm({
 					title: this.$t('syllabus.tree.removeTitle'),
-					content: '确认忽略该章节吗?',
+					content: this.$t('syllabus.tree.ignoreTip'),
 					onOk: () => {
 						this.$api.syllabus.ShareAgree({
 							"code": this.$store.state.userInfo.TEAMModelId,
-							"id": data.id,
+							"ids": [data.id],
 							"type": "share",
 							"opt": "ignore"
 						}).then(res => {
 							if(res.status === 200){
-								this.$Message.success('操作成功!')
+								this.$Message.success(this.$t('syllabus.doSuc'))
 								this.$parent.getShareVolumeList(true)
 							}
 						})
@@ -201,7 +193,7 @@
 					this.curChapter = data
 					this.chapterCopyModal = true
 				}else{
-					this.$Message.warning('请先创建您的个人课纲!')
+					this.$Message.warning(this.$t('syllabus.tree.noVTip'))
 				}
 			},
 			/* 复制章节的业务逻辑 */
@@ -216,10 +208,10 @@
 				// 在复制章节过程中 如果章节节点以及子节点有关联试题试卷 则需要询问用户是否进行入库操作
 				if(hasItemOrPaper){
 					this.$Modal.confirm({
-						title: '提示',
-						content: '该章节中有关联试题试卷信息,是否需要同步到您的个人试题试卷库?',
-						okText: '同步并复制',
-						cancelText: '不需要',
+						title: this.$t('syllabus.tip'),
+						content: this.$t('syllabus.tree.hasItemTip'),
+						okText: this.$t('syllabus.tree.okText'),
+						cancelText: this.$t('syllabus.tree.cancelText'),
 						onOk: async () => {
 							let copyResult = await this.doCopyResources(allRNodes,true)
 							console.log('复制后的回调',copyResult)
@@ -241,16 +233,17 @@
 				console.log(JSON.stringify(this.flatRNodes))
 				let upsertParams = [{
 					id: this.curChapter.id,
+					codeval: this.curCode,
 					volumeId: targetVolume.id,
 					scope: targetVolume.scope,
-					trees: [this.refreshCopyChapter(this.curChapter)]
+					trees: [this.refreshCopyChapter(this.curChapter,targetVolume.id)]
 				}]
 				this.$api.syllabus.UpsertTree(upsertParams).then((res) => {
 					if (!res.error && res) {
-						this.$Message.success("复制成功");
+						this.$Message.success(this.$t('syllabus.tree.copySuc'));
 						this.chapterCopyModal = false
 					} else {
-						this.$Message.error("获取数据失败");
+						this.$Message.error(this.$t('syllabus.saveFailTip'));
 					}
 				}).catch(e => {
 					this.$Message.error(e);
@@ -259,9 +252,10 @@
 				})
 			},
 			/* 将复制的节点内的创建信息、code等更新为当前用户 */
-			refreshCopyChapter(chapterNode){
+			refreshCopyChapter(chapterNode,newVolumeId){
 				console.log(this.flatRNodes);
 				let myId = this.$store.state.userInfo.TEAMModelId
+				chapterNode.pid = newVolumeId
 				const fn = (source)=>{
 					source.creatorId = myId
 					if(source.rnodes.length){
@@ -292,14 +286,15 @@
 						if(copyFile.type === 'item'){
 							promiseArr.push(this.copyItemToBlob(copyFile,needSave))
 							// 如果不入库 则需要修改资源的link地址
-							if(!needSave){
-								this.flatRNodes[fileIndex].link = '/syllabus/' + rnodes[fileIndex].id + '/' + rnodes[fileIndex].id + '.json'
-							}
+							this.flatRNodes[fileIndex].link = (needSave ? '/item/' : '/syllabus/') + rnodes[fileIndex].id + '/' + rnodes[fileIndex].id + '.json'
+							
 						}else if(copyFile.type === 'paper'){
 							promiseArr.push(this.copyPaperToBlob(copyFile,needSave))
 							// 如果不入库 则需要修改资源的link地址
 							if(!needSave){
-								this.flatRNodes[fileIndex].link = rnodes[fileIndex].link.replace('paper','syllabus')
+								this.flatRNodes[fileIndex].link = rnodes[fileIndex].link.replace('paper/','syllabus/')
+							}else{
+								this.flatRNodes[fileIndex].link = rnodes[fileIndex].link.replace('syllabus/','paper/')
 							}
 						}else{
 							promiseArr.push(this.copyFileToBlob(copyFile))
@@ -330,11 +325,12 @@
 			},
 			/* 复制试题到当前用户BLOB */
 			copyItemToBlob(resourceItem,needSave){
+				console.log(resourceItem)
 				// console.log(JSON.stringify(rnodes))
 				return new Promise(async (r, j) => {
 					let toPath = needSave ? `item/${resourceItem.id}/` : `syllabus/${resourceItem.id}/`
 					// 是否入库 来决定保存的路径
-					let fromPath = `item/${resourceItem.id}`
+					let fromPath = resourceItem.link.split('/')[1] + '/' + resourceItem.id
 					// 试题的拥有者也就是复制的来源容器
 					let myId = this.$store.state.userInfo.TEAMModelId
 					let fileOwner = resourceItem.code.replace('Item-','')
@@ -470,49 +466,135 @@
 			// 删除节点操作
 			remove(node, data) {
 				let isFirstLevel = this.isFirstLevel(data)
+				let rNodeLinks = isFirstLevel ? this.getAllRNodes(data).map(i => i.link) : data.rnodes.map(i => i.link)
+				console.log(rNodeLinks)
 				this.$Modal.confirm({
 					title: this.$t('syllabus.tree.removeTitle'),
 					content: this.$t('syllabus.tree.removeConfirm'),
-					onOk: () => {
+					onOk: async () => {
 						const parent = node.parent
 						const children = parent.data.children || parent.data
 						const index = children.findIndex(d => d.id === data.id)
-						// 如果是删除的第一层的节点 则直接访问API进行删除 如果不是 则记录子节点的PID
-						if(isFirstLevel){
-							this.$api.syllabus.DeleteTree({
-								id:data.id,
-								code:this.volume.id,
-								scope:this.volume.scope
-							}).then(res => {
-								if (!res.error) {
-									if(res.code === 404){
-										this.$parent.modifyIdArr  = this.$parent.modifyIdArr.filter(i => i !== data.id)
-									}
-									children.splice(index, 1)
-									this.$nextTick().then(() => {
-										const firstNode = document.querySelector('.el-tree-node')
-										firstNode.click();
-									})
-									this.$Message.success(this.$t('syllabus.tree.removeSucTip'))
-								} else {
-									this.$Message.warning(res.error);
-								}
-							}).catch(err => {
-								this.$Message.error(err);
-							})
-						}else{
-							children.splice(index, 1)
-							this.$Message.success(this.$t('syllabus.tree.removeSucTip'))
-							this.$parent.hasModify = true
-							this.$emit('addModifyId',this.getChapterIdById(data.id))
+						this.$parent.curNode = {
+							id:'',
+							rnodes:[]
 						}
+						this.doDeleteBlobResource(rNodeLinks,data).then(result => {
+							// 如果是删除的第一层的节点 则直接访问API进行删除 如果不是 则记录子节点的PID
+							if(isFirstLevel){
+								this.$api.syllabus.DeleteTree({
+									id:data.id,
+									code:this.curCode,
+									scope:this.volume.scope
+								}).then(res => {
+									if (!res.error) {
+										if(res.code === 404){
+											this.$parent.modifyIdArr  = this.$parent.modifyIdArr.filter(i => i !== data.id)
+										}
+										this.$parent.treeOrigin = this.$parent.treeOrigin.filter(i => i.id !== data.id)
+										children.splice(index, 1)
+										this.$nextTick().then(() => {
+											const firstNode = document.querySelector('.el-tree-node')
+											firstNode && firstNode.click();
+										})
+										this.$Message.success(this.$t('syllabus.tree.removeSucTip'))
+									} else {
+										this.$Message.warning(res.error);
+									}
+								}).catch(err => {
+									this.$Message.error(err);
+								})
+							}else{
+								children.splice(index, 1)
+								this.$Message.success(this.$t('syllabus.tree.removeSucTip'))
+								this.$parent.hasModify = true
+								this.$emit('addModifyId',this.getChapterIdById(data.id))
+								this.$nextTick().then(() => {
+									const firstNode = document.querySelector('.el-tree-node')
+									firstNode && firstNode.click();
+								})
+							}
+						})
 					}
 				})
 			},
+			/* 删除对应关联的资源 */
+			doDeleteBlobResource(links,data){
+				return new Promise((r,j) => {
+					if(!links.length) r(200)
+					let syllabusLinks = links.filter(i => i.includes('/syllabus/'))
+					if(syllabusLinks.length){
+						this.$api.syllabus.CheckLink({
+							"links": syllabusLinks,
+							"code": this.curCode,
+							"scope": this.isSchool ? 'school' : 'private'
+						}).then(res => {
+							console.log(res)
+							let needDeleteLink = []
+							let curVolumeId = this.volume.id
+							let curChapterId = this.getChapterIdById(data.id)
+							let curNodeId = data.id
+							// 如果删除的是章节 则判断是否有其他章节引用了资源 如果没有 则要进行blob删除
+							if(curChapterId === curNodeId){
+								syllabusLinks.forEach(i => {
+									if(!res.links.filter(j => j.link === i && j.syllabusId !== curChapterId).length){
+										needDeleteLink.push(i)
+									}
+								})
+							}else{
+								syllabusLinks.forEach(i => {
+									if(!res.links.filter(j => j.link === i && j.syllabusId !== curChapterId && j.treeid !== curNodeId).length){
+										needDeleteLink.push(i)
+									}
+								})
+							}
+							if(needDeleteLink.length){
+								let promiseArr = []
+								needDeleteLink.forEach(i => {
+									promiseArr.push(this.deleteBlobPrefix(i))
+								})
+								Promise.all(promiseArr).then(result => {
+									r(result)
+								}).catch(err => {
+									j(err)
+								})
+							}else{
+								r(200)
+							}
+						}).catch(e => {
+							j(e)
+						})
+					}else{
+						r(200)
+					}
+				})
+			},
+			
+			/* 删除blob指定试题目录下所有 */
+			deleteBlobPrefix(link) {
+				return new Promise((resolve, reject) => {
+					this.$api.blob.deletePrefix({
+						"cntr": this.curCode,
+						"prefix": link.substring(1)
+					}).then(
+						(res) => {
+							if (!res.error) {
+								resolve(200)
+							} else {
+								resolve(500)
+							}
+						},
+						(err) => {
+							reject(err)
+						}
+					)
+				})
+			},
+			
 			// 点击添加展开弹窗
 			onAddNode(node,data, e) {
 				if(node.level === 3){
-					this.$Message.warning('每个章节最多只能有3级!')
+					this.$Message.warning(this.$t('syllabus.tree.nodeCountTip'))
 					return
 				}
 				this.isEditItem = false
@@ -575,6 +657,7 @@
 			/* 获取整个树的章节与子节点归属 */
 			getAllChild(arr){
 				let result = []
+				if(!arr.length) return result
 				arr.forEach(item => {
 					result.push({
 						chapterId:item.id,
@@ -622,7 +705,7 @@
 					// 判断是否为一级节点,如果是二级节点则需要拿对应的一级节点去做判断
 					let chapterId = nodeData.pid === this.volume.id ? nodeData.id : this.getChapterIdById(nodeData.id)
 					let chapterNode = this.treeDatas.find(i => i.id === chapterId)
-					return this.volume.creatorId === userId || (chapterNode.auth && chapterNode.auth.length && chapterNode.auth.map(i => i.tmdid).includes(userId))
+					return this.volume.creatorId === userId || (chapterNode && chapterNode.auth && chapterNode.auth.length && chapterNode.auth.map(i => i.tmdid).includes(userId))
 				}
 			},
 			// 判断当前用户是否可以删除当前章节
@@ -631,11 +714,13 @@
 					return nodeData.creatorId === this.$store.state.userInfo.TEAMModelId
 				}
 			},
+			curCode() {
+				return this.isSchool ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId
+			},
 			inShareView(){
 				return !this.isSchool && this.$parent.activeTab === 'fromShare'
 			},
 			volumeList(){
-				console.log(this.$parent)
 				return this.$parent.myVolumeList
 			}
 		},
@@ -643,15 +728,20 @@
 			// 监听课纲数据变化
 			treeData: {
 				handler: function(n, o) {
+					console.log(n);
 					// 以下为拼接树形数据以及册别数据
 					this.treeDatas = n.map(i => {
-						i.trees[0].auth = i.auth
-						return i.trees[0]
+						if(i.trees.length){
+							i.trees[0].auth = i.auth
+							return i.trees[0]
+						}else{
+							return {}
+						}
 					})
 					this.getAllChild(this.treeDatas)
 					this.$nextTick().then(() => {
 						const firstNode = document.querySelector('.el-tree-node')
-						firstNode.click();
+						firstNode && firstNode.click();
 					})
 				},
 				immediate: true,

+ 17 - 17
TEAMModelOS/ClientApp/src/components/syllabus/InviteTeacher.vue

@@ -1,19 +1,19 @@
 <template>
 	<div class="it-container">
 		<div class="node-info">
-			<span>当前选择章节:</span>
+			<span>{{ $t('syllabus.curChapter') }}:</span>
 			<span class="node-name">{{ nodeInfo.title }}</span>
 			<br>
 			<br>
 			<div style="display: inline-block;" v-if="!isSchool">
-				<span>当前章节已分享教师:</span>
+				<span>{{ $t('syllabus.sharedTeacher') }}:</span>
 				<!-- <span v-for="(item,index) in nodeInfo.auth" :key="item.tmdid" class="share-item">{{ item.tmdname }}( {{ item.tmdid }})</span> -->
-				<span v-if="!nodeInfo.auth || !nodeInfo.auth.length" style="color: #dacaca;">暂无数据</span>
+				<span v-if="!nodeInfo.auth || !nodeInfo.auth.length" style="color: #dacaca;">{{ $t('syllabus.noData') }}</span>
 				<Tag closable color="success" v-for="(item,index) in nodeInfo.auth" :key="item.tmdid" @on-close="doDelShare(item,index)">{{ item.tmdname }}( {{ item.tmdid }})</Tag>
 			</div>
 		</div>
 		<div class="search-wrap" v-if="isSchool">
-			<Input v-model="searchVal"  placeholder="输入教师名字或ID查询..."
+			<Input v-model="searchVal"  :placeholder="$t('syllabus.itPlace1')"
 			icon="ios-close-circle-outline" @on-click="onCloseSearch" @on-change="onSearchChange"/>
 		</div>
 		<div class="teacher-wrap">
@@ -32,17 +32,17 @@
 			</Table>
 			<div class="search-id-wrap" v-else>
 				<div class="id-search">
-					<Input v-model="searchIdVal" placeholder="搜索教师..." search @on-search="onIdSearch"/>
+					<Input v-model="searchIdVal" :placeholder="$t('syllabus.itPlace2')" search @on-search="onIdSearch"/>
 					<p v-html="$t('teachermgmt.addTeacher.content')"></p>
-					<p v-if="curTeacher && curTeacher.id" class="search-result-text">搜索结果</p>
+					<p v-if="curTeacher && curTeacher.id" class="search-result-text">{{ $t('syllabus.searchResult') }}</p>
 					<div v-if="curTeacher && curTeacher.id" class="search-result-wrap">
 						<PersonalPhoto :name="curTeacher.name" :picture="curTeacher.picture" />
 						<p class="t-name">{{ curTeacher.name }}</p>
 						<p class="t-id">{{ curTeacher.id }}</p>
 					</div>
-					<p v-if="!curTeacher && hasSearchResult" class="search-none">暂未查询到相关结果</p>
-					<p v-if="hasSearchResult" class="re-search" @click="onReSearch">重新搜索</p>
-					<Button type="success" :laading="isShareLoading" v-if="hasSearchResult"  @click="doShare">确认分享</Button>
+					<p v-if="!curTeacher && hasSearchResult" class="search-none">{{ $t('syllabus.noSearchResult') }}</p>
+					<p v-if="hasSearchResult" class="re-search" @click="onReSearch">{{ $t('syllabus.reSearch') }}</p>
+					<Button type="success" :laading="isShareLoading" v-if="hasSearchResult"  @click="doShare">{{ $t('syllabus.confirmShare') }}</Button>
 				</div>
 			</div>
 		</div>
@@ -102,7 +102,7 @@
 					  }
 					},
 					{
-						title: '是否共编',
+						title: this.$t('syllabus.isCoEdit'),
 						key:'action',
 						slot: 'action'
 					}
@@ -166,8 +166,8 @@
 			handleBeforeChange(){
 				return new Promise((resolve) => {
 					this.$Modal.confirm({
-						title: '修改确认',
-						content: '确认修改当前用户的共编状态?',
+						title: this.$t('syllabus.modifyTip'),
+						content: this.$t('syllabus.modifyText'),
 						onOk: () => {
 							resolve();
 						}
@@ -177,7 +177,7 @@
 			/* 修改共编权限 */
 			onSwitchChange(val,isAdd){
 				this.sendShareApi(val.id,val.name,false,isAdd).then(res => {
-					this.$Message.success(isAdd ? '授权成功!' : '取消授权成功!')
+					this.$Message.success(isAdd ? this.$t('syllabus.authSuc') : this.$t('syllabus.authFail'))
 					if(isAdd){
 						this.nodeInfo.auth.push({
 							tmdid:val.id,
@@ -196,7 +196,7 @@
 				this.isShareLoading = true
 				this.sendShareApi(val.id,val.name,true,true).then(res => {
 					setTimeout(() => {
-						this.$Message.success('分享成功!')
+						this.$Message.success(this.$t('syllabus.shareSuc'))
 						if(!this.nodeInfo.auth.map(i => i.tmdid).includes(val.id)){
 							this.nodeInfo.auth.push({
 								tmdid:val.id,
@@ -215,12 +215,12 @@
 			/* 取消分享操作 */
 			doDelShare(val,index){
 				this.$Modal.confirm({
-					title: '取消分享',
-					content: '确认取消对' + val.tmdname + '的分享?',
+					title: this.$t('syllabus.cancelShare'),
+					content:this.$t('syllabus.cancelTip1') + val.tmdname + this.$t('syllabus.cancelTip2'),
 					onOk: () => {
 						this.sendShareApi(val.tmdid,val.tmdname,true,false).then(res => {
 							this.nodeInfo.auth.splice(index,1)
-							this.$Message.success('取消分享成功!')
+							this.$Message.success(this.$t('syllabus.shareFail'))
 							this.$forceUpdate()
 						})
 					}

+ 5 - 1
TEAMModelOS/ClientApp/src/locale/lang/en-US/cusMgt.js

@@ -97,6 +97,7 @@ export default {
     private:'个人',
     school:'校本',
     noRecord:'暂无课堂记录',
+    groupTips:'溫馨提示:當前分組信息為行政班分組,任課教師可前往HiTeach端自定義分組。 ',
 
     //ManageClass.vue
     classLabel:'班级:',
@@ -248,6 +249,9 @@ export default {
         errorContent:'课程名单获取失败,请重新扫码加入!',
         joinOk:'加入成功',
         joinErr:'加入失敗',
-        getListErr:'獲取名單信息失敗'
+        getListErr:'獲取名單信息失敗',
+        hasJoin:'恭喜您,已經加入成功! ',
+        getErr:'用戶信息獲取失敗',
+        parseErr:'登錄信息解析失敗'
     }
 }

+ 6 - 1
TEAMModelOS/ClientApp/src/locale/lang/en-US/index.js

@@ -26,6 +26,8 @@ import cusMgt from './cusMgt'
 import home from './home'
 import updModal from './updModal'
 import task from './task'
+import utils from './utils'
+import user from './user'
 
 export default {
   schoolBaseInfo,
@@ -57,6 +59,8 @@ export default {
   learnActivity,
   updModal,
   task,
+  utils,
+  user,
   formConfigP: {
     input: 'Please Enter ',
     select: 'Please Sele',
@@ -88,5 +92,6 @@ export default {
     term: 'Choose to focus on the school year',
     term: 'Please select your academic year',
     compare: 'Choose data comparison'
-  }
+  },
+  headTitle: '醍摩豆雲平台 TEAM Model Cloud'
 }

+ 44 - 12
TEAMModelOS/ClientApp/src/locale/lang/en-US/login.js

@@ -3,15 +3,18 @@ export default {
         ser: '數據中心位置',
         IDLogin: '醍摩豆帳號登入',
         QRLogin: 'QRCode 掃瑪登入',
-        schoolLogin: '校內帳號登入'
+        schoolLogin: '校內帳號登入',
+        ies5: '醍摩豆雲平台'
     },
     subTitle: {
-        IDLogin: '系統管理者、教師、學生與家長登入口',
-        QRLogin: '使用HiTA或AClassONE掃描進行登入',
-        schoolLogin: '由學校同一分配給學生使用的帳號登入口'
+        IDLogin: '登入IES智慧教學服務,即刻存取雲端服務',
+        QRLogin: '使用HiTA5掃描進行登入',
+        schoolLogin: '由學校统一分配給學生使用的帳號登入口',
+        selectType: '請選擇您的身份進行登入',
+        studentIDLogin: '由學生自主申請的醍摩豆帳號登入口'
     },
     serAdress: {
-        China: '中國',
+        China: '大陸',
         Global: '全球'
     },
     tooltip: {
@@ -19,7 +22,7 @@ export default {
         text2: '使用學校提供的學生專屬帳號與密碼登入本校,體驗本校提供的活動、繳交作業或參與考試等任務。'
     },
     placeholder: {
-        id : '醍摩豆ID / 手機號碼 / E-Mail',
+        id : 'Email / 手機號碼 / 用戶編號',
         psw: '密碼',
         schoolMenu: '選擇學校',
         schoolID : '帳號',
@@ -28,11 +31,13 @@ export default {
     link: {
         QRLogin: 'QRCode登入',
         IDLogin: '帳號登入',
-        regist: '註冊帳號',
-        forgetPsw: '忘記密碼'
+        regist: '免費註冊',
+        forgetPsw: '忘記密碼',
+        if: '還沒有帳號嗎?',
+        click: '點此'
     },
     communy:{
-        title: '或使用第三方平台登入',
+        title: '或下列方式登入',
         fb: 'Facebook',
         google: 'Google',
         wechat: 'WeChat',
@@ -42,13 +47,40 @@ export default {
     },
     sse:{
         error:{
-            text1: '快速登入code 效'
+            text1: '快速登入code 效'
         },
-        text1: 'IES5智慧服平台'
+        text1: 'IES5智慧服平台'
     },
     modal: {
         title: '选择身份',
         btn1: '老师',
         btn2: '学生'
-    }
+    },
+    china: '中國',
+    global: '全球',
+    chooseTips: '請選擇您的身份進行登入',
+    welcome1: '歡迎來到醍摩豆 5',
+    welcome2: '新時代智慧教育之旅 就此展開',
+    subtitle1: 'HiTeach 5 智慧教學系統',
+    teachText1: '實踐差異化教學,現代化因材施教',
+    teachText2: '線上線下,混和式教學系統首選',
+    teachText3: '合作學習,素養導向教學的全面應用',
+    teachText4: '看見每個學生思考',
+    subtitle2: '醍摩豆雲平台 IES 5',
+    iesText1: '新架構與新技術,速度升級更有感',
+    iesText2: '兼容各式媒體與檔案,雲端資源庫隨取即用',
+    iesText3: '題庫組卷、閱卷系統,評量應用更多元',
+    iesText4: '博拉圖學情分析,學生成績精準提升',
+    toOfficial: '了解更多關於醍摩豆 5',
+    teacherIden:'教師身份',
+    teacherText:'以教師身份登入IES 5智慧教學服務,即刻存取雲端服務',
+    studentIden:'學生身份',
+    studText:'以學生身份登入AClass ONE智慧學伴服務,完成學習任務與測驗',
+    noAccout:'還沒有賬號嗎? ',
+    freeReg:'免費註冊',
+    teaCli:'教師端',
+    stuCli:'學生端',
+    beian1:'',
+    beian2:'',
+    copyright:'2021 HABOOK Group TeamModel'
 }

+ 4 - 2
TEAMModelOS/ClientApp/src/locale/lang/en-US/settings.js

@@ -56,15 +56,17 @@ export default {
 	openStatus: '状态',
 	enable: '启用',
 	disable: '禁用',
-	webhook: "webhook",
+	webhook: "通知接口配置",
+	authorization: "接口密钥",
 	domainName: "域名",
-	subNews: "订阅通知",
+	subNews: "订阅通知接口",
 	apiType1: "数据写入接口",
 	apiType2: "数据读取接口",
 	apiName: '接口名称',
 	apiAddress: '接口地址',
 	apiMethod: '请求方法',
 	apiParams: '参数示例',
+	newName: "通知名称",
 	openKeep: '保存平台',
 	edit: "编辑应用",
 	unedit: '取消编辑',

+ 29 - 27
TEAMModelOS/ClientApp/src/locale/lang/en-US/teachContent.js

@@ -1,6 +1,6 @@
 export default {
-  recent:'最近',
-  recentTips:'临时缓存最近上传的资源,最多保存20条。退出账号或更换电脑缓存记录将会被清空。',
+  recent: '最近',
+  recentTips: '临时缓存最近上传的资源,最多保存20条。退出账号或更换电脑缓存记录将会被清空。',
   filterRes: '教材',
   filterPicture: '图片',
   filterVideo: '视频',
@@ -33,29 +33,31 @@ export default {
   props7: '存储空间不足!',
   uploadText: '点击或者拖拽上传',
 
-  resTips:'HiTeach生成的课件,只支持HTEX格式的教材预览',
-  space:'空间:',
-  calcing:'计算中...',
-  blobFull:'(已满)',
-  otherType:'其他类型',
-  appData:'应用数据',
-  delBatch:'批量删除',
-  viewport1:'列表模式',
-  viewport2:'图片模式',
-  bottomTips:'--------我也是有底线的--------',
-  notAudio:'您的浏览器不支持 audio 元素。',
-  nameOk:'名称修改成功',
-  nameErr:'名称修改失败',
-  fullTips:'存储空间已满,无法上传',
-  loadAll:'已经加载全部',
-  authErr:'获取Blob授权失败',
-  sizeErr:'空间计算异常',
-  delBatchTips1:'请在列表模式使用批量删除!',
-  delBatchTips2:'请先选择需要删除的文件!',
-  specialChart:'不能包含特殊字符',
-  noData:'暂无数据',
-  applyPd:'适用学段:',
-  applySub:'适用学科',
-   applyGrade:'适用年级',
-  public:'公共资源'
+  resTips: 'HiTeach生成的课件,只支持HTEX格式的教材预览',
+  space: '空间:',
+  calcing: '计算中...',
+  blobFull: '(已满)',
+  otherType: '其他类型',
+  appData: '应用数据',
+  delBatch: '批量删除',
+  viewport1: '列表模式',
+  viewport2: '图片模式',
+  bottomTips: '--------我也是有底线的--------',
+  notAudio: '您的浏览器不支持 audio 元素。',
+  nameOk: '名称修改成功',
+  nameErr: '名称修改失败',
+  fullTips: '存储空间已满,无法上传',
+  loadAll: '已经加载全部',
+  authErr: '获取Blob授权失败',
+  sizeErr: '空间计算异常',
+  delBatchTips1: '请在列表模式使用批量删除!',
+  delBatchTips2: '请先选择需要删除的文件!',
+  specialChart: '不能包含特殊字符',
+  noData: '暂无数据',
+  applyPd: '适用学段:',
+  applySub: '适用学科',
+  applyGrade: '适用年级',
+  public: '公共资源',
+  startDown: '開始下載',
+  videoTips:'溫馨提示:視頻只支持MP4格式在線播放! '
 }

+ 28 - 0
TEAMModelOS/ClientApp/src/locale/lang/en-US/user.js

@@ -0,0 +1,28 @@
+export default{
+    updAvatar:'上传头像',
+    name:'姓名:',
+    noSet:'未设置',
+    edit:'编辑',
+    setting:'设置',
+    psw:'密码:',
+    mobile:'手机号码:',
+    email:'电子邮箱:',
+    pswErr1:'两次密码输入不一样',
+    oldErr:'请输入原始密码',
+    pswErr2:'请输入新密码',
+    pswErr3:'请确认新密码',
+    mobileErr:'请输入手机号码',
+    codeWarning:'请输入验证码',
+    areaErr:'请选择区号',
+    emailErr:'请输入电子邮箱',
+    msgOk:'验证码发送成功',
+    noMobile:'手机号码不存在',
+    hasReg:'此手机号已注册',
+    msgErr:'短信发送失败',
+    emailOk:'邮件发送成功',
+    emailHasReg:'此邮箱已被注册',
+    emailErr:'邮件发送失败',
+    checkTips:'请检查信息是否正确',
+    updOk:'更新成功',
+    codeErr:'验证码错误'
+}

+ 39 - 0
TEAMModelOS/ClientApp/src/locale/lang/en-US/utils.js

@@ -0,0 +1,39 @@
+export default {
+    uploadVideoLimit:'最多只能上传一个视频!',
+	videoFormatError:'视频格式不正确!请重新选择!',
+	audioFormatError:'音频格式不正确!请重新选择!',
+	fileReadFail:'有试题数据读取失败!',
+	uploadLoading:'上传中...',
+	draw:'绘制',
+	text:'文本输入',
+	clear:'清屏',
+	undo:'撤销',
+	close:'关闭',
+	save:'保存',
+	width:'画笔粗细',
+	color:'画笔颜色',
+	fontSize:'字体大小',
+	fontColor:'字体颜色',
+	newNotice:'新通知',
+	noData:'暂无数据',
+	noJoinSchool:'暂未加入学校',
+	noShoolTip:'用户暂无学校列表数据',
+	getDataFail:'获取数据失败',
+	teacher:'教师',
+	student:'学生',
+	accountManage:'账号管理',
+	courseNum:'任教课程数',
+	activityNum:'发布活动数',
+	classNum:'任教班级数',
+	full:'已满',
+	spaceStatus:'个人空间状态',
+	res:'教材',
+	img:'图片',
+	video:'视频',
+	audio:'音频',
+	doc:'文档',
+	bank:'题库',
+	other:'其他内容',
+	caclErrorL:'空间计算异常',
+	logout:'退出登录',	
+}

+ 5 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/cusMgt.js

@@ -97,6 +97,7 @@ export default {
     private:'个人',
     school:'学校',
     noRecord:'暂无课堂记录',
+    groupTips:'温馨提示:当前分组信息为行政班分组,任课教师可前往HiTeach端自定义分组。',
 
     //ManageClass.vue
     classLabel:'班级:',
@@ -248,6 +249,9 @@ export default {
         errorContent:'课程名单获取失败,请重新扫码加入!',
         joinOk:'加入成功',
         joinErr:'加入失败',
-        getListErr:'获取名单信息失败'
+        getListErr:'获取名单信息失败',
+        hasJoin:'恭喜您,已经加入成功!',
+        getErr:'用户信息获取失败',
+        parseErr:'登录信息解析失败'
     }
 }

+ 3 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/evaluation.js

@@ -1,7 +1,9 @@
 export default {
 	editor:{
 		uploadVideo:'上传本地视频',
-		uploadAudio:'上传本地音频'
+		uploadAudio:'上传本地音频',
+		insertFormula:'插入公式',
+		confirmInsert:'确认插入'
 	}, 
     index:{
 		item:'试题',

+ 4 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/index.js

@@ -33,6 +33,7 @@ import utils from './utils'
 import knowledge from './knowledge'
 import task from './task'
 import syllabus from './syllabus'
+import user from './user'
 export default {
   schoolBaseInfo,
   classMgmt,
@@ -69,6 +70,7 @@ export default {
   knowledge,
   task,
   syllabus,
+  user,
   test: '测试',
   formConfigP: {
     input: '请输入',
@@ -101,5 +103,6 @@ export default {
     term: '选择关注学年期',
     term: '请选择学年期',
     compare: '选择数据对比'
-  }
+  },
+  headTitle: '醍摩豆云平台 TEAM Model Cloud'  
 }

+ 51 - 19
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/login.js

@@ -1,47 +1,52 @@
 export default {
     title: {
         ser: '数据中心位置',
-        IDLogin: '醍摩豆账号登入',
-        QRLogin: 'QRCode 扫玛登入',
-        schoolLogin: '校内账号登入'
+        IDLogin: '醍摩豆账号登录',
+        QRLogin: '扫描登入',
+        schoolLogin: '校内帐号登入',
+        ies5: '醍摩豆云平台'
     },
     subTitle: {
         IDLogin: '系统管理者、教师、学生与家长登入口',
         QRLogin: '使用HiTA或AClassONE扫描进行登入',
-        schoolLogin: '由学校统一分配给学生使用的账号登入口'
+        schoolLogin: '由学校统一分配给学生使用的帐号登入口',
+        selectType: '请选择您的身份进行登入',
+        studentIDLogin: '由学生自主申请的醍摩豆帐号登入口'
     },
     serAdress: {
-        China: '大',
+        China: '大',
         Global: '全球'
     },
     tooltip: {
-        text1: '藉由完整的醍摩豆号体验教师的多校教学资源与课程串联,以及学生的在升学过程中产生的完整学习历程。 ',
-        text2: '使用学校提供的学生专属号与密码登入本校,体验本校提供的活动、缴交作业或参与考试等任务。 '
+        text1: '藉由完整的醍摩豆号体验教师的多校教学资源与课程串联,以及学生的在升学过程中产生的完整学习历程。 ',
+        text2: '使用学校提供的学生专属号与密码登入本校,体验本校提供的活动、缴交作业或参与考试等任务。 '
     },
     placeholder: {
-        id : '醍摩豆ID / 手机号码 / E-Mail',
+        id: '醍摩豆ID / 手机号码 / E-Mail',
         psw: '密码',
         schoolMenu: '选择学校',
-        schoolID : '账号',
+        schoolID: '账号',
         schoolPsw: '密码'
     },
     link: {
-        QRLogin: 'QRCode登入',
-        IDLogin: '账号登入',
-        regist: '注册账号',
-        forgetPsw: '忘记密码'
+        QRLogin: '扫描登入',
+        IDLogin: '帐号登入',
+        regist: '免费注册',
+        forgetPsw: '忘记密码',
+        if: '还没有帐号吗?',
+        click: '点此'
     },
     communy:{
-        title: '或使用第三方平台登入',
+        title: '或下列方式登入',
         fb: 'Facebook',
         google: 'Google',
         wechat: 'WeChat',
     },
-    apiError:{
-        text1:'您的账号或密码不正确'
+    apiError: {
+        text1: '您的账号或密码不正确'
     },
-    sse:{
-        error:{
+    sse: {
+        error: {
             text1: '快速登入code 无效'
         },
         text1: 'IES5智慧服务平台'
@@ -50,5 +55,32 @@ export default {
         title: '选择身份',
         btn1: '老师',
         btn2: '学生'
-    }
+    },
+    china: '中国',
+    global: '全球',
+    chooseTips: '请选择您的身份进行登入',
+    welcome1: '欢迎来到醍摩豆 5',
+    welcome2: '新时代智慧教育之旅 就此展开',
+    subtitle1: 'HiTeach 5 智慧教学系统',
+    teachText1: '实践差异化教学,现代化因材施教',
+    teachText2: '线上线下,混和式教学系统首选',
+    teachText3: '合作学习,素养导向教学的全面应用',
+    teachText4: '看见每个学生思考',
+    subtitle2: '醍摩豆云平台 IES 5',
+    iesText1: '新架构与新技术,速度升级更有感',
+    iesText2: '兼容各式媒体与档案,云端资源库随取即用',
+    iesText3: '题库组卷、阅卷系统,评量应用更多元',
+    iesText4: '博拉图学情分析,学生成绩精准提升',
+    toOfficial: '了解更多关于醍摩豆 5',
+    teacherIden:'教师身份',
+    teacherText:'以教师身份登入IES 5智慧教学服务,即刻存取云端服务',
+    studentIden:'学生身份',
+    studText:'以学生身份登入AClass ONE智慧学伴服务,完成学习任务与测验',
+    noAccout:'还没有账号吗?',
+    freeReg:'免费注册',
+    teaCli:'教师端',
+    stuCli:'学生端',
+    beian1:'蜀ICP备18027363号-1',
+    beian2:'川公网安备 51010402000615',
+    copyright:'2021 HABOOK Group 醍摩豆'
 }

+ 4 - 2
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/settings.js

@@ -56,15 +56,17 @@ export default {
 	openStatus: '状态',
 	enable: '启用',
 	disable: '禁用',
-	webhook: "webhook",
+	webhook: "通知接口配置",
+	authorization: "接口密钥",
 	domainName: "域名",
-	subNews: "订阅通知",
+	subNews: "订阅通知接口",
 	apiType1: "数据写入接口",
 	apiType2: "数据读取接口",
 	apiName: '接口名称',
 	apiAddress: '接口地址',
 	apiMethod: '请求方法',
 	apiParams: '参数示例',
+	newName: "通知名称",
 	openKeep: '保存平台',
 	edit: "编辑应用",
 	unedit: '取消编辑',

+ 1 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/survey.js

@@ -9,6 +9,7 @@ export default {
 	save:'保存问卷',
 	cancelEdit:'取消编辑',
 	surveyResult:'问卷数据',
+	surveyProgress:'问卷进度',
 	addItem:'新增题目',
 	single:'单选题',
 	multiple:'多选题',

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

@@ -1,8 +1,17 @@
 export default{
+	noPreview:'该类型文件不支持预览!',
 	praviteSyllabus:'个人课纲',
+	fromCreate:'我创建的课纲',
+	fromShare:'他人分享的课纲',
 	btnSave:'存储变更',
 	volumeList:'册别清单',
+	search:'搜索',
+	edit:'编辑',
+	delete:'删除',
+	add:'新建册别',
 	place1:'输入册别名称...',
+	isDel:'已失效',
+	deleteDelV:'删除失效册别',
 	syllabusMenu:'课纲目录',
 	relate:'关联资源',
 	addResource:'添加资源',
@@ -31,11 +40,17 @@ export default{
 	count:'题数',
 	deleteVolume:'删除册别',
 	deleteConfirm:'确认删除该册别?',
+	deleteIsDelConfirm:'确认删除该失效册别?',
 	deleteSuc:'删除成功',
 	deleteFail:'删除失败',
+	isDelTip:'该册别已被创建者移除',
 	uploadSuc:'上传成功',
+	tip:'提示',
+	copyTip1:'校本试卷库中已存在名称为',
+	copyTip2:'的试卷,是否继续操作覆盖原试卷?',
 	removeConfirm:'确定要移除该资源吗?',
 	doSuc:'操作成功',
+	saveFailTip:'保存失败',
 	isExistVolume:'已存在相同册别',
     tree:{
 		hasResource:'有关联资源',
@@ -58,5 +73,35 @@ export default{
 		nodeNameTip:'节点名称不能为空',
 		editSucTip:'编辑成功',
 		addSucTip:'添加成功',
-	}
+		copyNode:'复制节点',
+		curChapter:'当前章节',
+		targetV:'目标册别',
+		ignoreTip:'确认忽略该章节?',
+		noVTip:'请先创建您的个人课纲!',
+		hasItemTip:'该章节中有关联试题试卷信息,是否需要同步到您的个人试题试卷库?',
+		okText:'同步并复制',
+		cancelText:'不需要',
+		copySuc:'复制成功',
+		nodeCountTip:'每个章节最多只能有3级!'
+	},
+	curChapter:'当前选择章节',
+	sharedTeacher:'当前章节已分享教师',
+	noData:'暂无数据',
+	itPlace1:'输入教师名字或ID查询...',
+	itPlace2:'搜索教师...',
+	searchResult:'搜索结果',
+	noSearchResult:'暂未查询到相关结果',
+	reSearch:'重新搜索',
+	confirmShare:'确认分享',
+	isCoEdit:'是否共编',
+	modifyTip:'修改确认',
+	modifyText:'确认修改当前用户的共编状态?',
+	authSuc:'授权成功',
+	authFail:'取消授权成功',
+	shareSuc:'分享成功',
+	shareFail:'取消分享成功',
+	cancelShare:'取消分享',
+	cancelTip1:'确认取消对',
+	cancelTip2:'的分享?',
+	
 }

+ 3 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/teachContent.js

@@ -57,5 +57,7 @@ export default {
   applyPd:'适用学段',
   applySub:'适用学科',
   applyGrade:'适用年级:',
-  public:'公共资源'
+  public:'公共资源',
+  startDown:'开始下载',
+  videoTips:'温馨提示:视频只支持MP4格式在线播放!'
 }

+ 28 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/user.js

@@ -0,0 +1,28 @@
+export default{
+    updAvatar:'上传头像',
+    name:'姓名:',
+    noSet:'未设置',
+    edit:'编辑',
+    setting:'设置',
+    psw:'密码:',
+    mobile:'手机号码:',
+    email:'电子邮箱:',
+    pswErr1:'两次密码输入不一样',
+    oldErr:'请输入原始密码',
+    pswErr2:'请输入新密码',
+    pswErr3:'请确认新密码',
+    mobileErr:'请输入手机号码',
+    codeWarning:'请输入验证码',
+    areaErr:'请选择区号',
+    emailErr:'请输入电子邮箱',
+    msgOk:'验证码发送成功',
+    noMobile:'手机号码不存在',
+    hasReg:'此手机号已注册',
+    msgErr:'短信发送失败',
+    emailOk:'邮件发送成功',
+    emailHasReg:'此邮箱已被注册',
+    emailErr:'邮件发送失败',
+    checkTips:'请检查信息是否正确',
+    updOk:'更新成功',
+    codeErr:'验证码错误'
+}

+ 5 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/cusMgt.js

@@ -97,6 +97,7 @@ export default {
     private:'個人',
     school:'學校',     
     noRecord:'暫無課堂記錄',
+    groupTips:'溫馨提示:當前分組信息為行政班分組,任課教師可前往HiTeach端自定義分組。 ',
     
     //ManageClass.vue
     classLabel: '班級:',
@@ -248,6 +249,9 @@ export default {
         errorContent:'課程名單獲取失敗,請重新掃碼加入! ',
         joinOk:'加入成功',
         joinErr:'加入失敗',
-        getListErr:'獲取名單信息失敗'
+        getListErr:'獲取名單信息失敗',
+        hasJoin:'恭喜您,已經加入成功! ',
+        getErr:'用戶信息獲取失敗',
+        parseErr:'登錄信息解析失敗'
     }  
 }

+ 3 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/evaluation.js

@@ -1,7 +1,9 @@
 export default {
 	editor: {
 		uploadVideo: '上傳本地視頻',
-		uploadAudio: '上傳本地音訊'
+		uploadAudio: '上傳本地音訊',
+		insertFormula:'插入公式',
+		confirmInsert:'確認插入'
 	},
 	index: {
 		item: '試題',

+ 4 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/index.js

@@ -33,6 +33,7 @@ import utils from './utils'
 import knowledge from './knowledge'
 import task from './task'
 import syllabus from './syllabus'
+import user from './user'
 export default {
   
   schoolBaseInfo,
@@ -70,6 +71,7 @@ export default {
   knowledge,
   task,
   syllabus,
+  user,
   test: '測試',
   formConfigP: {
     input: '請輸入',
@@ -102,5 +104,6 @@ export default {
     term: '選擇關注學年期',
     term: '請選擇學年期',
     compare: '選擇數據對比'
-  }
+  },
+  headTitle: '醍摩豆雲平台 TEAM Model Cloud'
 }

+ 43 - 11
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/login.js

@@ -2,13 +2,16 @@ export default {
     title: {
         ser: '數據中心位置',
         IDLogin: '醍摩豆帳號登入',
-        QRLogin: 'QRCode 掃瑪登入',
-        schoolLogin: '校內帳號登入'
+        QRLogin: '掃描登入',
+        schoolLogin: '校內帳號登入',
+        ies5: '醍摩豆雲平台'
     },
     subTitle: {
-        IDLogin: '系統管理者、教師、學生與家長登入口',
+        IDLogin: '登入IES智慧教學服務,即刻存取雲端服務',
         QRLogin: '使用HiTA5掃描進行登入',
-        schoolLogin: '由學校统一分配給學生使用的帳號登入口'
+        schoolLogin: '由學校统一分配給學生使用的帳號登入口',
+        selectType: '請選擇您的身份進行登入',
+        studentIDLogin: '由學生自主申請的醍摩豆帳號登入口'
     },
     serAdress: {
         China: '大陸',
@@ -19,20 +22,22 @@ export default {
         text2: '使用學校提供的學生專屬帳號與密碼登入本校,體驗本校提供的活動、繳交作業或參與考試等任務。'
     },
     placeholder: {
-        id : '醍摩豆ID / 手機號碼 / E-Mail',
+        id : 'Email / 手機號碼 / 用戶編號',
         psw: '密碼',
         schoolMenu: '選擇學校',
         schoolID : '帳號',
         schoolPsw: '密碼'
     },
     link: {
-        QRLogin: 'QRCode登入',
+        QRLogin: '掃描登入',
         IDLogin: '帳號登入',
-        regist: '註冊帳號',
-        forgetPsw: '忘記密碼'
+        regist: '免費註冊',
+        forgetPsw: '忘記密碼',
+        if: '還沒有帳號嗎?',
+        click: '點此'
     },
     communy:{
-        title: '或使用第三方平台登入',
+        title: '或下列方式登入',
         fb: 'Facebook',
         google: 'Google',
         wechat: 'WeChat',
@@ -47,8 +52,35 @@ export default {
         text1: 'IES5智慧服務平台'
     },
     modal: {
-        title: '選擇身',
+        title: '選擇身',
         btn1: '老師',
         btn2: '學生'
-    }
+    },
+    china: '中國',
+    global: '全球',
+    chooseTips: '請選擇您的身份進行登入',
+    welcome1: '歡迎來到醍摩豆 5',
+    welcome2: '新時代智慧教育之旅 就此展開',
+    subtitle1: 'HiTeach 5 智慧教學系統',
+    teachText1: '實踐差異化教學,現代化因材施教',
+    teachText2: '線上線下,混和式教學系統首選',
+    teachText3: '合作學習,素養導向教學的全面應用',
+    teachText4: '看見每個學生思考',
+    subtitle2: '醍摩豆雲平台 IES 5',
+    iesText1: '新架構與新技術,速度升級更有感',
+    iesText2: '兼容各式媒體與檔案,雲端資源庫隨取即用',
+    iesText3: '題庫組卷、閱卷系統,評量應用更多元',
+    iesText4: '博拉圖學情分析,學生成績精準提升',
+    toOfficial: '了解更多關於醍摩豆 5',
+    teacherIden:'教師身份',
+    teacherText:'以教師身份登入IES 5智慧教學服務,即刻存取雲端服務',
+    studentIden:'學生身份',
+    studText:'以學生身份登入AClass ONE智慧學伴服務,完成學習任務與測驗',
+    noAccout:'還沒有賬號嗎? ',
+    freeReg:'免費註冊',
+    teaCli:'教師端',
+    stuCli:'學生端',
+    beian1:'',
+    beian2:'',
+    copyright:'2021 HABOOK Group 醍摩豆'
 }

+ 4 - 2
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/settings.js

@@ -56,15 +56,17 @@ export default {
     openStatus: '狀態',
     enable: '啟用',
     disable: '禁用',
-    webhook: "webhook",
+    webhook: "通知接口配寘",
+    authorization: "接口金鑰",
     domainName: "域名",
-	subNews: "訂閱通知",
+	subNews: "訂閱通知接口",
 	apiType1: "數據寫入接口",
 	apiType2: "數據讀取接口",
     apiName: '接口名稱',
     apiAddress: '接口地址',
     apiMethod: '請求方法',
     apiParams: '參數示例',
+    newName: "通知名稱",
     openKeep: '儲存平臺',
     edit: "編輯應用",
     unedit: '取消編輯',

+ 1 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/survey.js

@@ -9,6 +9,7 @@ export default {
 	save: '儲存問卷',
 	cancelEdit: '取消編輯',
 	surveyResult: '問卷數據',
+	surveyProgress:'問卷進度',
 	addItem: '新增題目',
 	single: '單選題',
 	multiple: '複選题',

+ 46 - 2
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/syllabus.js

@@ -1,8 +1,17 @@
 export default {
+	noPreview:'該類型檔案不支持預覽!',
 	praviteSyllabus: '個人課綱',
+	fromCreate: '我創建的課綱',
+	fromShare: '他人分享的課綱',
 	btnSave: '存儲變更',
 	volumeList: '册別清單',
+	search: '蒐索',
+	edit: '編輯',
+	delete: '删除',
+	add: '新建册別',
 	place1: '輸入册別名稱…',
+	isDel: '已失效',
+	deleteDelV: '删除失效册別',
 	syllabusMenu: '課綱目錄',
 	relate: '關聯資源',
 	addResource: '添加資源',
@@ -31,15 +40,21 @@ export default {
 	count: '題數',
 	deleteVolume: '删除册別',
 	deleteConfirm: '確認删除該册別?',
+	deleteIsDelConfirm: '確認删除該失效册別?',
 	deleteSuc: '删除成功',
 	deleteFail: '删除失敗',
+	isDelTip: '該册別已被創建者移除',
 	uploadSuc: '上傳成功',
+	tip: '提示',
+	copyTip1: '校本試卷庫中已存在名稱為',
+	copyTip2: '的試卷,是否繼續操作覆蓋原試卷?',
 	removeConfirm: '確定要移除該資源嗎?',
 	doSuc: '操作成功',
+	saveFailTip: '保存失敗',
 	isExistVolume: '已存在相同册別',
 	tree: {
 		hasResource: '有關聯資源',
-		hasCoEdit:'有共編許可權',
+		hasCoEdit: '有共編許可權',
 		edit: '編輯',
 		add: '添加',
 		remove: '删除',
@@ -58,5 +73,34 @@ export default {
 		nodeNameTip: '節點名稱不能為空',
 		editSucTip: '編輯成功',
 		addSucTip: '添加成功',
-	}
+		copyNode: '複製節點',
+		curChapter: '當前章節',
+		targetV: '目標册別',
+		ignoreTip: '確認忽略該章節?',
+		noVTip: '請先創建您的個人課綱!',
+		hasItemTip: '該章節中有關聯試題試卷資訊,是否需要同步到您的個人試題試卷庫?',
+		okText: '同步並複製',
+		cancelText: '不需要',
+		copySuc: '複製成功',
+		nodeCountTip: '每個章節最多只能有3級!'
+	},
+	curChapter: '當前選擇章節',
+	sharedTeacher: '當前章節已分享教師',
+	noData: '暫無數據',
+	itPlace1: '輸入教師名字或ID査詢…',
+	itPlace2: '蒐索教師…',
+	searchResult: '搜索結果',
+	noSearchResult: '暫未査詢到相關結果',
+	reSearch: '重新搜索',
+	confirmShare: '確認分享',
+	isCoEdit: '是否共編',
+	modifyTip: '修改確認',
+	modifyText: '確認修改當前用戶的共編狀態?',
+	authSuc: '授權成功',
+	authFail: '取消授權成功',
+	shareSuc: '分享成功',
+	shareFail: '取消分享成功',
+	cancelShare: '取消分享',
+	cancelTip1: '確認取消對',
+	cancelTip2: '的分享?',
 }

+ 3 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/teachContent.js

@@ -57,5 +57,7 @@ export default {
   applyPd:'適用學段:',
   applySub:'適用學科',
   applyGrade:'適用年級',
-  public:'公共資源'
+  public:'公共資源',
+  startDown:'開始下載',
+  videoTips:'溫馨提示:視頻只支持MP4格式在線播放! '
 }

+ 28 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/user.js

@@ -0,0 +1,28 @@
+export default{
+    updAvatar:'上傳頭像',
+    name:'姓名:',
+    noSet:'未設置',
+    edit:'編輯',
+    setting:'設置',
+    psw:'密碼:',
+    mobile:'手機號碼:',
+    email:'電子郵箱:',
+    pswErr1:'兩次密碼輸入不一樣',
+    oldErr:'請輸入原始密碼',
+    pswErr2:'請輸入新密碼',
+    pswErr3:'請確認新密碼',
+    mobileErr:'請輸入手機號碼',
+    codeWarning:'請輸入驗證碼',
+    areaErr:'請選擇區號',
+    emailErr:'請輸入電子郵箱',
+    msgOk:'驗證碼發送成功',
+    noMobile:'手機號碼不存在',
+    hasReg:'此手機號已註冊',
+    msgErr:'短信發送失敗',
+    emailOk:'郵件發送成功',
+    emailHasReg:'此郵箱已被註冊',
+    emailErr:'郵件發送失敗',
+    checkTips:'請檢查信息是否正確',
+    updOk:'更新成功',
+    codeErr:'驗證碼錯誤'
+}

+ 1 - 1
TEAMModelOS/ClientApp/src/router/routes.js

@@ -43,7 +43,7 @@ export const routes = [
 				meta: {
 					middleware: ['login?']
 				},
-				component: resolve => require(['@/view/login/test.vue'], resolve)
+				component: resolve => require(['@/view/login/Index.vue'], resolve)
 			},
 			{
 				name: 'loginTeacher',

+ 2 - 2
TEAMModelOS/ClientApp/src/static/Global.js

@@ -9,7 +9,7 @@ const DEFAULT_SCHOOL_CODE = 'SYSTEM_NO_SCHOOL' //尚未归属学校的默认学
 //文件类型,对应内容模块Blob目录
 const CONTENT_TYPES = {
 	'image': ['JPG', 'JPEG', 'PNG', 'GIF', 'SVG'],
-	'video': ['AVI', 'MP4', 'WEBM'],
+	'video': ['AVI', 'MP4', 'WEBM', 'MOV', 'MPEG'],
 	'doc': ['PPT', 'PPTX', 'DOC', 'DOCX', 'PDF', 'XLS', 'XLSX', 'CSV'],
 	'res': ['HTE', 'HTEX'],
 	'audio': ['MP3', 'WAV', 'OGG']
@@ -139,4 +139,4 @@ export default {
 	install
 
 }
-export {GLOBAL}
+export { GLOBAL }

+ 961 - 0
TEAMModelOS/ClientApp/src/static/countryCodeData.js

@@ -0,0 +1,961 @@
+export default {
+	'Bangladesh': {
+		'Culture': 'BD',
+		'CountryEn': 'Bangladesh',
+		'CountryCn': '孟加拉',
+		'CountryTw': '孟加拉',
+		'Code': 880
+	},
+	'Brunei': {
+		'Culture': 'ms-BN',
+		'CountryEn': 'Brunei',
+		'CountryCn': '汶莱',
+		'CountryTw': '汶萊',
+		'Code': 673
+	},
+	'Cambodia': {
+		'Culture': 'KH',
+		'CountryEn': 'Cambodia',
+		'CountryCn': '柬埔寨',
+		'CountryTw': '柬埔寨',
+		'Code': 855
+	},
+	'Cameroon': {
+		'Culture': '',
+		'CountryEn': 'Cameroon',
+		'CountryCn': '喀麦隆',
+		'CountryTw': '喀麥隆',
+		'Code': 237
+	},
+	'China': {
+		'Culture': 'zh-CN',
+		'CountryEn': 'China',
+		'CountryCn': '中国',
+		'CountryTw': '中國',
+		'Code': 86
+	},
+	'Russia': {
+		'Culture': 'ru-RU',
+		'CountryEn': 'Russia',
+		'CountryCn': '俄罗斯',
+		'CountryTw': '俄羅斯',
+		'Code': 7
+	},
+	'Hong Kong': {
+		'Culture': 'zh-HK',
+		'CountryEn': 'Hong Kong',
+		'CountryCn': '中国香港',
+		'CountryTw': '香港',
+		'Code': 852
+	},
+	'India': {
+		'Culture': '',
+		'CountryEn': 'India',
+		'CountryCn': '印度',
+		'CountryTw': '印度',
+		'Code': 91
+	},
+	'Indonesia': {
+		'Culture': 'id-ID',
+		'CountryEn': 'Indonesia',
+		'CountryCn': '印尼',
+		'CountryTw': '印尼',
+		'Code': 62
+	},
+	'Irag': {
+		'Culture': 'ar-IQ',
+		'CountryEn': 'Irag',
+		'CountryCn': '伊拉克',
+		'CountryTw': '伊拉克',
+		'Code': 964
+	},
+	'Iran': {
+		'Culture': 'fa-IR',
+		'CountryEn': 'Iran',
+		'CountryCn': '伊朗',
+		'CountryTw': '伊朗',
+		'Code': 98
+	},
+	'Israel': {
+		'Culture': 'he-IL',
+		'CountryEn': 'Israel',
+		'CountryCn': '以色列',
+		'CountryTw': '以色列',
+		'Code': 972
+	},
+	'Japan': {
+		'Culture': 'ja-JP',
+		'CountryEn': 'Japan',
+		'CountryCn': '日本',
+		'CountryTw': '日本',
+		'Code': 81
+	},
+	'SriLanka': {
+		'Culture': '',
+		'CountryEn': 'Sri Lanka',
+		'CountryCn': '斯里兰卡',
+		'CountryTw': '斯里蘭卡',
+		'Code': 94
+	},
+	'Sudan': {
+		'Culture': '',
+		'CountryEn': 'Sudan',
+		'CountryCn': '苏丹',
+		'CountryTw': '蘇丹',
+		'Code': 249
+	},
+	'UnitedArabEmirates': {
+		'Culture': '',
+		'CountryEn': 'United Arab Emirates',
+		'CountryCn': '阿拉伯联合大公国',
+		'CountryTw': '阿拉伯聯合大公國',
+		'Code': 971
+	},
+	'Zimbabwe': {
+		'Culture': '',
+		'CountryEn': 'Zimbabwe',
+		'CountryCn': '辛巴威',
+		'CountryTw': '辛巴威',
+		'Code': 263
+	},
+	'SaudiArabia': {
+		'Culture': '',
+		'CountryEn': 'Saudi Arabia',
+		'CountryCn': '沙乌地阿拉伯',
+		'CountryTw': '沙烏地阿拉伯',
+		'Code': 966
+	},
+	'Maldives': {
+		'Culture': 'div-MV',
+		'CountryEn': 'Maldives',
+		'CountryCn': '马尔地夫',
+		'CountryTw': '馬爾地夫',
+		'Code': 960
+	},
+	'Jamaica': {
+		'Culture': 'en-JM',
+		'CountryEn': 'Jamaica',
+		'CountryCn': '牙买加',
+		'CountryTw': '牙買加',
+		'Code': 1876
+	},
+	'Kuwait': {
+		'Culture': 'ar-KW',
+		'CountryEn': 'Kuwait',
+		'CountryCn': '科威特',
+		'CountryTw': '科威特',
+		'Code': 965
+	},
+	'KoreaNorth': {
+		'Culture': '',
+		'CountryEn': 'Korea North',
+		'CountryCn': '北韩',
+		'CountryTw': '北韓',
+		'Code': 850
+	},
+	'KoreaSouth': {
+		'Culture': '',
+		'CountryEn': 'Korea South',
+		'CountryCn': '南韩',
+		'CountryTw': '南韓',
+		'Code': 82
+	},
+	'Laos': {
+		'Culture': '',
+		'CountryEn': 'Laos',
+		'CountryCn': '寮国',
+		'CountryTw': '寮國',
+		'Code': 856
+	},
+	'Lebanon': {
+		'Culture': 'ar-LB',
+		'CountryEn': 'Lebanon',
+		'CountryCn': '黎巴嫩',
+		'CountryTw': '黎巴嫩',
+		'Code': 961
+	},
+	'Libya': {
+		'Culture': 'ar-LY',
+		'CountryEn': 'Libya',
+		'CountryCn': '利比亚',
+		'CountryTw': '利比亞',
+		'Code': 218
+	},
+	'Macao': {
+		'Culture': 'zh-MO',
+		'CountryEn': 'Macao',
+		'CountryCn': '中国澳门',
+		'CountryTw': '澳門',
+		'Code': 853
+	},
+	'Madagascar': {
+		'Culture': '',
+		'CountryEn': 'Madagascar',
+		'CountryCn': '马达加斯加',
+		'CountryTw': '馬達加斯加',
+		'Code': 261
+	},
+	'Malawi': {
+		'Culture': '',
+		'CountryEn': 'Malawi',
+		'CountryCn': '马拉威',
+		'CountryTw': '馬拉威',
+		'Code': 265
+	},
+	'Malaysia': {
+		'Culture': 'ms-MY',
+		'CountryEn': 'Malaysia',
+		'CountryCn': '马来西亚',
+		'CountryTw': '馬來西亞',
+		'Code': 60
+	},
+	'Mongolia': {
+		'Culture': 'mn-MN',
+		'CountryEn': 'Mongolia',
+		'CountryCn': '外蒙古',
+		'CountryTw': '外蒙古',
+		'Code': 976
+	},
+	'Myanmar': {
+		'Culture': '',
+		'CountryEn': 'Myanmar',
+		'CountryCn': '缅甸',
+		'CountryTw': '緬甸',
+		'Code': 95
+	},
+	'Nepal': {
+		'Culture': '',
+		'CountryEn': 'Nepal',
+		'CountryCn': '尼泊尔',
+		'CountryTw': '尼泊爾',
+		'Code': 977
+	},
+	'Oman': {
+		'Culture': 'ar-OM',
+		'CountryEn': 'Oman',
+		'CountryCn': '阿曼',
+		'CountryTw': '阿曼',
+		'Code': 968
+	},
+	'Pakistan': {
+		'Culture': 'ur-PK',
+		'CountryEn': 'Pakistan',
+		'CountryCn': '巴基斯坦',
+		'CountryTw': '巴基斯坦',
+		'Code': 92
+	},
+	'Philippines': {
+		'Culture': 'en-PH',
+		'CountryEn': 'Philippines',
+		'CountryCn': '菲律宾',
+		'CountryTw': '菲律賓',
+		'Code': 63
+	},
+	'Senegal': {
+		'Culture': '',
+		'CountryEn': 'Senegal',
+		'CountryCn': '赛内加尔',
+		'CountryTw': '賽內加爾',
+		'Code': 221
+	},
+	'Singapore': {
+		'Culture': 'zh-SG',
+		'CountryEn': 'Singapore',
+		'CountryCn': '新加坡',
+		'CountryTw': '新加坡',
+		'Code': 65
+	},
+	'Slovak': {
+		'Culture': 'sk-SK',
+		'CountryEn': 'Slovak',
+		'CountryCn': '斯洛伐克',
+		'CountryTw': '斯洛伐克',
+		'Code': 421
+	},
+	'Syria': {
+		'Culture': 'syr-SY',
+		'CountryEn': 'Syria',
+		'CountryCn': '叙利亚',
+		'CountryTw': '敘利亞',
+		'Code': 963
+	},
+	'Taiwan': {
+		'Culture': 'zh-TW',
+		'CountryEn': 'Taiwan',
+		'CountryCn': '中国台湾',
+		'CountryTw': '臺灣',
+		'Code': 886
+	},
+	'Tanzania': {
+		'Culture': '',
+		'CountryEn': 'Tanzania',
+		'CountryCn': '坦尚尼亚',
+		'CountryTw': '坦尚尼亞',
+		'Code': 255
+	},
+	'Thailand': {
+		'Culture': 'th-TH',
+		'CountryEn': 'Thailand',
+		'CountryCn': '泰国',
+		'CountryTw': '泰國',
+		'Code': 66
+	},
+	'Trinidad & Tobago': {
+		'Culture': 'en-TT',
+		'CountryEn': 'Trinidad & Tobago',
+		'CountryCn': '千里达及托巴哥',
+		'CountryTw': '千里達及托巴哥',
+		'Code': 1868
+	},
+	'Tunisia': {
+		'Culture': 'ar-TN',
+		'CountryEn': 'Tunisia',
+		'CountryCn': '突尼西亚',
+		'CountryTw': '突尼西亞',
+		'Code': 216
+	},
+	'Turkey': {
+		'Culture': 'tr-TR',
+		'CountryEn': 'Turkey',
+		'CountryCn': '土耳其',
+		'CountryTw': '土耳其',
+		'Code': 90
+	},
+	'Vietnam': {
+		'Culture': 'vi-VN',
+		'CountryEn': 'Vietnam',
+		'CountryCn': '越南',
+		'CountryTw': '越南',
+		'Code': 84
+	},
+	'Yemen Rep': {
+		'Culture': 'ar-YE',
+		'CountryEn': 'Yemen Rep',
+		'CountryCn': '叶门共和国',
+		'CountryTw': '葉門共和國',
+		'Code': 967
+	},
+	'America': {
+		'Culture': 'en-US',
+		'CountryEn': 'America',
+		'CountryCn': '美国',
+		'CountryTw': '美國',
+		'Code': 1
+	},
+	'Andorra': {
+		'Culture': '',
+		'CountryEn': 'Andorra',
+		'CountryCn': '安道尔',
+		'CountryTw': '安道爾',
+		'Code': 376
+	},
+	'Argentina': {
+		'Culture': 'es-AR',
+		'CountryEn': 'Argentina',
+		'CountryCn': '阿根廷',
+		'CountryTw': '阿根廷',
+		'Code': 54
+	},
+	'Bahamas': {
+		'Culture': '',
+		'CountryEn': 'Bahamas',
+		'CountryCn': '巴哈马',
+		'CountryTw': '巴哈馬',
+		'Code': 1242
+	},
+	'Bahrain': {
+		'Culture': 'ar-BH',
+		'CountryEn': 'Bahrain',
+		'CountryCn': '巴林',
+		'CountryTw': '巴林',
+		'Code': 973
+	},
+	'Belize': {
+		'Culture': 'en-BZ',
+		'CountryEn': 'Belize',
+		'CountryCn': '贝里斯',
+		'CountryTw': '貝里斯',
+		'Code': 501
+	},
+	'Brazil': {
+		'Culture': 'pt-BR',
+		'CountryEn': 'Brazil',
+		'CountryCn': '巴西',
+		'CountryTw': '巴西',
+		'Code': 55
+	},
+	'Bolivia': {
+		'Culture': 'es-BO',
+		'CountryEn': 'Bolivia',
+		'CountryCn': '玻利维亚',
+		'CountryTw': '玻利維亞',
+		'Code': 591
+	},
+	'Canada': {
+		'Culture': 'en-CA',
+		'CountryEn': 'Canada',
+		'CountryCn': '加拿大',
+		'CountryTw': '加拿大',
+		'Code': 1
+	},
+	'Casta Rica': {
+		'Culture': '',
+		'CountryEn': 'Casta Rica',
+		'CountryCn': '哥斯大黎加',
+		'CountryTw': '哥斯大黎加',
+		'Code': 506
+	},
+	'Chile': {
+		'Culture': 'es-CL',
+		'CountryEn': 'Chile',
+		'CountryCn': '智利',
+		'CountryTw': '智利',
+		'Code': 56
+	},
+	'Colombia': {
+		'Culture': 'es-CO',
+		'CountryEn': 'Colombia',
+		'CountryCn': '哥伦比亚',
+		'CountryTw': '哥倫比亞',
+		'Code': 57
+	},
+	'Cuba': {
+		'Culture': '',
+		'CountryEn': 'Cuba',
+		'CountryCn': '古巴',
+		'CountryTw': '古巴',
+		'Code': 53
+	},
+	'Cyprus': {
+		'Culture': '',
+		'CountryEn': 'Cyprus',
+		'CountryCn': '赛普勒斯',
+		'CountryTw': '賽普勒斯',
+		'Code': 357
+	},
+	'Dominican Rep.': {
+		'Culture': 'es-DO',
+		'CountryEn': 'Dominican Rep.',
+		'CountryCn': '多明尼加',
+		'CountryTw': '多明尼加',
+		'Code': 1
+	},
+	'Ecuador': {
+		'Culture': 'es-EC',
+		'CountryEn': 'Ecuador',
+		'CountryCn': '厄瓜多',
+		'CountryTw': '厄瓜多',
+		'Code': 593
+	},
+	'Guatemala': {
+		'Culture': 'es-GT',
+		'CountryEn': 'Guatemala',
+		'CountryCn': '瓜地马拉',
+		'CountryTw': '瓜地馬拉',
+		'Code': 502
+	},
+	'Honduras': {
+		'Culture': 'es-HN',
+		'CountryEn': 'Honduras',
+		'CountryCn': '宏都拉斯',
+		'CountryTw': '宏都拉斯',
+		'Code': 504
+	},
+	'Mauritius': {
+		'Culture': '',
+		'CountryEn': 'Mauritius',
+		'CountryCn': '摩里西斯',
+		'CountryTw': '摩里西斯',
+		'Code': 230
+	},
+	'Mexico': {
+		'Culture': 'es-MX',
+		'CountryEn': 'Mexico',
+		'CountryCn': '墨西哥',
+		'CountryTw': '墨西哥',
+		'Code': 52
+	},
+	'Venezuela': {
+		'Culture': 'es-VE',
+		'CountryEn': 'Venezuela',
+		'CountryCn': '委内瑞拉',
+		'CountryTw': '委內瑞拉',
+		'Code': 58
+	},
+	'PuertoRico': {
+		'Culture': 'es-PR',
+		'CountryEn': 'Puerto Rico',
+		'CountryCn': '波多黎各',
+		'CountryTw': '波多黎各',
+		'Code': 1
+	},
+	'Nicaragua': {
+		'Culture': 'es-NI',
+		'CountryEn': 'Nicaragua',
+		'CountryCn': '尼加拉瓜',
+		'CountryTw': '尼加拉瓜',
+		'Code': 505
+	},
+	'Panama': {
+		'Culture': 'es-PA',
+		'CountryEn': 'Panama',
+		'CountryCn': '巴拿马',
+		'CountryTw': '巴拿馬',
+		'Code': 507
+	},
+	'PapuaNewGuinea': {
+		'Culture': '',
+		'CountryEn': 'Papua New Guinea',
+		'CountryCn': '巴布亚纽几内亚',
+		'CountryTw': '巴布亞紐幾內亞',
+		'Code': 675
+	},
+	'Paraguay': {
+		'Culture': 'es-PY',
+		'CountryEn': 'Paraguay',
+		'CountryCn': '巴拉圭',
+		'CountryTw': '巴拉圭',
+		'Code': 595
+	},
+	'Peru': {
+		'Culture': 'es-PE',
+		'CountryEn': 'Peru',
+		'CountryCn': '秘鲁',
+		'CountryTw': '秘魯',
+		'Code': 51
+	},
+	'Austria': {
+		'Culture': 'de-AT',
+		'CountryEn': 'Austria',
+		'CountryCn': '奥地利',
+		'CountryTw': '奧地利',
+		'Code': 43
+	},
+	'Belgium': {
+		'Culture': '',
+		'CountryEn': 'Belgium',
+		'CountryCn': '比利时',
+		'CountryTw': '比利時',
+		'Code': 32
+	},
+	'Bulgaria': {
+		'Culture': 'bg-BG',
+		'CountryEn': 'Bulgaria',
+		'CountryCn': '保加利亚',
+		'CountryTw': '保加利亞',
+		'Code': 359
+	},
+	'Czech Rep.': {
+		'Culture': 'cs-CZ',
+		'CountryEn': 'Czech Rep.',
+		'CountryCn': '捷克',
+		'CountryTw': '捷克',
+		'Code': 420
+	},
+	'Denmark': {
+		'Culture': 'da-DK',
+		'CountryEn': 'Denmark',
+		'CountryCn': '丹麦',
+		'CountryTw': '丹麥',
+		'Code': 45
+	},
+	'Finland': {
+		'Culture': 'fi-FI',
+		'CountryEn': 'Finland',
+		'CountryCn': '芬兰',
+		'CountryTw': '芬蘭',
+		'Code': 358
+	},
+	'France': {
+		'Culture': 'fr-FR',
+		'CountryEn': 'France',
+		'CountryCn': '法国',
+		'CountryTw': '法國',
+		'Code': 33
+	},
+	'Germany': {
+		'Culture': 'de-DE',
+		'CountryEn': 'Germany',
+		'CountryCn': '德国',
+		'CountryTw': '德國',
+		'Code': 49
+	},
+	'Gibraltar': {
+		'Culture': '',
+		'CountryEn': 'Gibraltar',
+		'CountryCn': '直布罗陀',
+		'CountryTw': '直布羅陀',
+		'Code': 350
+	},
+	'Grenada': {
+		'Culture': '',
+		'CountryEn': 'Grenada',
+		'CountryCn': '格瑞那达',
+		'CountryTw': '格瑞那達',
+		'Code': 1473
+	},
+	'Greek': {
+		'Culture': 'el-GR',
+		'CountryEn': 'Greek',
+		'CountryCn': '希腊',
+		'CountryTw': '希臘',
+		'Code': 30
+	},
+	'Hungary': {
+		'Culture': 'hu-HU',
+		'CountryEn': 'Hungary',
+		'CountryCn': '匈牙利',
+		'CountryTw': '匈牙利',
+		'Code': 36
+	},
+	'Haiti': {
+		'Culture': '',
+		'CountryEn': 'Haiti',
+		'CountryCn': '海地',
+		'CountryTw': '海地',
+		'Code': 509
+	},
+	'Iceland': {
+		'Culture': 'is-IS',
+		'CountryEn': 'Iceland',
+		'CountryCn': '冰岛',
+		'CountryTw': '冰島',
+		'Code': 354
+	},
+	'Ireland': {
+		'Culture': 'en-IE',
+		'CountryEn': 'Ireland',
+		'CountryCn': '爱尔兰',
+		'CountryTw': '愛爾蘭',
+		'Code': 353
+	},
+	'Italy': {
+		'Culture': 'it-IT',
+		'CountryEn': 'Italy',
+		'CountryCn': '义大利',
+		'CountryTw': '義大利',
+		'Code': 39
+	},
+	'IvoryCoastRep': {
+		'Culture': '',
+		'CountryEn': 'Ivory Coast Rep.',
+		'CountryCn': '象牙海岸',
+		'CountryTw': '象牙海岸',
+		'Code': 225
+	},
+	'Luxembourg': {
+		'Culture': '',
+		'CountryEn': 'Luxembourg',
+		'CountryCn': '卢森堡',
+		'CountryTw': '盧森堡',
+		'Code': 352
+	},
+	'Monaco': {
+		'Culture': 'fr-MC',
+		'CountryEn': 'Monaco',
+		'CountryCn': '摩纳哥',
+		'CountryTw': '摩納哥',
+		'Code': 377
+	},
+	'Netherlands': {
+		'Culture': 'nl-NL',
+		'CountryEn': 'Netherlands',
+		'CountryCn': '荷兰',
+		'CountryTw': '荷蘭',
+		'Code': 31
+	},
+	'Norway': {
+		'Culture': '',
+		'CountryEn': 'Norway',
+		'CountryCn': '挪威',
+		'CountryTw': '挪威',
+		'Code': 47
+	},
+	'Poland': {
+		'Culture': 'pl-PL',
+		'CountryEn': 'Poland',
+		'CountryCn': '波兰',
+		'CountryTw': '波蘭',
+		'Code': 48
+	},
+	'Portugal': {
+		'Culture': 'pt-PT',
+		'CountryEn': 'Portugal',
+		'CountryCn': '葡萄牙',
+		'CountryTw': '葡萄牙',
+		'Code': 351
+	},
+	'Qatar': {
+		'Culture': 'ar-QA',
+		'CountryEn': 'Qatar',
+		'CountryCn': '卡达',
+		'CountryTw': '卡達',
+		'Code': 974
+	},
+	'Romania': {
+		'Culture': 'ro-RO',
+		'CountryEn': 'Romania',
+		'CountryCn': '罗马尼亚',
+		'CountryTw': '羅馬尼亞',
+		'Code': 40
+	},
+	'Spain': {
+		'Culture': 'es-ES',
+		'CountryEn': 'Spain',
+		'CountryCn': '西班牙',
+		'CountryTw': '西班牙',
+		'Code': 34
+	},
+	'Sweden': {
+		'Culture': 'sv-SE',
+		'CountryEn': 'Sweden',
+		'CountryCn': '瑞典',
+		'CountryTw': '瑞典',
+		'Code': 46
+	},
+	'Switzerland': {
+		'Culture': '',
+		'CountryEn': 'Switzerland',
+		'CountryCn': '瑞士',
+		'CountryTw': '瑞士',
+		'Code': 41
+	},
+	'UnitedKingdom': {
+		'Culture': 'en-GB',
+		'CountryEn': 'United Kingdom',
+		'CountryCn': '英国',
+		'CountryTw': '英國',
+		'Code': 44
+	},
+	'Ukraine': {
+		'Culture': 'uk-UA',
+		'CountryEn': 'Ukraine',
+		'CountryCn': '乌克兰',
+		'CountryTw': '烏克蘭',
+		'Code': 380
+	},
+	'Uruguay': {
+		'Culture': 'es-UY',
+		'CountryEn': 'Uruguay',
+		'CountryCn': '乌拉圭',
+		'CountryTw': '烏拉圭',
+		'Code': 598
+	},
+	'Vatican': {
+		'Culture': '',
+		'CountryEn': 'Vatican',
+		'CountryCn': '梵谛冈',
+		'CountryTw': '梵諦岡',
+		'Code': 379
+	},
+	'Yugoslavia': {
+		'Culture': '',
+		'CountryEn': 'Yugoslavia',
+		'CountryCn': '南斯拉夫',
+		'CountryTw': '南斯拉夫',
+		'Code': 381
+	},
+	'Australia': {
+		'Culture': 'en-AU',
+		'CountryEn': 'Australia',
+		'CountryCn': '澳大利亚',
+		'CountryTw': '澳大利亞',
+		'Code': 61
+	},
+	'Fiji': {
+		'Culture': '',
+		'CountryEn': 'Fiji',
+		'CountryCn': '斐济',
+		'CountryTw': '斐濟',
+		'Code': 679
+	},
+	'Guam': {
+		'Culture': '',
+		'CountryEn': 'Guam',
+		'CountryCn': '关岛',
+		'CountryTw': '關島',
+		'Code': 1
+	},
+	'Malta': {
+		'Culture': 'mt-MT',
+		'CountryEn': 'Malta',
+		'CountryCn': '马尔他',
+		'CountryTw': '馬爾他',
+		'Code': 356
+	},
+	'NewCaledonia': {
+		'Culture': '',
+		'CountryEn': 'New Caledonia',
+		'CountryCn': '新喀里多尼亚',
+		'CountryTw': '新喀里多尼亞',
+		'Code': 687
+	},
+	'NewZealand': {
+		'Culture': 'en-NZ',
+		'CountryEn': 'New Zealand',
+		'CountryCn': '纽西兰',
+		'CountryTw': '紐西蘭',
+		'Code': 64
+	},
+	'Palau': {
+		'Culture': '',
+		'CountryEn': 'Palau',
+		'CountryCn': '帛琉',
+		'CountryTw': '帛琉',
+		'Code': 680
+	},
+	'Saipan': {
+		'Culture': '',
+		'CountryEn': 'Saipan',
+		'CountryCn': '塞班岛',
+		'CountryTw': '塞班島',
+		'Code': 670
+	},
+	'Solomon IS.': {
+		'Culture': '',
+		'CountryEn': 'Solomon IS.',
+		'CountryCn': '所罗门群岛',
+		'CountryTw': '所羅門群島',
+		'Code': 677
+	},
+	'Central African': {
+		'Culture': '',
+		'CountryEn': 'Central African',
+		'CountryCn': '中非共和国',
+		'CountryTw': '中非共和國',
+		'Code': 236
+	},
+	'Congo': {
+		'Culture': '',
+		'CountryEn': 'Congo',
+		'CountryCn': '刚果',
+		'CountryTw': '剛果',
+		'Code': 243
+	},
+	'Egypt': {
+		'Culture': 'ar-EG',
+		'CountryEn': 'Egypt',
+		'CountryCn': '埃及',
+		'CountryTw': '埃及',
+		'Code': 671
+	},
+	'ElSalvador': {
+		'Culture': 'es-SV',
+		'CountryEn': 'El Salvador',
+		'CountryCn': '萨尔瓦多',
+		'CountryTw': '薩爾瓦多',
+		'Code': 503
+	},
+	'Estonia': {
+		'Culture': 'et-EE',
+		'CountryEn': 'Estonia',
+		'CountryCn': '爱沙尼亚',
+		'CountryTw': '愛沙尼亞',
+		'Code': 372
+	},
+	'Ethiopia': {
+		'Culture': '',
+		'CountryEn': 'Ethiopia',
+		'CountryCn': '衣索匹亚',
+		'CountryTw': '衣索匹亞',
+		'Code': 251
+	},
+	'Gabonese Rep.': {
+		'Culture': '',
+		'CountryEn': 'Gabonese Rep.',
+		'CountryCn': '加彭共和国',
+		'CountryTw': '加彭共和國',
+		'Code': 241
+	},
+	'Jordan': {
+		'Culture': 'ar-JO',
+		'CountryEn': 'Jordan',
+		'CountryCn': '约旦',
+		'CountryTw': '約旦',
+		'Code': 962
+	},
+	'Kenya': {
+		'Culture': 'sw-KE',
+		'CountryEn': 'Kenya',
+		'CountryCn': '肯亚',
+		'CountryTw': '肯亞',
+		'Code': 254
+	},
+	'Lesotho': {
+		'Culture': '',
+		'CountryEn': 'Lesotho',
+		'CountryCn': '赖索托',
+		'CountryTw': '賴索托',
+		'Code': 266
+	},
+	'Liberia': {
+		'Culture': '',
+		'CountryEn': 'Liberia',
+		'CountryCn': '赖比瑞亚',
+		'CountryTw': '賴比瑞亞',
+		'Code': 231
+	},
+	'Morocco': {
+		'Culture': 'ar-MA',
+		'CountryEn': 'Morocco',
+		'CountryCn': '摩洛哥',
+		'CountryTw': '摩洛哥',
+		'Code': 212
+	},
+	'Mozambique': {
+		'Culture': '',
+		'CountryEn': 'Mozambique',
+		'CountryCn': '莫三比克',
+		'CountryTw': '莫三比克',
+		'Code': 258
+	},
+	'Niger': {
+		'Culture': '',
+		'CountryEn': 'Niger',
+		'CountryCn': '尼日共和国',
+		'CountryTw': '尼日共和國',
+		'Code': 227
+	},
+	'Nigeria': {
+		'Culture': '',
+		'CountryEn': 'Nigeria',
+		'CountryCn': '奈及利亚',
+		'CountryTw': '奈及利亞',
+		'Code': 234
+	},
+	'South Africa Rep.': {
+		'Culture': '',
+		'CountryEn': 'South Africa Rep.',
+		'CountryCn': '南非',
+		'CountryTw': '南非',
+		'Code': 27
+	},
+	'Swaziland': {
+		'Culture': '',
+		'CountryEn': 'Swaziland',
+		'CountryCn': '史瓦济兰',
+		'CountryTw': '史瓦濟蘭',
+		'Code': 268
+	},
+	'Uganda': {
+		'Culture': '',
+		'CountryEn': 'Uganda',
+		'CountryCn': '乌干达',
+		'CountryTw': '烏干達',
+		'Code': 256
+	},
+	'Algeria': {
+		'Culture': 'ar-DZ',
+		'CountryEn': 'Algeria',
+		'CountryCn': '阿尔及利亚',
+		'CountryTw': '阿爾及利亞',
+		'Code': 213
+	},
+	'Algeria': {
+		'Culture': 'be-BY',
+		'CountryEn': 'Belarus',
+		'CountryCn': '白俄罗斯',
+		'CountryTw': '白俄羅斯',
+		'Code': 375
+	}
+}

+ 0 - 5
TEAMModelOS/ClientApp/src/utils/blobTool.js

@@ -396,8 +396,6 @@ export default class BlobTool {
      * @param {string} filePath 文件url + 容器 之后的路径
      */
     deleteBlob(filePath, size) {
-        console.log(typeof size)
-        console.log(typeof size == 'number')
         if (filePath && typeof size == 'number') {
             filePath = filePath.substring(1)
             return new Promise((r, j) => {
@@ -472,7 +470,6 @@ export default class BlobTool {
      * 2、目标url(sourceUrl)如果需要授权的容器,则url需要凭借授权。
      */
     copyBlob(targetUrl, sourceUrl, sas) {
-		console.log(...arguments)
         return new Promise((r, j) => {
             let newBlob = this.containerClient.getBlobClient(targetUrl)
             let encodeUrl = encodeURI(sourceUrl)
@@ -496,7 +493,6 @@ export default class BlobTool {
      * @param {boolean} handleSize 是否内部处理blobsize
      */
     copyFolder(targetFolder, sourceFolder, blobTool, handleSize = true) {
-		console.log(...arguments);
         return new Promise(async (r, j) => {
             let blobs = undefined
             let sasString = ''
@@ -511,7 +507,6 @@ export default class BlobTool {
                 }, false)
                 sasString = this.sasString
             }
-			console.log(blobs);
             //上传之前检查文件夹大小
             let beforeSize = 0
             let cont = ''

+ 4 - 0
TEAMModelOS/ClientApp/src/utils/editorLangTw.js

@@ -62,6 +62,10 @@ let editor_tw_config = {
                 },
             },
             panelMenus: {
+				删除:' 删除 ',
+				formula:{
+					插入公式:'插入公式',
+				},
                 emoticon: {
                     默认: '默認',
                     新浪: '新浪',

+ 1 - 1
TEAMModelOS/ClientApp/src/utils/editorTools.js

@@ -409,7 +409,7 @@ export default {
 		      // 公式输入插件
 		      constructor(editors) {
 		        const $elem = $(
-		        	'<div class="w-e-menu" style="color:red"><i class="ivu-icon ivu-icon-logo-tumblr" style="font-size: 20px;"></i></div>'
+		        	'<div class="w-e-menu" style="color:red"><i class="icon iconfont icon-function" style="font-size: 20px;font-weight:bold"></i></div>'
 		        );
 		        super($elem, editors);
 		      }

+ 6 - 0
TEAMModelOS/ClientApp/src/utils/evTools.js

@@ -360,6 +360,8 @@ export default {
 			try{
 				let jsonInfo = await $tools.getFile(fullPath)
 				let jsonData = JSON.parse(jsonInfo)
+				jsonData.scope = 'private'
+				jsonData.code = tmdId
 				// 获取试卷包含的试题数据并包装好
 				if(jsonData.slides && jsonData.slides.length){
 					let promiseArr = []
@@ -423,6 +425,8 @@ export default {
 			try{
 				let jsonInfo = await $tools.getFile(blobHost + paper.blob + '/index.json' + sasString.sas)
 				let jsonData = JSON.parse(jsonInfo)
+				jsonData.scope = curScope
+				jsonData.code = paper.code
 				// 获取试卷包含的试题数据并包装好
 				if(jsonData.slides && jsonData.slides.length){
 					let promiseArr = []
@@ -489,6 +493,8 @@ export default {
 			try {
 				let jsonInfo = await $tools.getFile(sasString.url + '/' + paper.code + paper.blob + '/index.json' + sasString.sas)
 				let jsonData = JSON.parse(jsonInfo)
+				jsonData.scope = curScope
+				jsonData.code = paper.code
 				// 获取试卷包含的试题数据并包装好
 				if (jsonData.slides && jsonData.slides.length) {
 					jsonData.item = []

+ 7 - 0
TEAMModelOS/ClientApp/src/utils/js-fn.js

@@ -128,6 +128,10 @@ function compressImgByUrl(url, name, quality) {
                 //r(resultFile)
                 r(newImgData)
             }
+            img.onerror = function (e) {
+                console.error('图片Error', e)
+                j('Format Error')
+            }
         }
         catch (err) {
             j(err)
@@ -159,6 +163,9 @@ function createVideoPoster(url, name, quality) {
                     //let resultFile = dataURLtoFile(newVideoData, name)
                     r(newVideoData)
                 })
+                video.addEventListener('error', (e) => {
+                    j('Format Error')
+                })
             }
             catch (err) {
                 j(err)

+ 2 - 2
TEAMModelOS/ClientApp/src/utils/kityformula.js

@@ -11,13 +11,13 @@ export default function(editor) {
 		// panel 中可包含多个 tab
 		tabs: [{
 				// tab 的标题
-				title: editor.i18next.t('menus.panelMenus.formula.插入公式'),
+				title: app.$t('evaluation.editor.insertFormula'),
 				// 模板
 				tpl: `<div>
                   <iframe id="${inputIFrameId}" class="iframe" height="500px" width="100%" frameborder="0" scrolling="no" src="/kityformula/index.html"></iframe>
                   <div class="w-e-button-container">
                       <button id="${btnOkId}" class="right">
-                          ${editor.i18next.t('确认插入')}
+                          ${app.$t('evaluation.editor.confirmInsert')}
                       </button>
                   </div>
               </div>`,

+ 2 - 0
TEAMModelOS/ClientApp/src/utils/public.js

@@ -281,9 +281,11 @@ export default {
 						break;
 					case 404:
 						Message.error('未访问到资源!')
+						reject(404)
 						break;
 					case 403:
 						Message.error('授权异常,无法访问!')
+						reject(403)
 						break;		
 					default:
 						break;

+ 1 - 2
TEAMModelOS/ClientApp/src/view/answersheet/index.vue

@@ -393,8 +393,7 @@
 			})
 		},
 		beforeRouteLeave(to, from, next) {
-			if(to.name === 'newSchoolPaper' || to.name === 'newPrivatePaper' || to.name === 'schoplBank' || to.name === 'personalBank'){
-				console.error(to)
+			if(to.name === 'newSchoolPaper' || to.name === 'newPrivatePaper' || to.name === 'schoolBank' || to.name === 'personalBank'){
 				// 设置下一个路由的 meta
 				to.meta.isKeep = true;  // 让 A 缓存,即不刷新
 			}

+ 89 - 24
TEAMModelOS/ClientApp/src/view/evaluation/bank/ExerciseList.vue

@@ -35,33 +35,33 @@
 				<span class="filter-title">{{$t('evaluation.filter.type')}}:</span>
 				<CheckboxGroup v-model="filterType" border @on-change="filterTypeChange">
 					<Checkbox label="all">{{$t('evaluation.filter.all')}}</Checkbox>
-					<Checkbox label="single">{{$t('evaluation.single')}}</Checkbox>
-					<Checkbox label="multiple">{{$t('evaluation.multiple')}}</Checkbox>
-					<Checkbox label="judge">{{$t('evaluation.judge')}}</Checkbox>
-					<Checkbox label="complete">{{$t('evaluation.complete')}}</Checkbox>
-					<Checkbox label="subjective">{{$t('evaluation.subjective')}}</Checkbox>
-					<Checkbox label="connector">{{$t('evaluation.connector')}}</Checkbox>
-					<Checkbox label="correct">{{$t('evaluation.correct')}}</Checkbox>
-					<Checkbox label="compose">{{$t('evaluation.compose')}}</Checkbox>
+					<Checkbox label="single">{{$t('evaluation.single')}}<span class="filter-count">({{ typeCountArr.length ? typeCountArr[0] : 0 }})</span></Checkbox>
+					<Checkbox label="multiple">{{$t('evaluation.multiple')}}<span class="filter-count">({{ typeCountArr.length ? typeCountArr[1] : 0 }})</span></Checkbox>
+					<Checkbox label="judge">{{$t('evaluation.judge')}}<span class="filter-count">({{ typeCountArr.length ? typeCountArr[2] : 0 }})</span></Checkbox>
+					<Checkbox label="complete">{{$t('evaluation.complete')}}<span class="filter-count">({{ typeCountArr.length ? typeCountArr[3] : 0 }})</span></Checkbox>
+					<Checkbox label="subjective">{{$t('evaluation.subjective')}}<span class="filter-count">({{ typeCountArr.length ? typeCountArr[4] : 0 }})</span></Checkbox>
+					<Checkbox label="connector">{{$t('evaluation.connector')}}<span class="filter-count">({{ typeCountArr.length ? typeCountArr[5] : 0 }})</span></Checkbox>
+					<Checkbox label="correct">{{$t('evaluation.correct')}}<span class="filter-count">({{ typeCountArr.length ? typeCountArr[6] : 0 }})</span></Checkbox>
+					<Checkbox label="compose">{{$t('evaluation.compose')}}<span class="filter-count">({{ typeCountArr.length ? typeCountArr[7] : 0 }})</span></Checkbox>
 				</CheckboxGroup>
 			</div>
 			<div class="filter-item">
 				<span class="filter-title">{{$t('evaluation.filter.diff')}}:</span>
 				<CheckboxGroup v-model="filterDiff" border @on-change="filterDiffChange">
 					<Checkbox label="all">{{$t('evaluation.filter.all')}}</Checkbox>
-					<Checkbox v-for="(item, index) in exersicesDiff" :key="index" :label="index + 1">{{ item }}</Checkbox>
+					<Checkbox v-for="(item, index) in exersicesDiff" :key="index" :label="index + 1">{{ item }}<span class="filter-count">({{ levelCountArr.length ? levelCountArr[index] : 0 }})</span></Checkbox>
 				</CheckboxGroup>
 			</div>
 			<div class="filter-item">
 				<span class="filter-title">{{$t('evaluation.filter.level')}}:</span>
 				<CheckboxGroup v-model="filterField" border @on-change="filterFieldChange">
 					<Checkbox label="all">{{$t('evaluation.filter.all')}}</Checkbox>
-					<Checkbox :label="1">{{$t('evaluation.level1')}}</Checkbox>
-					<Checkbox :label="2">{{$t('evaluation.level2')}}</Checkbox>
-					<Checkbox :label="3">{{$t('evaluation.level3')}}</Checkbox>
-					<Checkbox :label="4">{{$t('evaluation.level4')}}</Checkbox>
-					<Checkbox :label="5">{{$t('evaluation.level5')}}</Checkbox>
-					<Checkbox :label="6">{{$t('evaluation.level6')}}</Checkbox>
+					<Checkbox :label="1">{{$t('evaluation.level1')}}<span class="filter-count">({{ fieldCountArr.length ? fieldCountArr[0] : 0 }})</span></Checkbox>
+					<Checkbox :label="2">{{$t('evaluation.level2')}}<span class="filter-count">({{ fieldCountArr.length ? fieldCountArr[1] : 0 }})</span></Checkbox>
+					<Checkbox :label="3">{{$t('evaluation.level3')}}<span class="filter-count">({{ fieldCountArr.length ? fieldCountArr[2] : 0 }})</span></Checkbox>
+					<Checkbox :label="4">{{$t('evaluation.level4')}}<span class="filter-count">({{ fieldCountArr.length ? fieldCountArr[3] : 0 }})</span></Checkbox>
+					<Checkbox :label="5">{{$t('evaluation.level5')}}<span class="filter-count">({{ fieldCountArr.length ? fieldCountArr[4] : 0 }})</span></Checkbox>
+					<Checkbox :label="6">{{$t('evaluation.level6')}}<span class="filter-count">({{ fieldCountArr.length ? fieldCountArr[5] : 0 }})</span></Checkbox>
 				</CheckboxGroup>
 			</div>
 			<div class="filter-item">
@@ -236,9 +236,14 @@
 				allPointList: [],
 				originData: [],
 				flag:false,
+				itemConds:[],
 				gradeCountArr:[],
 				subjectCountArr:[],
-				periodCountArr:[]
+				periodCountArr:[],
+				typeCountArr:[],
+				fieldCountArr:[],
+				levelCountArr:[],
+				
 			};
 		},
 		created() {
@@ -260,7 +265,7 @@
 			getSchoolInfo() {
 				this.dataLoading = true;
 				try {
-					this.$store.dispatch("user/getSchoolProfile").then((res) => {
+					this.$store.dispatch("user/getSchoolProfile").then(async (res) => {
 						let schoolBaseInfo = res.school_base;
 						if (schoolBaseInfo) {
 							this.schoolInfo = schoolBaseInfo;
@@ -273,6 +278,7 @@
 							if (schoolBaseInfo.period.length) {
 								this.gradeList = schoolBaseInfo.period[0].grades;
 								this.subjectList = schoolBaseInfo.period[0].subjects;
+								this.itemConds = await this.getFilterCount()
 							}
 						}
 						this.doFilter();
@@ -306,6 +312,9 @@
 					scope: this.curScope,
 					pid: null
 				};
+				if(filterKey === 'period'){
+					this.getAllCountsByPeriod()
+				}
 				this.getExerciseList(this.filterParams,filterKey);
 			},
 
@@ -323,7 +332,8 @@
 							list = res.items.filter(i => i.periodId === this.periodList[this.filterPeriod].id)
 							this.flag = true
 						}
-						this.getItemsCount(list,filterKey)
+						this.getAllCountsByPeriod()
+						// this.getItemsCount(list,filterKey)
 						/* 获取试题总数 */
 						this.totalNum = list.length;
 						this.originData = list;
@@ -336,17 +346,71 @@
 					}
 					setTimeout(() => {
 						that.dataLoading = false;
-					}, 1000);
+					}, 0);
 				});
 			},
 			
+			
+			/* 获取题库数量统计数据 */
+			getFilterCount(){
+				return new Promise((r,j) => {
+					this.$api.evaluation.getFilterCount({
+						schoolCode:this.$store.state.userInfo.schoolCode
+					}).then(res => {
+						console.log(res)
+						r(res.itemConds)
+					}).catch(e => {
+						j(e)
+					})
+				})
+				
+			},
+			
+			getAllCountsByPeriod(){
+				let curPeriodCond = this.itemConds.find(i => i.id === this.periodList[this.filterPeriod].id)
+				let typeList = Object.keys(this.exersicesType)
+				let levelList = Object.keys(this.exersicesDiff)
+				let fieldList = Object.keys(this.exersicesField)
+				this.gradeCountArr = this.gradeList.map((i,index) => {
+					return curPeriodCond.grades.find(j => +j.id === index) ? curPeriodCond.grades.find(j => +j.id === index).count : 0
+				})
+				this.subjectCountArr = this.subjectList.map(i => {
+					return curPeriodCond.subjects.find(j => j.id === i.id) ? curPeriodCond.subjects.find(j => j.id === i.id).count : 0
+				})
+				this.typeCountArr = typeList.map(i => {
+					return curPeriodCond.subjects.reduce((a,b) => a + this._.sum(Object.values(b.types[i].level)),0)
+				})
+				this.levelCountArr = levelList.map(i => {
+					return curPeriodCond.subjects.reduce((a,b) => a + this._.sum(this.findKey(b,'level').map(j => j[+i+1+''])),0)
+				})
+				this.fieldCountArr = fieldList.map(i => {
+					return curPeriodCond.subjects.reduce((a,b) => a + this._.sum(this.findKey(b,'field').map(j => j[+i+1+''])),0)
+				})
+				console.log(curPeriodCond)
+				console.log(this.gradeCountArr)
+				console.log(this.subjectCountArr)
+				console.log(this.levelCountArr)
+				console.log(this.fieldCountArr)
+			},
+			
+			findKey(data, field) {
+				let result = []
+				const fn = (source)=>{
+					for (const key in source) {
+						if (key === field) {
+						  result.push(source[key]);
+						}else if (Object.prototype.toString.call(source[key]) === '[object Object]') {
+						  fn(source[key], field);
+						}
+					}
+				}
+				fn(data)
+				return result
+			},
+			
 			/* 获取所有学段下的试题总数 */
 			getPeriodCount(items){
-				let periodIdArr = this.periodList.map(i => i.id)
-				let periodCountArr = periodIdArr.map(i => {
-					return items.filter(j => j.periodId === i).length
-				})
-				return periodCountArr
+				return this.periodList.map(i => this.itemConds.find(j => j.id === i.id).count)
 			},
 			
 			/* 获取试题的年级、科目的数量统计 */
@@ -436,6 +500,7 @@
 				this.subjectList = this.schoolInfo.period[val].subjects;
 				this.filterGrade = [false];
 				this.filterSubject = [false];
+				// this.getAllCountsByPeriod()
 				this.doFilter('period');
 			},
 

+ 2 - 5
TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue

@@ -177,7 +177,6 @@
 			// this.getPaperList()
 			// this.doFilter()
 			this.isShowSchoolBank = this.$route.name === "schoolBank";
-
 		},
 		activated() {
 			this.isShowSchoolBank = this.$route.name === "schoolBank";
@@ -281,7 +280,7 @@
 				let that = this
 				this.$api.learnActivity.FindExamPaper(params).then(async res => {
 					let list = res.papers
-					if((!this.flag && this.periodList.length && this.filterParams.code === this.$store.state.userInfo.schoolCode) || (this.flag && !this.isShowSchoolBank && this.filterParams.code === this.$store.state.userInfo.schoolCode)){
+					if((!this.flag && this.periodList.length && this.filterParams.code === this.$store.state.userInfo.schoolCode) || (!this.flag && !this.isShowSchoolBank && this.filterParams.code === this.$store.state.userInfo.schoolCode)){
 						this.filterCounts.periodCountArr = this.getPeriodCount(res.papers)
 						list = res.papers.filter(i => i.periodId === this.periodList[0].id)
 						this.flag = true
@@ -329,13 +328,11 @@
 			
 			/* 获取试题的年级、科目的数量统计 */
 			getItemsCount(papers,filterKey){
-				if(filterKey !== 'grade'){
+				if(filterKey === 'period' || !filterKey){
 					let gradeIdArr = this.$refs.baseFilter.gradeList.map((i,index) => index)
 					this.filterCounts.gradeCountArr = gradeIdArr.map(i => {
 						return papers.filter(j => j.gradeIds && j.gradeIds.includes(i + '')).length
 					})
-				}
-				if(filterKey !== 'subject'){
 					let subjectIdArr = this.$refs.baseFilter.subjectList.map(i => i.id)
 					this.filterCounts.subjectCountArr = subjectIdArr.map(i => {
 						return papers.filter(j => j.subjectId === i).length

+ 16 - 5
TEAMModelOS/ClientApp/src/view/evaluation/bank/index.vue

@@ -144,11 +144,22 @@
 				return this.$store.state.totalAnalysis.paperScrollTop
 			},
 		},
+		beforeRouteEnter(to, from, next) {
+			if(from.name !== 'answerSheet'){
+				to.meta.isKeep = false
+			}else{
+				to.meta.isKeep = true
+			}
+			next();
+		},
 		beforeRouteLeave(to, from, next) {
 			if(to.name === 'newSchoolPaper' || to.name === 'newPrivatePaper'){
 				// 设置下一个路由的 meta
 				to.meta.isKeep = false;  // 让 A 缓存,即不刷新
 			}
+			if(to.name === 'answerSheet'){
+				from.meta.isKeep = true
+			}
 			next();
 		},
 		watch: {
@@ -157,11 +168,11 @@
 					// console.log(n)
 				}
 			},
-			$route( to , from ){   
-			   if(from.name !== 'answerSheet' && (to.name === 'schoolBank' || to.name === 'personalBank')){
-				  this.onShowPaperList()
-			   }
-			 }
+			// $route( to , from ){   
+			//    if(from.name !== 'answerSheet' && (to.name === 'schoolBank' || to.name === 'personalBank')){
+			// 	  this.onShowPaperList()
+			//    }
+			//  }
 		}
 	}
 </script>

+ 14 - 5
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseRepair.vue

@@ -51,9 +51,11 @@
 		    <NewChooseContent :showSyllabus="isFalse"
 		                   :showOther="isFalse"
 		                   :showQuestion="isFalse"
+						   :showPaper="isFalse"
 						   :defaultFiles="curRepair.blobUrl"
 						   ref="chooseContentRef"
-		                   @on-file-change="onSelectFile"></NewChooseContent>
+		                   @on-file-change="onSelectFile"
+						   v-if="isRelatedContent"></NewChooseContent>
 		
 		    <Button class="modal-btn" :loading="isLoading" @click="onConfirmRelate">{{$t('evaluation.confirm')}}</Button>
 		</Modal>
@@ -71,7 +73,7 @@
 			<p style="margin: 15px 2px;">{{ $t('evaluation.repairResourse.link')}}{{ isSiteLink ?  '' : $t('evaluation.repairResourse.tip1')}}</p>
 			
 			<!-- 选择内容 -->
-			<Button type="info" @click="isRelatedContent = true" v-if="isSiteLink">{{$t('evaluation.newExercise.chooseContent')}}</Button>
+			<Button type="info" @click="doSelectContent" v-if="isSiteLink">{{$t('evaluation.newExercise.chooseContent')}}</Button>
 			<!-- 手动输入 -->
 			<Input v-model="curOutLink" v-if="!isSiteLink" :placeholder="$t('evaluation.repairResourse.place2')" @on-enter="onAddOutLink"/>
 			<!-- 链接link列表 -->
@@ -135,6 +137,15 @@
 				this.defaultFiles = []
 			},
 			
+			doSelectContent(){
+				this.isRelatedContent = true
+				this.$nextTick(() => {
+					if(this.$refs.chooseContentRef){
+						this.$refs.chooseContentRef.clickTab('content')
+					}
+				})
+			},
+			
 			/* 回车添加外部资源链接 */
 			onAddOutLink(){
 				if(this.isURL(this.curOutLink)){
@@ -219,9 +230,7 @@
 		},
 
 		mounted() {
-			if(this.$refs.chooseContentRef){
-				this.$refs.chooseContentRef.clickTab('content')
-			}
+			
 
 		},
 		watch:{

+ 8 - 7
TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue

@@ -212,10 +212,6 @@
 				})
 			},
 
-			onTabChange(val) {
-				// console.log(val)
-			},
-
 			/* 返回顶部 */
 			handleBackToTop() {
 				this.$nextTick(() => {
@@ -264,6 +260,9 @@
 			/* 给导入的试题 补充最新的试卷学段年级以及科目信息 */
 			refreshImportItems(list) {
 				return new Promise((r, reject) => {
+					console.log(this.schoolInfo);
+					console.log(this.evaluationInfo);
+					console.log(this.evaluationInfo.paperPeriod);
 					let objectiveTypes = ['single', 'multiple']
 					let code = this.isSchool ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo
 						.TEAMModelId
@@ -1093,6 +1092,8 @@
 					}
 				)
 			},
+			
+
 
 			/* 渲染需要编辑的试卷信息 */
 			async doRender(paper) {
@@ -1166,18 +1167,18 @@
 
 		beforeRouteEnter(to, from, next) {
 			if (from.name === 'answerSheet' && (to.name === 'newSchoolPaper' || to.name === 'newPrivatePaper')) {
-				console.log('xxxxxxxxxxxx',to)
 				to.meta.isKeep = true
 			}
 			next()
 		},
 		beforeRouteLeave(to, from, next) {
 			if(to.name === 'answerSheet'){
-				console.error(to)
-				console.error(from)
 				// 设置下一个路由的 meta
 				from.meta.isKeep = true;  // 让 A 缓存,即不刷新
 			}
+			if(to.name === 'schoolBank' || to.name === 'personalBank'){
+				to.meta.isKeep = false
+			}
 			next();
 		},
 		watch: {

+ 0 - 0
TEAMModelOS/ClientApp/src/view/homepage/HomePage.vue


Some files were not shown because too many files changed in this diff