Explorar el Código

Merge branch 'develop' of http://106.12.23.251:10080/TEAMMODEL/TEAMModelOS into develop

chenmy hace 6 años
padre
commit
43fda683c3
Se han modificado 45 ficheros con 2192 adiciones y 48 borrados
  1. 12 0
      TEAMModelOS.Model/Common/Dtos/LoginResult.cs
  2. 18 0
      TEAMModelOS.Model/Common/Dtos/TeamModelIdInfo.cs
  3. 22 0
      TEAMModelOS.Model/Common/Dtos/TicketInfo.cs
  4. 32 0
      TEAMModelOS.Model/Common/Models/LoginInfo.cs
  5. 12 0
      TEAMModelOS.Model/Common/Models/RoleUser.cs
  6. 2 2
      TEAMModelOS.Model/Syllabus/Models/TeachingResource.cs
  7. 4 0
      TEAMModelOS.Model/Syllabus/Models/KnowledgePoint.cs
  8. 1 1
      TEAMModelOS.Model/Syllabus/Models/SyllabusResource.cs
  9. 4 4
      TEAMModelOS.Model/Syllabus/Models/Textbook.cs
  10. 0 21
      TEAMModelOS.Model/Syllabus/Models/TextbookStruct.cs
  11. 0 1
      TEAMModelOS.Model/TEAMModelOS.Model.csproj
  12. 7 1
      TEAMModelOS.SDK/Extension/JwtAuth/JwtAuthExtension.cs
  13. 10 4
      TEAMModelOS.SDK/Extension/JwtAuth/JwtHelper/JwtHelper.cs
  14. 173 0
      TEAMModelOS.SDK/Extension/JwtAuth/JwtHelper/RsaHelper.cs
  15. 1 1
      TEAMModelOS.SDK/Extension/JwtAuth/Models/JwtSetting.cs
  16. 15 0
      TEAMModelOS.SDK/Extension/JwtAuth/Models/JwtTokenOptions.cs
  17. 185 0
      TEAMModelOS.Service/Common/Implements/LoginInfoService.cs
  18. 13 0
      TEAMModelOS.Service/Common/Interfaces/ILoginInfoService.cs
  19. 0 1
      TEAMModelOS.Service/TEAMModelOS.Service.csproj
  20. 1 1
      TEAMModelOS/ClientApp/app.js
  21. BIN
      TEAMModelOS/ClientApp/assets/banner.jpg
  22. BIN
      TEAMModelOS/ClientApp/assets/tmd_logo.png
  23. 1 1
      TEAMModelOS/ClientApp/common/Ztree.vue
  24. 1 1
      TEAMModelOS/ClientApp/components/BaseContent.vue
  25. 1 1
      TEAMModelOS/ClientApp/components/SAchooseattention.vue
  26. 4 0
      TEAMModelOS/ClientApp/components/app-root.vue
  27. 174 0
      TEAMModelOS/ClientApp/components/graph/bar.vue
  28. 156 0
      TEAMModelOS/ClientApp/components/graph/classline.vue
  29. 89 0
      TEAMModelOS/ClientApp/components/graph/legendPie.vue
  30. 124 0
      TEAMModelOS/ClientApp/components/graph/nestingPie.vue
  31. 99 0
      TEAMModelOS/ClientApp/components/graph/ringPie.vue
  32. 57 0
      TEAMModelOS/ClientApp/css/site.css
  33. 8 2
      TEAMModelOS/ClientApp/router/routes.js
  34. 3 2
      TEAMModelOS/ClientApp/utils/http.js
  35. 0 0
      TEAMModelOS/ClientApp/utils/public.js
  36. 62 2
      TEAMModelOS/ClientApp/view/index.vue
  37. 197 0
      TEAMModelOS/ClientApp/view/smart-dashboard/smart-dashboard.less
  38. 501 0
      TEAMModelOS/ClientApp/view/smart-dashboard/smart-dashboard.vue
  39. 149 0
      TEAMModelOS/Controllers/Common/LoginController.cs
  40. 9 0
      TEAMModelOS/Controllers/Syllabus/SyllabusController.cs
  41. 5 1
      TEAMModelOS/Startup.cs
  42. 2 0
      TEAMModelOS/package.json
  43. 27 0
      TEAMModelOS/private.pem
  44. 9 0
      TEAMModelOS/public.pem
  45. 2 1
      TEAMModelOS/webpack.config.js

+ 12 - 0
TEAMModelOS.Model/Common/Dtos/LoginResult.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.Model.Common.Dtos
+{
+    public class LoginResult
+    {
+        public string JwtToken { get; set; }
+        public bool CheckTicket { get; set; }
+    }
+}

+ 18 - 0
TEAMModelOS.Model/Common/Dtos/TeamModelIdInfo.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.Model.Common.Dtos
+{
+    /// <summary>
+    /// 对接醍摩豆ID封装信息
+    /// </summary>
+    public class TeamModelIdInfo
+    {
+        public string name { get; set; }
+        public string cellphone { get; set; }
+        public string countryCode { get; set; }
+        public string id { get; set; }
+        public string[] iesStation { get; set; }
+    }
+}

+ 22 - 0
TEAMModelOS.Model/Common/Dtos/TicketInfo.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.Model.Common.Dtos
+{
+    /// <summary>
+    /// 前端返回获取的醍摩豆ID登录基本信息及ticket
+    /// </summary>
+    public class TicketInfo
+    {
+        //[Required(ErrorMessage = "{0} 必须填写")]
+        public string Ticket { get; set; }
+        //[Required(ErrorMessage = "{0} 必须填写")]
+        public string Name { get; set; }
+        //[Required(ErrorMessage = "{0} 必须填写")]
+        public string Sign { get; set; }
+        // [Required(ErrorMessage = "{0} 必须填写")]
+        public string TeamModelId { get; set; }
+        public string Token { get; set; }
+    }
+}

+ 32 - 0
TEAMModelOS.Model/Common/Models/LoginInfo.cs

@@ -0,0 +1,32 @@
+using Microsoft.WindowsAzure.Storage.Table;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.Model.Common.Models
+{
+    /// <summary>
+    /// 登录信息
+    /// </summary>
+    public class LoginInfo : TableEntity
+    {
+        public string TeamModelId { get; set; }
+        public string Phone { get; set; }
+        public string Ticket { get; set; }
+        public string Name { get; set; }
+        public string Token { get; set; }
+        public string CountryCode { get; set; }
+        /// <summary>
+        /// 登录时间
+        /// </summary>
+        public long LoginTime { get; set; }
+        /// <summary>
+        /// 超时时间
+        /// </summary>
+        public long Timeout { get; set; }
+        /// <summary>
+        /// 到期时间
+        /// </summary>
+        public long Expires { get; set; }
+    }
+}

+ 12 - 0
TEAMModelOS.Model/Common/Models/RoleUser.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.Model.Common.Models
+{
+    public class RoleUser
+    {
+        public string Name { get; set; }
+        public string Phone { get; set; }
+    }
+}

+ 2 - 2
TEAMModelOS.Model/Syllabus/Models/TeachingResource.cs

@@ -5,9 +5,9 @@ using System.Text;
 namespace TEAMModelOS.Model.Syllabus.Models
 {
     /// <summary>
-    /// 教学资源
+    /// 课件  教学资源
     /// </summary>
-    public class TeachingResource
+    public class Courseware
     {
         public string Id { get; set; }
         public string SyResId { get; set; }

+ 4 - 0
TEAMModelOS.Model/Syllabus/Models/KnowledgePoint.cs

@@ -13,5 +13,9 @@ namespace TEAMModelOS.Model.Syllabus.Models
         public string Id { get; set; }
         public string Pid { get; set; }
         public string Name { get; set; }
+        /// <summary>
+        /// 语言版本
+        /// </summary>
+        public string Lang { get; set; }
     }
 }

+ 1 - 1
TEAMModelOS.Model/Syllabus/Models/SyllabusResource.cs

@@ -8,7 +8,7 @@ namespace TEAMModelOS.Model.Syllabus.Models
     /// <summary>
     /// 课纲资源
     /// </summary>
-    public class SyllabusResource : TableEntity
+    public class Resource : TableEntity
     {
         public string Id { get; set; }
         public string Name { get; set; }

+ 4 - 4
TEAMModelOS.Model/Syllabus/Models/Textbook.cs

@@ -23,13 +23,13 @@ namespace TEAMModelOS.Model.Syllabus.Models
         /// </summary>
         public string Edition { get; set; }
         /// <summary>
-        /// 名称
+        /// 册别
         /// </summary>
-        public string Name { get; set; }
+        public string Term { get; set; }
         /// <summary>
-        /// 年级
+        /// 名称 默认 学段+科目+版本+册别   
         /// </summary>
-        public string Grade { get; set; }
+        public string Name { get; set; }
         /// <summary>
         /// 备注
         /// </summary>

+ 0 - 21
TEAMModelOS.Model/Syllabus/Models/TextbookStruct.cs

@@ -1,21 +0,0 @@
-using MessagePack;
-using Microsoft.WindowsAzure.Storage.Table;
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace TEAMModelOS.Model.Syllabus.Models
-{
-    [MessagePackObject(keyAsPropertyName: true)]
-    public class TextbookStruct: TableEntity
-    {
-        /// <summary>
-        /// 1.学段,2.学科,3.版本,4.册别
-        /// </summary>
-        public string Type { get; set; }
-        public string PCode { get; set; }
-        public string Code { get; set; }
-        public string Name { get; set; }
-        public bool Template { get; set; }
-    }
-}

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

@@ -6,7 +6,6 @@
 
   <ItemGroup>
     <Folder Include="Analysis\Dtos\" />
-    <Folder Include="Common\" />
   </ItemGroup>
   <ItemGroup>
     <PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />

+ 7 - 1
TEAMModelOS.SDK/Extension/JwtAuth/JwtAuthExtension.cs

@@ -8,6 +8,8 @@ using Microsoft.IdentityModel.Tokens;
 using System;
 using System.Text;
 using System.Threading.Tasks;
+using TEAMModelOS.SDK.Extension.JwtAuth.JwtHelper;
+using TEAMModelOS.SDK.Context.Configuration;
 
 namespace TEAMModelOS.SDK.Extension.JwtAuth
 {
@@ -16,7 +18,11 @@ namespace TEAMModelOS.SDK.Extension.JwtAuth
         public static void JwtAuth(this IServiceCollection services , IConfigurationSection configuration)
         {
             services.Configure<JwtSetting>(configuration);
-            var creds = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["SecurityKey"]));
+            // var creds = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["SecurityKey"]), SecurityAlgorithms.RsaSha256Signature);
+            //var creds = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["SecurityKey"]));
+            string path = BaseConfigModel.ContentRootPath;
+            RsaSecurityKey creds = new RsaSecurityKey(RsaHelper.LoadCertificateFile(path + "/private.pem"));
+            //RsaSecurityKey creds = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["SecurityKey"])), SecurityAlgorithms.RsaSha256Signature);
             // 令牌验证参数
             var tokenValidationParameters = new TokenValidationParameters
             {

+ 10 - 4
TEAMModelOS.SDK/Extension/JwtAuth/JwtHelper/JwtHelper.cs

@@ -8,6 +8,8 @@ using System.Linq;
 using System.Security.Claims;
 using System.Text;
 using TEAMModelOS.SDK.Helper.Common.DateTimeHelper;
+using TEAMModelOS.SDK.Context.Configuration;
+using System.Security.Cryptography;
 
 namespace TEAMModelOS.SDK.Extension.JwtAuth.JwtHelper
 {
@@ -40,15 +42,16 @@ namespace TEAMModelOS.SDK.Extension.JwtAuth.JwtHelper
             claims.Add(new Claim(JwtClaimTypes.Issuer, setting.Issuer));
             claims.Add(new Claim(JwtClaimTypes.Scope, claimModel.Scope));
             claims.AddRange(claimModel.Roles.ToArray().Select(s=>new Claim(JwtClaimTypes.Role,s)));
-            var creds = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(setting.SecurityKey)), SecurityAlgorithms.HmacSha512);
+            string path = BaseConfigModel.ContentRootPath;
+            RSACryptoServiceProvider provider = RsaHelper.LoadCertificateFile(path + "/private.pem");
+            RsaSecurityKey rsaSecurity = new RsaSecurityKey(provider);
+            var creds =new SigningCredentials(rsaSecurity, SecurityAlgorithms.RsaSha256);
+
             var jwt = new JwtSecurityToken(
                 claims:claims,
                 signingCredentials:creds
                 );
             var jwtHandler = new JwtSecurityTokenHandler();
-              jwtHandler.WriteToken(jwt);
-
-
             return new JwtResponse {
                 access_token = jwtHandler.WriteToken(jwt),
                 scope = claimModel.Scope
@@ -61,6 +64,9 @@ namespace TEAMModelOS.SDK.Extension.JwtAuth.JwtHelper
         /// <returns></returns>
         public static ClaimModel SerializeJWT(string jwtStr)
         {
+
+            ///https://www.cnblogs.com/JacZhu/p/6837676.html#Update2.0  刷新     用户的 Token 在过期时间之内根本无法手动设置失效,随之而来的还有重放攻击等等问题
+
             var jwtHandler = new JwtSecurityTokenHandler();
             JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
             ClaimModel claimModel = new ClaimModel();

+ 173 - 0
TEAMModelOS.SDK/Extension/JwtAuth/JwtHelper/RsaHelper.cs

@@ -0,0 +1,173 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+using TEAMModelOS.SDK.Helper.Common.JsonHelper;
+
+namespace TEAMModelOS.SDK.Extension.JwtAuth.JwtHelper
+{
+    public static class RsaHelper
+    {
+        public static string RSASign(string data, string privateKeyPem)
+        {
+            RSACryptoServiceProvider rsaCsp = LoadCertificateFile(privateKeyPem);
+            byte[] dataBytes = Encoding.UTF8.GetBytes(data);
+            byte[] signatureBytes = rsaCsp.SignData(dataBytes, "SHA1");
+            return Convert.ToBase64String(signatureBytes);
+        }
+       
+        private static byte[] GetPem(string type, byte[] data)
+        {
+            string pem = Encoding.UTF8.GetString(data);
+            string header = String.Format("-----BEGIN {0}-----\\n", type);
+            string footer = String.Format("-----END {0}-----", type);
+            int start = pem.IndexOf(header) + header.Length;
+            int end = pem.IndexOf(footer, start);
+            string base64 = pem.Substring(start, (end - start));
+            return Convert.FromBase64String(base64);
+        }
+        public static string  LoadCertificateFileToSting (string filename) {
+            FileStream fs = System.IO.File.OpenRead(filename);
+            byte[] data = new byte[fs.Length];
+            byte[] res = null;
+            fs.Read(data, 0, data.Length);
+            if (data[0] != 0x30)
+            {
+                res = GetPem("RSA PRIVATE KEY", data);
+            }
+            return res.ToJson();
+        }
+        public static RSACryptoServiceProvider LoadCertificateFile(string filename)
+        {
+            FileStream fs = System.IO.File.OpenRead(filename);
+            byte[] data = new byte[fs.Length];
+            byte[] res = null;
+            fs.Read(data, 0, data.Length);
+            if (data[0] != 0x30)
+            {
+                res = GetPem("RSA PRIVATE KEY", data);
+            }
+            string ss = res.ToJson();
+            RSACryptoServiceProvider rsa = DecodeRSAPrivateKey(res);
+            return rsa;
+        }
+
+        private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
+        {
+            byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
+
+            // --------- Set up stream to decode the asn.1 encoded RSA private key ------
+            MemoryStream mem = new MemoryStream(privkey);
+            BinaryReader binr = new BinaryReader(mem);  //wrap Memory Stream with BinaryReader for easy reading
+            byte bt = 0;
+            ushort twobytes = 0;
+            int elems = 0;
+            try
+            {
+                twobytes = binr.ReadUInt16();
+                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
+                    binr.ReadByte();    //advance 1 byte
+                else if (twobytes == 0x8230)
+                    binr.ReadInt16();    //advance 2 bytes
+                else
+                    return null;
+
+                twobytes = binr.ReadUInt16();
+                if (twobytes != 0x0102) //version number
+                    return null;
+                bt = binr.ReadByte();
+                if (bt != 0x00)
+                    return null;
+
+
+                //------ all private key components are Integer sequences ----
+                elems = GetIntegerSize(binr);
+                MODULUS = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                E = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                D = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                P = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                Q = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                DP = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                DQ = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                IQ = binr.ReadBytes(elems);
+                // ------- create RSACryptoServiceProvider instance and initialize with public key -----
+                CspParameters CspParameters = new CspParameters
+                {
+                    Flags = CspProviderFlags.UseMachineKeyStore
+                };
+                RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1024, CspParameters);
+                RSAParameters RSAparams = new RSAParameters
+                {
+                    Modulus = MODULUS,
+                    Exponent = E,
+                    D = D,
+                    P = P,
+                    Q = Q,
+                    DP = DP,
+                    DQ = DQ,
+                    InverseQ = IQ
+                };
+                RSA.ImportParameters(RSAparams);
+                return RSA;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception("",ex);
+            }
+            finally
+            {
+                binr.Close();
+            }
+        }
+
+        private static int GetIntegerSize(BinaryReader binr)
+        {
+            byte bt = 0;
+            byte lowbyte = 0x00;
+            byte highbyte = 0x00;
+            int count = 0;
+            bt = binr.ReadByte();
+            if (bt != 0x02)        //expect integer
+                return 0;
+            bt = binr.ReadByte();
+
+            if (bt == 0x81)
+                count = binr.ReadByte();    // data size in next byte
+            else
+                if (bt == 0x82)
+            {
+                highbyte = binr.ReadByte();    // data size in next 2 bytes
+                lowbyte = binr.ReadByte();
+                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
+                count = BitConverter.ToInt32(modint, 0);
+            }
+            else
+            {
+                count = bt;        // we already have the data size
+            }
+
+            while (binr.ReadByte() == 0x00)
+            {    //remove high order zeros in data
+                count -= 1;
+            }
+            binr.BaseStream.Seek(-1, SeekOrigin.Current);        //last ReadByte wasn't a removed zero, so back up a byte
+            return count;
+        }
+    }
+}

+ 1 - 1
TEAMModelOS.SDK/Extension/JwtAuth/Models/JwtSetting.cs

@@ -25,6 +25,6 @@ namespace TEAMModelOS.SDK.Extension.JwtAuth.Models
         /// <summary>
         /// JWT Secret Key
         /// </summary>
-        public string SecurityKey { get; set; }
+        //public string SecurityKey { get; set; }
     }
 }

+ 15 - 0
TEAMModelOS.SDK/Extension/JwtAuth/Models/JwtTokenOptions.cs

@@ -0,0 +1,15 @@
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.SDK.Extension.JwtAuth.Models
+{
+     public  class JwtTokenOptions
+    {
+        public string Audience { get; set; }
+        public RsaSecurityKey Key { get; set; }
+        public SigningCredentials Credentials { get; set; }
+        public string Issuer { get; set; }
+    }
+}

+ 185 - 0
TEAMModelOS.Service/Common/Implements/LoginInfoService.cs

@@ -0,0 +1,185 @@
+using IdentityModel;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Text;
+using System.Threading.Tasks;
+using TEAMModelOS.Model.Common.Dtos;
+using TEAMModelOS.Model.Common.Models;
+using TEAMModelOS.SDK.Context.Configuration;
+using TEAMModelOS.SDK.Context.Constant.Common;
+using TEAMModelOS.SDK.Context.Exception;
+using TEAMModelOS.SDK.Extension.DataResult.JsonRpcRequest;
+using TEAMModelOS.SDK.Extension.DataResult.JsonRpcResponse;
+using TEAMModelOS.SDK.Extension.HttpClient.Implements;
+using TEAMModelOS.SDK.Extension.JwtAuth.JwtHelper;
+using TEAMModelOS.SDK.Extension.JwtAuth.Models;
+using TEAMModelOS.SDK.Helper.Common.DateTimeHelper;
+using TEAMModelOS.SDK.Helper.Common.JsonHelper;
+using TEAMModelOS.SDK.Helper.Network.HttpHelper;
+using TEAMModelOS.SDK.Helper.Security.BCryptHelper;
+using TEAMModelOS.SDK.Module.AzureTable.Interfaces;
+using TEAMModelOS.Service.Common.Interfaces;
+
+namespace TEAMModelOS.Service.Common.Implements
+{
+    public class LoginInfoService : ILoginInfoService
+    {
+        private IAzureTableDBRepository _repository;
+        private IOptions<JwtSetting> _options;
+        private IHttpContextAccessor _httpContextAccessor;
+        private HttpClientService _httpClientService;
+        public LoginInfoService(IAzureTableDBRepository repository, IOptions<JwtSetting> options, IHttpContextAccessor httpContextAccessor  , HttpClientService httpClientService)
+        {
+            _httpContextAccessor = httpContextAccessor;
+            _options = options;
+            _repository = repository;
+            _httpClientService = httpClientService;
+        }
+
+        public async Task<LoginResult> CheckLoginAsync(TicketInfo ticketInfo) {
+
+            string jtoken = HttpContextHelper.GetValueInHttp(_httpContextAccessor.HttpContext.Request, Constants.AUTHORIZATION);
+            if (string.IsNullOrEmpty(ticketInfo.Token))
+            {
+                string code = BCryptHelper.Ecrypt(ticketInfo.Ticket + ticketInfo.TeamModelId);
+                bool f = BCryptHelper.Verify(ticketInfo.Ticket + ticketInfo.TeamModelId, ticketInfo.Sign);
+                LoginResult result = new LoginResult();
+                LoginInfo login = _repository.FindOneByKey<LoginInfo>("Ticket", ticketInfo.Ticket).Result;
+                if (login != null && !string.IsNullOrEmpty(login.Token))
+                {
+                    result.CheckTicket = true;
+                    string token = CreateJwtToken(login);
+                    result.JwtToken = token;
+                    login.Token = token;
+                    await _repository.Update<LoginInfo>(login);
+                    return result;
+                }
+                Dictionary<string, string> dict = new Dictionary<string, string>
+                {
+                    { Constants.AUTHORIZATION, BaseConfigModel.Configuration["HaBookAuth:UserInfoKey"] }
+                };
+                JosnRPCRequest<Dictionary<string, object>> request = new JosnRPCRequest<Dictionary<string, object>>
+                {
+                    method = "UserInfo"
+                };
+                Dictionary<string, object> ticket = new Dictionary<string, object>
+                {
+                    { "ticket", ticketInfo.Ticket }
+                };
+                request.@params = ticket;
+                string data = MessagePackHelper.ObjectToJson(request);
+                string jsonStr = _httpClientService.HttpPost(BaseConfigModel.Configuration["HaBookAuth:AccountUrl"], data, dict, Constants.CONTENT_TYPE_JSON, 1000, Encoding.UTF8);
+                if (!string.IsNullOrEmpty(jsonStr))
+                {
+                    JosnRPCResponse<TeamModelIdInfo> response = MessagePackHelper.JsonToObject<JosnRPCResponse<TeamModelIdInfo>>(jsonStr);
+                    if (response.error == null && response != null)
+                    {
+
+
+                        result.CheckTicket = true;
+                        LoginInfo loginInfo = new LoginInfo
+                        {
+                            PartitionKey = response.result.cellphone,
+                            Phone = response.result.cellphone,
+                            RowKey = Guid.NewGuid().ToString(),
+                            TeamModelId = response.result.id,
+                            Name = response.result.name,
+                            Ticket = ticketInfo.Ticket,
+                            CountryCode = response.result.countryCode
+                        };
+                        string jwtToken = CreateJwtToken(loginInfo);
+                        loginInfo.Token = jwtToken;
+                        result.JwtToken = jwtToken;
+                        SaveLoginInfoAsync(loginInfo);
+                        return result;
+                    }
+                    else
+                    {
+                        result.CheckTicket = false;
+                        return result;
+                    }
+                }
+                else
+                {
+                    result.CheckTicket = false;
+                    return result;
+                }
+            }
+            else
+            {
+                ClaimModel claimModel = JwtHelper.SerializeJWT(ticketInfo.Token);
+
+                foreach (Claim claim in claimModel.Claims)
+                {
+                    if ("exp".Equals(claim.Type))
+                    {
+                        var dateTime = DateTimeHelper.ConvertToTimeStamp10(DateTime.Now);
+                        var exp = claim.Value;
+                        if (dateTime > long.Parse(exp))
+                        {
+                            throw new BizException(401, "Unauthorized");
+                        }
+                    }
+                }
+                Dictionary<string, Object> msp = new Dictionary<string, object>
+                {
+                    { "Token", ticketInfo.Token }
+                };
+                LoginInfo loginInfo = _repository.FindOneByDict<LoginInfo>(msp).Result;
+                if (loginInfo != null && !string.IsNullOrEmpty(loginInfo.Token))
+                {
+                    return new LoginResult { JwtToken = loginInfo.Token, CheckTicket = true };
+                }
+                else
+                {
+                    throw new BizException(401, "Unauthorized");
+                }
+            }
+        }
+        public string CreateJwtToken(LoginInfo loginInfo)
+        {
+            //List<ContestRoleUser> roots = BaseConfigModel.GetAppSettings<List<ContestRoleUser>>("RoleUser:Root");
+            //List<ContestRoleUser> admins = BaseConfigModel.GetAppSettings<List<ContestRoleUser>>("RoleUser:Admin");
+
+            List<RoleUser> roots = BaseConfigModel.Configuration.GetSection("RoleUser:Root").Get<List<RoleUser>>();
+            List<RoleUser> admins = BaseConfigModel.Configuration.GetSection("RoleUser:Admin").Get<List<RoleUser>>();
+            string role = "";
+            foreach (var roleUser in roots)
+            {
+                if (roleUser.Phone.Equals(loginInfo.CountryCode + loginInfo.Phone))
+                {
+                    role = role + "Root,";
+                    break;
+                }
+            }
+            foreach (var roleUser in admins)
+            {
+                if (roleUser.Phone.Equals(loginInfo.CountryCode + loginInfo.Phone))
+                {
+                    role = role + "Admin,";
+                    break;
+                }
+            }
+            role = role + "User";
+            ClaimModel model = new ClaimModel
+            {
+                Scope = "WebApp"
+            };
+            model.Claims.Add(new Claim(JwtClaimTypes.Name, loginInfo.Name));
+            model.Claims.Add(new Claim(JwtClaimTypes.Id, loginInfo.TeamModelId));
+            model.Claims.Add(new Claim(JwtClaimTypes.PhoneNumber, loginInfo.Phone));
+            model.Roles.Add(role);
+            JwtResponse jwtResponse = JwtHelper.IssueJWT(model, _options.Value);
+            return jwtResponse.access_token;
+        }
+
+        public LoginInfo SaveLoginInfoAsync(LoginInfo loginInfo)
+        {
+            return _repository.Save<LoginInfo>(loginInfo).Result;
+        }
+    }
+}

+ 13 - 0
TEAMModelOS.Service/Common/Interfaces/ILoginInfoService.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TEAMModelOS.Model.Common.Dtos;
+
+namespace TEAMModelOS.Service.Common.Interfaces
+{
+    public interface ILoginInfoService :IBusinessService
+    {
+        Task<LoginResult> CheckLoginAsync(TicketInfo loginInfo);
+    }
+}

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

@@ -7,7 +7,6 @@
   <ItemGroup>
     <Folder Include="Analysis\Implements\" />
     <Folder Include="Analysis\Interfaces\" />
-    <Folder Include="Common\Implements\" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
TEAMModelOS/ClientApp/app.js

@@ -8,7 +8,7 @@ import { FontAwesomeIcon } from './icons'
 import iView from 'iview';
 import 'iview/dist/styles/iview.css';
 import i18n from '@/locale';
-import commons from "@/common/public.js"
+import commons from "@/utils/public.js"
 
 //新添加的
 import vuescroll from 'vue-scroll'

BIN
TEAMModelOS/ClientApp/assets/banner.jpg


BIN
TEAMModelOS/ClientApp/assets/tmd_logo.png


+ 1 - 1
TEAMModelOS/ClientApp/common/Ztree.vue

@@ -178,7 +178,7 @@ export default {
           },
           children: [
             {
-              title: "第一章",
+              title: "第一章语文语文",
               order: 0,
               expand: true,
               children: [

+ 1 - 1
TEAMModelOS/ClientApp/components/BaseContent.vue

@@ -183,7 +183,7 @@
               }
           },
           handleUpload(){
-              this.uploadModal = true;
+            this.uploadModal = true;
           },
           handleUploadConfirm(){
 

+ 1 - 1
TEAMModelOS/ClientApp/components/SAchooseattention.vue

@@ -94,7 +94,7 @@
     },
   }
 </script>
-<style>
+<style scoped>
   .ivu-select-selection {
     background-color: #cfd4da !important;;
   }

+ 4 - 0
TEAMModelOS/ClientApp/components/app-root.vue

@@ -19,4 +19,8 @@
 </script>
 
 <style>
+#app, body, html{
+  width: 100%;
+  height: 100%;
+}
 </style>

+ 174 - 0
TEAMModelOS/ClientApp/components/graph/bar.vue

@@ -0,0 +1,174 @@
+<template>
+  <div id="myStudybar" style="width:89%;height:100%;margin-left: 1%;margin-top:0%;"></div>
+</template>
+
+<script>
+  export default {
+    data () {
+      return {
+        modedata:{},
+        name: '',
+        ctitle:'',
+        schoolnames: {},
+      }
+    },
+    computed: {
+//    ------------------len-----------------
+     
+    },
+    created(){
+
+    },
+    mounted(){
+    	this.init(this.mode,this.obj,'sokratestotal','苏格拉底');
+    },
+    methods: {
+      drawLine(datas,datai,titles){
+        let _this = this
+        // 基于准备好的dom,初始化echarts实例
+        let myChart = this.$echarts.init(document.getElementById('myStudybar'));
+        // 绘制图表
+        myChart.setOption({
+          title : {
+            text:titles,
+            x:'left',
+            top: '1',
+            textStyle:{
+              color:'#fff',
+              fontSize:16,
+            }
+          },
+          color: ['#3398DB'],
+            tooltip : {
+              trigger: 'axis',
+              axisPointer : {            // 坐标轴指示器,坐标轴触发有效
+                type : 'shadow'        // 默认为直线,可选为:'line' | 'shadow'
+              },
+              formatter: function (params, ticket, callback) {
+                let rez = '';
+                params.forEach(item => {
+                  rez = '<p>'+ _this.schoolnames[item.name] +'<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + item.color.colorStops[0].color + '"></span>' + item.value + '</p>';
+                });
+                return rez;
+              }
+            },
+            grid: {
+              left: '0',
+              right: '5',
+              top:"15%",
+              bottom: '1',
+              containLabel: true
+            },
+            xAxis : [
+              {
+                type : 'category',
+                data : datai,
+                axisTick: {
+                  alignWithLabel: true
+                }
+              }
+            ],
+            yAxis : [
+              {
+                type : 'value',
+                color:'#3e3e3e',
+                splitLine: {
+                  lineStyle: {
+                      color: '#9E9E9E'
+                  }
+                },
+              }
+            ],
+            textStyle:{
+              color:'#9E9E9E',
+            },
+            series : [
+              {
+                name:'',
+                color:['rgb(0, 220, 254,0.5),rgb(0, 254, 224,0.5)'],
+                type:'bar',
+                barWidth: '60%',
+                data:datas,
+                itemStyle: {
+                  normal: {  
+                    // 渐变色
+                    color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                      offset: 0,
+                      color: "#0093AE" // 0% 处的颜色
+                    },{
+                      offset: 1,
+                      color: "#00BBA1" // 100% 处的颜色
+                    }], false)
+                    // color: function (params){
+                    //   // console.log(datas,235);
+                    //   return (params['company']==datas.xData[param.dataIndex])? 'rgb(204,0,51)': 'rgb(42,170,227)';
+                    // }
+                  }
+                }
+              }
+            ]
+        });
+      },
+      init(code,arr,name,c){
+      	let a = [] // Datas
+      	let b = [] // title
+        for(let i in arr){
+          if(this.schoolcode == arr[i].schoolcode.toUpperCase()){
+            a.push({
+                value: arr[i].dashboard.smartclasstable.schoolmessage[name],
+                itemStyle: {
+                    normal: {
+                      // 渐变色
+                      color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                        offset: 0,
+                        color: "#00DBFF" // 0% 处的颜色
+                      },{
+                        offset: 1,
+                        color: "#00FFDF" // 100% 处的颜色
+                      }], false)
+                    },
+                },
+            });
+          } else {
+            a.push(arr[i].dashboard.smartclasstable.schoolmessage[name]);
+          }
+          this.schoolnames[arr[i].schoolcode] = arr[i].schoolname
+        	b.push(arr[i].schoolcode)
+        }
+        this.drawLine(a,b,c)
+      },
+    },
+    watch:{
+      numdata(a){
+      	if(a.num == 1){
+      		this.name = 'curriculum',
+          this.ctitle='课程总数'
+      	}else if(a.num == 2){
+          this.name = 'electronicalnote',
+          this.ctitle='电子笔记数'
+      	}else if(a.num == 3){
+          this.name = 'uploadmovie',
+          this.ctitle='上传影片数'
+      	}else if(a.num == 4){
+          this.name = 'production',
+          this.ctitle='作业作品数'
+      	}else if(a.num == 5){
+          this.name = 'overturnclass',
+          this.ctitle='翻转课堂数'
+        }else if(a.num == 7){
+          this.name = 'sokratestotal',
+          this.ctitle='苏格拉底'          
+        }
+        if(a.num != 6) this.init(this.mode,this.obj,this.name,this.ctitle); // 长条图没有学习历程资料
+        
+      },
+      mode(a,b){
+        this.init(a,this.obj,this.name,this.ctitle)
+      }
+    },
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 156 - 0
TEAMModelOS/ClientApp/components/graph/classline.vue

@@ -0,0 +1,156 @@
+<template>
+    <div :id="id" style="height: 100%;width:100%;"></div>
+</template>
+
+<script>
+export default {
+    data () {
+        return {}
+    },
+    props:{
+        id:{
+            type: String
+        }
+    },
+    mounted(){
+        this.drawLine();
+    },
+    methods:{
+        drawLine(){
+            let _this = this
+            // 基于准备好的dom,初始化echarts实例
+            let myChart = this.$echarts.init(document.getElementById(this.id));
+            let arrlabel = ["","00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00", "24:00", ""];
+            let arrTotal = ["0","0", "0", "0", "0", "0", "10", "20", "30", "25", "35", "40", "60", "10", "0", "20", "30", "60", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"];
+            myChart.setOption({
+                backgroundColor: '#343a4073',
+                tooltip: {
+                    trigger: 'axis',
+                    borderRadius: 0,
+                    // backgroundColor:'#7AD1A8',
+                    // formatter: function(pm) {
+                    //     var param = pm[0];
+                    //     var pht = '<span style="display:inline-block;margin-right:5px;width:10px;height:10px;background-color:rgba(166,154,228);"></span>';
+                    //     console.log(param.marker);
+                    //     var prm = "星期" + "日一二三四五六 ".charAt(new Date(param.name).getDay());
+                    //     return param.name + ":&nbsp;&nbsp;" + prm + "<br>" +
+                    //         pht + param.seriesName + ":&nbsp;&nbsp;" + param.value + "&nbsp;&nbsp;|&nbsp;&nbsp;80.33ttt%";
+                    // },
+                    //   axisPointer: { // 坐标轴指示器,坐标轴触发有效
+                    //         type: 'none' // 默认为直线,可选为:'line' | 'shadow'
+                    //     },
+                },
+                grid: {
+                    left: '0',
+                    right: '0',
+                    bottom: '0',
+                    top: '0',
+                    containLabel: true,
+                },
+                xAxis: [{
+                    axisTick: {
+                        show: false,
+                    },
+                    type: 'category',
+                    axisLabel: {
+                        // show: false,
+                        margin: 10,
+                        textStyle: {
+                            fontSize: 12,
+                            color: '#fff'
+                        }
+                    },
+                    boundaryGap: false,
+                    splitLine: {
+                        lineStyle: {
+                            color: '#6c757d',
+                        },
+                        show: true
+                    },
+                    axisLine: {
+                        lineStyle: {
+                            color: '#6c757d',
+                            width: 1,
+                        }
+                    },
+                    data: arrlabel
+                }],
+                yAxis: [{
+                    position: 'right',
+                    type: 'value',
+                    // name: '单 位(%)',
+                    axisTick: {
+                        show: false
+                    },
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            color: '#6c757d'
+                        }
+                    },
+                    axisLabel: {
+                        margin: 10,
+                        inside: true,
+                        textStyle: {
+                            fontSize: 14,
+                            color:'transparent'
+                        }
+                    },
+                    splitLine: {
+                        show: true,
+                        lineStyle: {
+                            color: '#6c757d'
+                        }
+                    }
+                }],
+                series: [{
+                    // name: '实名率',
+                    type: 'line',
+                    // symbol: 'circle',
+                    //symbolSize: 5,
+                    // showSymbol: false,
+                    // markPoint: { // markLine 也是同理
+                    //   data: [{
+                    //     coord: [0,1,2,3,4], // 其中 5 表示 xAxis.data[5],即 '33' 这个元素。
+                    //     // coord: ['5', 33.4] // 其中 '5' 表示 xAxis.data中的 '5' 这个元素。
+                    //     // 注意,使用这种方式时,xAxis.data 不能写成 [number, number, ...]
+                    //     // 而只能写成 [string, string, ...]
+                    //   }]
+                    // },
+                    lineStyle: {
+                        normal: {
+                            width: 2
+                        }
+                    },
+                    areaStyle: {
+                        normal: {
+                            color: 'rgba(166,154,228,0.40)',
+                            //  shadowColor: 'rgba(0, 0, 0, 0.1)',
+                            //shadowBlur: 10
+                        }
+                    },
+                    itemStyle: {
+                        normal: {
+                            areaStyle: {
+                                type: 'default'
+                            },
+                            color: 'rgba(166,154,228)',
+                        },
+                        emphasis: {
+                            // color: 'rgb(0,196,132)',
+                            borderColor: 'rgba(0,196,132,0.2)',
+                            extraCssText: 'box-shadow: 8px 8px 8px rgba(0, 0, 0, 1);',
+                            // borderWidth: 10
+                        }
+                    },
+                    data: arrTotal
+                }]
+            });
+        }
+    }
+}
+
+</script>
+
+<style>
+</style>

+ 89 - 0
TEAMModelOS/ClientApp/components/graph/legendPie.vue

@@ -0,0 +1,89 @@
+<template>
+    <div :id="id" style="height: 100%;width:100%;"></div>
+</template>
+
+<script>
+export default {
+    data () {
+        return {}
+    },
+    props:{
+        id:{
+            type: String
+        }
+    },
+    mounted(){
+        this.drawLine();
+    },
+    methods:{
+        drawLine(){
+            let _this = this
+            // 基于准备好的dom,初始化echarts实例
+            let myChart = this.$echarts.init(document.getElementById(this.id));
+            myChart.setOption({
+                tooltip: {
+                    trigger: 'item',
+                    formatter: "{a} <br/>{b}: {c} ({d}%)"
+                },
+                series: [
+                    {
+                        name:'访问来源',
+                        type:'pie',
+                        radius: ['50%', '70%'],
+                        avoidLabelOverlap: false,
+                        label: {
+                            normal: {
+                                show: false,
+                                position: 'center',
+                                formatter: '{text|{b}}\n{value|{d}%}',
+                                rich: {
+                                    text: {
+                                        color: "#fefefe",
+                                        // fontSize: 14,
+                                        align: 'center',
+                                        verticalAlign: 'middle',
+                                        padding: 5
+                                    },
+                                    value: {
+                                        color: "#fefefe",
+                                        // fontSize: 24,
+                                        align: 'center',
+                                        verticalAlign: 'middle',
+                                    },
+                                }
+                            },
+                            emphasis: {
+                                show: true,
+                                textStyle: {
+                                    fontSize: 46,
+                                }
+                            }
+                        },
+                        labelLine: {
+                            normal: {
+                                show: false
+                            }
+                        },
+                        data:[
+                            {value:335, name:'直接访问'},
+                            {value:310, name:'邮件营销'},
+                            {value:234, name:'联盟广告'},
+                            {value:135, name:'视频广告'},
+                            {value:1548, name:'搜索引擎'}
+                        ]
+                    }
+                ]
+            });
+            myChart.dispatchAction({
+                type: 'highlight',
+                // seriesIndex: 1,
+                dataIndex: 2
+            });
+        }
+    }
+}
+
+</script>
+
+<style>
+</style>

+ 124 - 0
TEAMModelOS/ClientApp/components/graph/nestingPie.vue

@@ -0,0 +1,124 @@
+<template>
+    <div :id="id" style="height: 100%;width:100%;padding: 20px;"></div>
+</template>
+
+<script>
+export default {
+    data () {
+        return {}
+    },
+    props:{
+        id:{
+            type: String
+        }
+    },
+    mounted(){
+        this.drawLine();
+    },
+    methods:{
+        drawLine(){
+            let _this = this
+            // 基于准备好的dom,初始化echarts实例
+            let myChart = this.$echarts.init(document.getElementById(this.id));
+            myChart.setOption({
+                tooltip: {
+                    trigger: 'item',
+                    formatter: "{a} <br/>{b}: {c} ({d}%)"
+                },
+                // legend: {
+                //     orient: 'vertical',
+                //     x: 'left',
+                //     data:['直达','营销广告','搜索引擎','邮件营销','联盟广告','视频广告','百度','谷歌','必应','其他']
+                // },
+                series: [
+                    {
+                        name:'访问来源',
+                        type:'pie',
+                        selectedMode: 'single',
+                        radius: [0, '30%'],
+
+                        label: {
+                            normal: {
+                                position: 'inner'
+                            }
+                        },
+                        labelLine: {
+                            normal: {
+                                show: false
+                            }
+                        },
+                        data:[
+                            {value:335, name:'直达', selected:true},
+                            {value:679, name:'营销广告'},
+                            {value:1548, name:'搜索引擎'}
+                        ]
+                    },
+                    {
+                        name:'访问来源',
+                        type:'pie',
+                        radius: ['40%', '55%'],
+                        label: {
+                            normal: {
+                                formatter: '{a|{a}}{abg|}\n{hr|}\n  {b|{b}:}{c}  {per|{d}%}  ',
+                                backgroundColor: '#eee',
+                                borderColor: '#aaa',
+                                borderWidth: 1,
+                                borderRadius: 4,
+                                // shadowBlur:3,
+                                // shadowOffsetX: 2,
+                                // shadowOffsetY: 2,
+                                // shadowColor: '#999',
+                                // padding: [0, 7],
+                                rich: {
+                                    a: {
+                                        color: '#999',
+                                        lineHeight: 22,
+                                        align: 'center'
+                                    },
+                                    // abg: {
+                                    //     backgroundColor: '#333',
+                                    //     width: '100%',
+                                    //     align: 'right',
+                                    //     height: 22,
+                                    //     borderRadius: [4, 4, 0, 0]
+                                    // },
+                                    hr: {
+                                        borderColor: '#aaa',
+                                        width: '100%',
+                                        borderWidth: 0.5,
+                                        height: 0
+                                    },
+                                    b: {
+                                        fontSize: 16,
+                                        lineHeight: 33
+                                    },
+                                    per: {
+                                        color: '#eee',
+                                        backgroundColor: '#334455',
+                                        padding: [2, 4],
+                                        borderRadius: 2
+                                    }
+                                }
+                            }
+                        },
+                        data:[
+                            {value:335, name:'直达'},
+                            {value:310, name:'邮件营销'},
+                            {value:234, name:'联盟广告'},
+                            {value:135, name:'视频广告'},
+                            {value:1048, name:'百度'},
+                            {value:251, name:'谷歌'},
+                            {value:147, name:'必应'},
+                            {value:102, name:'其他'}
+                        ]
+                    }
+                ]         
+            });
+        }
+    }
+}
+
+</script>
+
+<style>
+</style>

+ 99 - 0
TEAMModelOS/ClientApp/components/graph/ringPie.vue

@@ -0,0 +1,99 @@
+<template>
+    <div :id="id" style="height: 100%;width:100%;"></div>
+</template>
+
+<script>
+export default {
+    data () {
+        return {}
+    },
+    props:{
+        id:{
+            type: String
+        },
+        mouseoverCallBack: {}, //[String] mouseover時emit的CallBack名
+    },
+    mounted(){
+        this.drawLine();
+    },
+    methods:{
+        drawLine(){
+            let _this = this
+            // 基于准备好的dom,初始化echarts实例
+            let myChart = this.$echarts.init(document.getElementById(this.id));
+            let data = [
+                {value:335, name:'直接访问'},
+                {value:310, name:'邮件营销'},
+                {value:234, name:'联盟广告'},
+                {value:135, name:'视频广告'},
+                {value:1548, name:'搜索引擎'}
+            ]
+            myChart.setOption({
+                tooltip: {
+                    trigger: 'item',
+                    formatter: "{a} <br/>{b}: {c} ({d}%)"
+                },
+                series: [
+                    {
+                        name:'访问来源',
+                        type:'pie',
+                        radius: ['50%', '70%'],
+                        avoidLabelOverlap: false,
+                        label: {
+                            normal: {
+                                show: false,
+                                position: 'center',
+                                formatter: '{text|{b}}\n{value|{d}%}',
+                                rich: {
+                                    text: {
+                                        color: "#fefefe",
+                                        // fontSize: 14,
+                                        align: 'center',
+                                        verticalAlign: 'middle',
+                                        padding: 5
+                                    },
+                                    value: {
+                                        color: "#fefefe",
+                                        // fontSize: 24,
+                                        align: 'center',
+                                        verticalAlign: 'middle',
+                                    },
+                                }
+                            },
+                            emphasis: {
+                                show: true,
+                                textStyle: {
+                                    fontSize: 46,
+                                }
+                            }
+                        },
+                        labelLine: {
+                            normal: {
+                                show: false
+                            }
+                        },
+                        data: data
+                    }
+                ]
+            });
+            myChart.dispatchAction({
+                type: 'highlight',
+                // seriesIndex: 1,
+                dataIndex: 2
+            });
+            //mouseover觸發項
+            if(typeof this.mouseoverCallBack !== "undefined") {
+                let callBackString = this.mouseoverCallBack;
+                myChart.on('highlight', function (params) {
+                    console.log(params)
+                    vm.$emit(callBackString, data[params.batch[0].dataIndex]);
+                });
+            }
+        }
+    }
+}
+
+</script>
+
+<style>
+</style>

+ 57 - 0
TEAMModelOS/ClientApp/css/site.css

@@ -76,4 +76,61 @@
         overflow: hidden;
         text-overflow: ellipsis;
     }
+}
+
+/* 邊角框設計 
+    使用時,最外層一定要有position: relative;
+*/
+.image-border {
+    position: absolute;
+    width: 30px;
+    height: 30px;
+}        
+.image-border-left-top {
+    top: -1px;
+    left: -1px;
+    border-left: 2px solid #FFFFFF;
+    border-top: 2px solid #FFFFFF;
+}
+.image-border-right-top {
+    top: -1px;
+    right: -1px;
+    border-right: 2px solid #FFFFFF;
+    border-top: 2px solid #FFFFFF;
+}   
+.image-border-left-bottom {
+    bottom: -1px;
+    left: -1px;
+    border-bottom: 2px solid #FFFFFF;
+    border-left: 2px solid #FFFFFF;
+}    
+.image-border-lright-bottom {
+    bottom: -1px;
+    right: -1px;
+    border-right: 2px solid #FFFFFF;
+    border-bottom: 2px solid #FFFFFF;
+}
+
+/* 拉軸隱藏
+用兩個div將拉軸藏起來
+*/
+.scroll-outer-container{
+    position: relative;
+    overflow: hidden;
+}
+.scroll-outer-container .scroll-inner-container{
+	position: absolute;
+	left: 0;
+	top: 0;
+	right: -18px;
+	bottom: 0;
+	overflow-x: hidden;
+	overflow-y: scroll;
+}
+
+/* 省略字符*/
+.ellipsis {
+    overflow:hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
 }

+ 8 - 2
TEAMModelOS/ClientApp/router/routes.js

@@ -3,10 +3,12 @@ import ServerSideLogin from '@/view/serverside/login'
 import HTTP404 from '@/view/404'
 import SAIndex from '@/view/SAindex'
 import Index from '@/view/index'
+import SmartDashBoard from '@/view/smart-dashboard/smart-dashboard'
 
 export const routes = [
-  { name: 'login', path: '/login', component: Login, display: 'Home', icon: 'home' },
-  { name: 'ServerSideLogin', path: '/serverside/login', component: ServerSideLogin, display: 'Home', icon: 'home' },
+  { name: 'login', path: '/login', component: Login },
+  { name: 'ServerSideLogin', path: '/serverside/login', component: ServerSideLogin},
+  { name: 'SmartDashBoard', path: '/smartdashboard', component: SmartDashBoard },  
   { name: 'index', path: '/', component: Index },
   { name: 'saindex', path: '/saindex', component: SAIndex },
  // { name: 'HTTP404', path: '/*', component: HTTP404, display: 'Home', icon: 'home' },
@@ -15,6 +17,10 @@ export const routes = [
     name: 'SyllabusIndex',
     component: resolve => require(['@/view/SyIndex.vue'], resolve),
     children: [
+      {
+        path: '/',
+        component: resolve => require(['@/components/BaseContent.vue'], resolve),
+      },
       {
         path: '/BaseContent',
         component: resolve => require(['@/components/BaseContent.vue'], resolve),

+ 3 - 2
TEAMModelOS/ClientApp/utils/http.js

@@ -8,7 +8,7 @@ axios.interceptors.request.use(
     // const token = getCookie('名称');
     config.data = JSON.stringify(config.data);
     config.headers = {
-      'Content-Type':'application/x-www-form-urlencoded'
+      'Content-Type':'application/json'
     }
     // if(token){
     //   config.params = {'token':token}
@@ -68,7 +68,8 @@ export function fetch(url,params={}){
  * @returns {Promise}
  */
 
-export function post(url,data = {}){
+export function post(url, data = {}) {
+  data.method = url;
   return new Promise((resolve,reject) => {
     axios.post(url,data)
       .then(response => {

TEAMModelOS/ClientApp/common/public.js → TEAMModelOS/ClientApp/utils/public.js


+ 62 - 2
TEAMModelOS/ClientApp/view/index.vue

@@ -1,7 +1,18 @@
 <template>
   <div id="app">
-      <button @click="sa()">学情分析</button>
-      <button @click="sy()">课纲管理</button>
+    <div class="main-content centerCol">
+      <img src="../assets/tmd_logo.png" class="logo" />
+      <div class="overlay-wrap center">
+        <div class="main-btn centerCol" @click="sa()">
+          <img src="http://chq.dygl.pujiaoyun.cn:80/static/img/pic.png" alt="">
+          <h4 class="color1">学情分析</h4>
+        </div>
+        <div class="main-btn centerCol" @click="sy()">
+          <img src="http://chq.dygl.pujiaoyun.cn:80/static/img/user.png" alt="">
+          <h4 class="color1">课纲系统</h4>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 <script>
@@ -20,5 +31,54 @@
   }
 </script>
 <style>
+  html, body, #app {
+    height: 100% !important;
+  }
+  .main-content {
+    width: 100%;
+    min-width: 1200px;
+    background: url("http://chq.dygl.pujiaoyun.cn/static/img/banner.jpg") center 100% no-repeat;
+    height: 100%;
+    position: relative;
+  }
+  .overlay-wrap {
+    width: 570px;
+    height: 290px;
+    background-color: rgba(218, 218, 218, 0.18);
+    margin-top:50px;
+  }
+  .overlay-wrap .main-btn {
+    height: 90%;
+    width: 45%;
+    cursor: pointer;
+    font-size: 18px;
+    color: #fff;
+    background-color:rgba(179,179,179,.22);
+    border: 0;
+    margin: 5% 2%;
+  }
+    .overlay-wrap .main-btn:hover {
+      background: rgba(179,179,179,.42);
+    }
+  .center {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+  }
+  .centerCol {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+  }
+  .color1 {
+     margin-top:20px;
+  }
+  .logo {
+     width:200px;
+     height:200px;
+     margin-bottom:50px;
+  }
 
 </style>

+ 197 - 0
TEAMModelOS/ClientApp/view/smart-dashboard/smart-dashboard.less

@@ -0,0 +1,197 @@
+#smart-dashboard{
+  width: 100%;
+  height: 100%;
+  background-color: #5981ad;
+  .Header{
+      width: 100%;
+      position: fixed;
+      padding: 10px 20px;
+      z-index: 1;
+      background-color: #fff;
+      .smart-mark{
+          letter-spacing: 3px;
+          color: #333;
+      }
+      .smart-markD{
+          letter-spacing: 3px;
+          color: #333;
+      }
+      .menu{
+          float: right;
+          height: initial;
+          background-color: transparent;
+          li{
+              color: #333;
+              line-height: initial;
+          }
+      }
+  }
+  .content{
+    padding-top: 40px;
+    height: 100%;
+    overflow: auto;
+    .box{
+      padding: 70px 0px 25px;
+      height: 100%;
+      margin: 0;
+      .block-1{
+        margin: 0 10px;
+        .classInfo{
+          padding: 15px 0 0 25px;
+          h5{
+            font-size: 1rem;
+          }
+          cursor: pointer;
+          &:hover{
+            background: -webkit-linear-gradient(left,transparent,#57a3f336);
+            background: -o-linear-gradient(right,transparent,#57a3f336);
+            background: -moz-linear-gradient(right,transparent,#57a3f336);
+            background: linear-gradient(to right,transparent,#57a3f336);
+          }
+        }
+      }
+      .block-2{
+        margin: 0 10px;
+        height: 100%;
+        .info-row{
+          height: 100%;
+          .info-col-main{
+            border-right: solid 1px #fff;
+            position: relative;
+            background-color: #343a4040;
+            .info-col-content{
+              h5{
+                font-size: 1.25rem;
+                font-weight: 100;
+              }
+              padding: 0 0 17px 25px;
+              color: #fefefe;
+              font-size: 0.82rem;
+              position: absolute;
+              bottom:0;
+            }
+          }
+          .info-col-secondary{
+            background-color: #343a4073;
+            position: relative;
+            .info-col-content{
+              padding: 0 0 17px 10px;
+              font-size: 0.82rem;
+              color: #fefefe;
+              position: absolute;
+              bottom:0;
+              width: 100%;
+              h5{
+                font-size: 1.18rem;
+                font-weight: 100;
+              }
+              &.border-right{
+                border-right: solid 1px #fff;
+              }
+            }
+          }
+        }
+      }
+      .block-3{
+        margin: 0 10px;
+        height: 100%;
+        .today-class-content{
+          height: 100%;
+          .title{
+            height: 20%;
+            font-size: 1rem;
+            color:#fff;
+            padding: 15px 0 15px 15px;
+            font-weight: 100;
+          }
+          .contents{
+            height: 80%;
+            .chart{
+              height: 100%;
+            }
+            .detail{
+              height: 100%;
+              .detail-row{
+                height: 100%;
+                padding: 5px 0;
+                .detail-col{
+                  height:32.3333%;
+                  color: #fefefe;
+                  .detail-title{
+                    display: block;
+                    color: #ccc;
+                    padding-left: 5px;
+                    border-left: 2px solid #333;
+                    &-border-red{
+                      border-left-color: red;
+                    }
+                    &-border-green{
+                      border-left-color: green;
+                    }
+                    &-border-pink{
+                      border-left-color: pink;
+                    }
+                    &-border-yellow{
+                      border-left-color: yellow;
+                    }
+                    &-border-blue{
+                      border-left-color: #2b85e4;
+                    }
+                    &-border-orange{
+                      border-left-color: orange;
+                    }
+                  }
+                  .detail-amount{
+                    font-size: 0.95rem;
+                    font-weight: 100;
+                  }
+                }
+              }
+
+            }
+          }
+        }
+      }
+      .block-4{
+        margin: 0 10px;
+        height: 100%;
+        .hours{
+          padding: 15px 0 0 15px;
+          .hours-title{
+            color: #ccc;
+          }
+          h5{
+            color: #ccc;
+            font-size: 1rem;
+            span{
+              padding:0 5px;
+              color: #fefefe;
+              font-size: 1.5rem;
+              font-weight: 300;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+.block-border{
+  border: 1px solid #6c757d;
+}
+.scrollstyle::-webkit-scrollbar {
+    width: 5px;
+}
+.scrollstyle::-webkit-scrollbar-track {
+  margin: 6px;
+  background: transparent;
+}
+.scrollstyle::-webkit-scrollbar-thumb {
+  border-radius: 10px;
+  background: #555;
+}
+.scrollstyle::-webkit-scrollbar-thumb:hover {
+  // background: #555;
+}
+.scrollstyle::-webkit-scrollbar-button {
+  display: none;
+}

+ 501 - 0
TEAMModelOS/ClientApp/view/smart-dashboard/smart-dashboard.vue

@@ -0,0 +1,501 @@
+<style lang="less" scoped>
+  @import './smart-dashboard.less';
+</style>
+
+<template>
+    <div id="smart-dashboard">
+      <div class="Header">
+        <Row type="flex" justify="center" align="bottom">
+          <Col :span="4">
+            <h5 class="smart-mark">醍摩豆智慧雲</h5>
+          </Col>
+          <Col :span="7">
+            <span class="smart-markD"><Icon type="md-pin" />學情分數分析服務/學情儀表板</span>
+          </Col>
+          <Col :span="12">
+            <Menu class="menu" mode="horizontal" theme="dark">
+              <MenuItem name="1">
+                  <Icon type="ios-paper" />
+                  智慧程式管理
+              </MenuItem>
+              <MenuItem name="2">
+                  <Icon type="ios-people" />
+                  教材資源管理
+              </MenuItem>
+              <MenuItem name="3">
+                  <Icon type="ios-people" />
+                  學習活動服務
+              </MenuItem>
+              <MenuItem name="4">
+                  <Icon type="ios-construct" />
+                  班級智慧服務
+              </MenuItem>
+          </Menu>
+          </Col>
+          <Col :span="1" style="text-align: right;">
+            <Icon style="font-size: 24px; color: #333;" type="md-person" />
+          </Col>
+        </Row>
+      </div>
+      <div class="content">
+        <Row class="box" type="flex" justify="center">
+          <Col  :span="8"  class="block-1 block-border">
+            <!-- block-1 -->
+            <!-- 邊角設計 start-->
+            <div class="image-border image-border-left-top" ></div>
+            <div class="image-border image-border-right-top" ></div>
+            <div class="image-border image-border-left-bottom" ></div>
+            <div class="image-border image-border-lright-bottom" ></div>
+            <!-- 邊角設計 end-->
+            <div style="height: 100%;background-color: #f3f3f34a;">
+              <div style="height: 60%;width:100%;background-color: red;"></div>
+              <div style="height: 40%;width:100%;position: relative;overflow: auto;">
+                <div class="scrollstyle" style="position: absolute;right: 0px;left: 0;top: 0;bottom: 0;overflow-x: hidden;overflow-y: auto;">
+                  <div class="classInfo" v-for="(item, index) in classInfoData" :key="index">
+                    <Row type="flex" justify="center" align="bottom" style="border-bottom: solid 1px #fff;padding-bottom: 7px;">
+                      <Col :span="16">
+                        <h5 style="color: #fefefe;">{{ item.className }}</h5>
+                        <span class="ellipsis" style="display: block;margin-top: 5px;padding-right: 20px;color: #6c757d;">{{ item.classProduct }}&nbsp;&nbsp;|&nbsp;&nbsp;{{ item.classDetail }}</span>
+                      </Col>
+                      <Col :span="8">
+                        <span class="ellipsis" style="color: #fefefe;max-width: 200px;display: inline-block;">{{ item.teacherName}}</span>&nbsp;<Icon style="vertical-align: top;color: #fefefe;" :size="15" type="ios-information-circle" />
+                        <Row  style="display: block;">
+                          <Col :span="12" style="color: #007bff;">
+                            教室熱度&nbsp;&nbsp;|&nbsp;&nbsp;{{item.classHot}}堂
+                          </Col>
+                          <Col :span="12" style="color: #00fb38;">
+                            {{ item.classMode == 0 ? '進行中' : '維護中'}}&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;{{item.classTime}}
+                          </Col>
+                        </Row>
+                      </Col>
+                    </Row>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </Col>
+          <Col  :span="5" class="block-2">
+            <!-- block-2 -->
+            <div class="block-border" style="height: 12%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <Row type="flex" justify="center" class="info-row">
+                <Col :span="9" class="info-col-main">
+                  <div class="info-col-content">
+                    <span>教室開機數</span><br/><small>今日數量</small>
+                    <h5>8</h5>
+                  </div>            
+                </Col>
+                <Col :span="5"  class="info-col-secondary">
+                  <div class="info-col-content border-right">
+                    <br/><small>前7日平均</small>
+                    <h5>8</h5>
+                  </div> 
+                </Col>
+                <Col :span="5"  class="info-col-secondary">
+                  <div class="info-col-content border-right">
+                    <br/><small>前30日平均</small>
+                    <h5>8</h5>
+                  </div> 
+                </Col>
+                <Col :span="5"  class="info-col-secondary">
+                  <div class="info-col-content">
+                    <br/><small>學期平均</small>
+                    <h5>8</h5>
+                  </div> 
+                </Col>
+              </Row>
+            </div>
+            <div style="height: 1.5%"></div>
+            <div class="block-border" style="height: 12%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <Row type="flex" justify="center" class="info-row">
+                <Col :span="9" class="info-col-main">
+                  <div class="info-col-content">
+                    <span>課堂開課數</span><br/><small>今日數量</small>
+                    <h5>54</h5>
+                  </div>            
+                </Col>
+                <Col :span="5"  class="info-col-secondary">
+                  <div class="info-col-content border-right">
+                    <br/><small>前7日平均</small>
+                    <h5>61.2</h5>
+                  </div> 
+                </Col>
+                <Col :span="5"  class="info-col-secondary">
+                  <div class="info-col-content border-right">
+                    <br/><small>前30日平均</small>
+                    <h5>58.9</h5>
+                  </div> 
+                </Col>
+                <Col :span="5"  class="info-col-secondary">
+                  <div class="info-col-content">
+                    <br/><small>學期平均</small>
+                    <h5>53.1</h5>
+                  </div> 
+                </Col>
+              </Row>
+            </div>
+            <div style="height: 1.5%"></div>
+            <div class="block-border" style="height: 12%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <Row type="flex" justify="center" class="info-row">
+                <Col :span="9" class="info-col-main">
+                  <div class="info-col-content">
+                    <span>課堂參與人數</span><br/><small>今日數量</small>
+                    <h5>1,892</h5>
+                  </div>            
+                </Col>
+                <Col :span="5"  class="info-col-secondary">
+                  <div class="info-col-content border-right">
+                    <br/><small>前7日平均</small>
+                    <h5>2,542</h5>
+                  </div> 
+                </Col>
+                <Col :span="5"  class="info-col-secondary">
+                  <div class="info-col-content border-right">
+                    <br/><small>前30日平均</small>
+                    <h5>2,301</h5>
+                  </div> 
+                </Col>
+                <Col :span="5"  class="info-col-secondary">
+                  <div class="info-col-content">
+                    <br/><small>學期平均</small>
+                    <h5>2,436</h5>
+                  </div> 
+                </Col>
+              </Row>
+            </div>
+            <div style="height: 1.5%"></div>
+            <div class="block-border" style="height: 29%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <div style="background-color: #343a4040;height:20%;border-bottom: 1px solid #fff;">
+                <h5 style="font-size: 1rem;color:#fff;padding: 15px 0 15px 15px;font-weight: 100;">今日課堂數量變化</h5>
+              </div>
+              <div style="height: 80%;padding: 20px 0;">
+                <ClassLine :id="'line1'"></ClassLine>
+              </div>
+            </div>
+            <div style="height: 1.5%"></div>
+            <div class="block-border" style="height: 29%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <div style="background-color: #343a4040;height: 20%;padding: 15px 0 15px 15px;border-bottom: 1px solid #fff;">
+                <h5 style="font-size: 1rem;color:#fff;font-weight: 100;">今日課堂效率值排名&nbsp;&nbsp;&nbsp;&nbsp;(8hr)</h5>
+              </div>
+              <div style="background-color: #343a4073;height: 80%;">
+                <Row type="flex" justify="center" align="middle" style="border-bottom: solid 1px #fff;color:#fff;padding: 7px 0 5px 0;margin-left: 15px;height: 33.3333333333%">
+                  <Col :span="16">
+                    <h5 style="font-size: 1rem;">GE303, 通識教室3</h5>
+                    <span style="color: rgb(108, 117, 125);display: block;padding-right: 20px;">今日使用時數&nbsp;&nbsp;|&nbsp;&nbsp;4:23:09</span>
+                  </Col>
+                  <Col :span="8">
+                    <h4 style="font-size: 1.3rem;text-align: right;padding-right: 15px;">107.3%</h4>
+                  </Col>
+                  </Row>
+                <Row type="flex" justify="center" align="middle" style="border-bottom: solid 1px #fff;color:#fff;padding: 7px 0 5px 0;margin-left: 15px;height: 33.3333333333%">
+                  <Col :span="16">
+                    <h5 style="font-size: 1rem;">GE303, 通識教室7</h5>
+                    <span style="color: rgb(108, 117, 125);display: block;padding-right: 20px;">今日使用時數&nbsp;&nbsp;|&nbsp;&nbsp;4:23:09</span>
+                  </Col>
+                  <Col :span="8">
+                    <h4 style="font-size: 1.3rem;text-align: right;padding-right: 15px;">69.4%</h4>
+                  </Col>
+                </Row>
+                <Row type="flex" justify="center" align="middle" style="color:#fff;padding: 7px 0 5px 0;margin-left: 15px;height: 33.3333333333%">
+                  <Col :span="16">
+                    <h5 style="font-size: 1rem;">GE303, 通識教室1</h5>
+                    <span style="color: rgb(108, 117, 125);display: block;padding-right: 20px;">今日使用時數&nbsp;&nbsp;|&nbsp;&nbsp;4:23:09</span>
+                  </Col>
+                  <Col :span="8">
+                    <h4 style="font-size: 1.3rem;text-align: right;padding-right: 15px;">67.9%</h4>
+                  </Col>
+                </Row>
+              </div>
+            </div>
+          </Col>
+          <Col  :span="5" class="block-3">
+            <div class="block-border" style="height: 23.91%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <div class="today-class-content">
+                <h5 class="title">今日智慧教堂啟用類型</h5>
+                <Row class="contents">
+                  <Col :span="12" class="chart"><RingPie :id="'ringPie1'"></RingPie></Col>
+                  <Col :span="12" class="detail">
+                    <Row class="detail-row">
+                      <Col :span="12" class="detail-col">
+                        <span class="detail-title detail-title-border-red">新增檔案</span>
+                        <h5 class="detail-amount">33% <small>(168)</small></h5>
+                      </Col>
+                      <Col :span="12" class="detail-col">
+                        <span  class="detail-title detail-title-border-green">書面問答</span>
+                        <h5 class="detail-amount" >33% <small>(168)</small></h5>
+                      </Col>
+                      <Col :span="12" class="detail-col">
+                        <span  class="detail-title detail-title-border-pink" >匯入.pptx</span>
+                        <h5 class="detail-amount" >33% <small>(168)</small></h5>
+                      </Col>
+                      <Col :span="12" class="detail-col">
+                        <span  class="detail-title detail-title-border-yellow" >PowerClick</span>
+                        <h5 class="detail-amount" >33% <small>(168)</small></h5>
+                      </Col>
+                      <Col :span="12" class="detail-col">
+                        <span  class="detail-title detail-title-border-blue" >開啟.hte</span>
+                        <h5 class="detail-amount" >33% <small>(168)</small></h5>
+                      </Col>
+                      <Col :span="12" class="detail-col">
+                        <span  class="detail-title detail-title-border-orange" >其他</span>
+                        <h5 class="detail-amount" >33% <small>(168)</small></h5>
+                      </Col>
+                    </Row>
+                  </Col>
+                </Row>
+              </div>
+            </div>
+            <div style="height: 2.17%;"></div>
+            <div class="block-border" style="height: 23.91%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <div style="height: 100%;" class="today-class-content">
+                <h5 class="title">今日各版本教室開課狀態</h5>
+                <Row class="contents">
+                  <Col :span="12" class="chart"><RingPie :id="'ringPie2'"></RingPie></Col>
+                  <Col :span="12" class="detail">
+                    <Row class="detail-row">
+                      <Col :span="12" class="detail-col">
+                        <span class="detail-title detail-title-border-red">HiTeach STD</span>
+                        <h5 class="detail-amount">26% <small>(8)</small></h5>
+                      </Col>
+                      <Col :span="12" class="detail-col">
+                        <span  class="detail-title detail-title-border-green">HiTeach PRE</span>
+                        <h5 class="detail-amount" >25% <small>(168)</small></h5>
+                      </Col>
+                      <Col :span="12" class="detail-col">
+                        <span  class="detail-title detail-title-border-pink" >HiTeach PRO</span>
+                        <h5 class="detail-amount" >18% <small>(168)</small></h5>
+                      </Col>
+                      <Col :span="12" class="detail-col">
+                        <span  class="detail-title detail-title-border-yellow" >HiTeach Mobile</span>
+                        <h5 class="detail-amount" >10% <small>(168)</small></h5>
+                      </Col>
+                      <Col :span="12" class="detail-col">
+                        <span  class="detail-title detail-title-border-blue" >HiTeach TBL</span>
+                        <h5 class="detail-amount" >21% <small>(168)</small></h5>
+                      </Col>
+                      <Col :span="12" class="detail-col">
+                        <span  class="detail-title detail-title-border-orange" >其他</span>
+                        <h5 class="detail-amount" >0% <small>(0)</small></h5>
+                      </Col>
+                    </Row>
+                  </Col>
+                </Row>
+              </div>
+            </div>
+            <div style="height: 2.17%;"></div>
+            <div class="block-border" style="height: 47.84%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->              
+            </div>
+          </Col>
+          <Col  :span="4" class="block-4">
+            <div class="block-border" style="height: 10.43%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <div class="hours">
+                <span class="hours-title">累積教堂時數</span>
+                <h5><span>4,234</span>h<span>43</span>m</h5>
+              </div>
+            </div>
+            <div style="height: 2.17%"></div>
+            <div class="block-border" style="height: 23.48%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <div style="height: 100%;">
+                <h5 style="height: 20%;font-size: 1rem;color: #fff;padding: 10px 0 0 10px;font-weight: 100;">智慧教室版本</h5>
+                <Row type="flex" justify="center" align="middle" style="height: 80%;">
+                  <Col style="height: 100%;" :span="12"><RingPie :id="'ringPie3'"></RingPie></Col>
+                  <Col :span="12" style="padding-left: 7px;">
+                    <span>HiTeach3 STD</span>
+                    <h5 style="font-weight: 100;font-size: 2rem;color: #fefefe;">36.3%</h5>
+                    <span>累計 4 筆</span>
+                  </Col>
+                </Row>
+              </div>
+            </div>
+            <div style="height: 2.17%"></div>
+            <div class="block-border" style="height: 23.48%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <div style="height: 100%;">
+                <h5 style="height: 20%;font-size: 1rem;color: #fff;padding: 10px 0 0 10px;font-weight: 100;">課堂作業系統</h5>
+                <Row type="flex" justify="center" align="middle" style="height: 80%;">
+                  <Col style="height: 100%;" :span="12"><RingPie :id="'ringPie4'"></RingPie></Col>
+                  <Col :span="12" style="padding-left: 7px;">
+                    <span>windows 7 (SP3)</span>
+                    <h5 style="font-weight: 100;font-size: 2rem;color: #fefefe;">63.6%</h5>
+                    <span>累計 1185 次</span>
+                  </Col>
+                </Row>
+              </div>
+            </div>
+            <div style="height: 2.17%"></div>
+            <div class="block-border" style="height: 36.1%;position: relative;">
+              <!-- 邊角設計 start-->
+              <div class="image-border image-border-left-top" ></div>
+              <div class="image-border image-border-right-top" ></div>
+              <div class="image-border image-border-left-bottom" ></div>
+              <div class="image-border image-border-lright-bottom" ></div>
+              <!-- 邊角設計 end-->
+              <div style="height: 100%">
+                <div style="height: 33.333333%">
+                  <Row type="flex" justify="center" align="middle" style="height: 100%;border-bottom: 1px solid #fff;margin-left: 20px;">
+                    <Col  :span="6">
+                      <img style="display: block;margin: auto;" src="@/assets/image/touxiang.png">
+                    </Col>
+                    <Col style="height: 100%; padding: 10px;" :span="18">
+                      <span style="color: #ccc">最高效率課堂使用者</span>
+                      <h5 style="max-width: 180px;font-size: 1rem;color: #fefefe;display: inline-block;" class="ellipsis">會錯意大將軍罪證地縛靈我愛老師方瑞新</h5><Icon style="vertical-align: top;color: #fefefe;" :size="15" type="ios-information-circle" />
+                      <span style="color: #ccc;display: block;">最高單醣效率值: 77.8 %</span>
+                      <span style="color: #ccc;display: block;">平均課堂效率值: 71.1 %</span>
+                    </Col>
+                  </Row>
+                </div>
+                <div style="height: 33.333333%">
+                  <Row type="flex" justify="center" align="middle" style="height: 100%;border-bottom: 1px solid #fff;margin-left: 20px;">
+                    <Col  :span="6">
+                      <img style="display: block;margin: auto;" src="@/assets/image/touxiang.png">
+                    </Col>
+                    <Col style="height: 100%; padding: 10px;" :span="18">
+                      <span style="color: #ccc">最高效率課堂使用者</span>
+                      <h5 style="max-width: 180px;font-size: 1rem;color: #fefefe;display: inline-block;" class="ellipsis">會錯意大將軍罪證地縛靈我愛老師方瑞新</h5><Icon style="vertical-align: top;color: #fefefe;" :size="15" type="ios-information-circle" />
+                      <span style="color: #ccc;display: block;">最高單醣效率值: 77.8 %</span>
+                      <span style="color: #ccc;display: block;">平均課堂效率值: 71.1 %</span>
+                    </Col>
+                  </Row>
+                </div>
+                <div style="height: 33.333333%">
+                  <Row type="flex" justify="center" align="middle" style="height: 100%;margin-left: 20px;">
+                    <Col  :span="6">
+                      <img style="display: block;margin: auto;" src="@/assets/image/touxiang.png">
+                    </Col>
+                    <Col style="height: 100%; padding: 10px;" :span="18">
+                      <span style="color: #ccc">最高效率課堂使用者</span>
+                      <h5 style="max-width: 180px;font-size: 1rem;color: #fefefe;display: inline-block;" class="ellipsis">會錯意大將軍罪證地縛靈我愛老師方瑞新</h5><Icon style="vertical-align: top;color: #fefefe;" :size="15" type="ios-information-circle" />
+                      <span style="color: #ccc;display: block;">最高單醣效率值: 77.8 %</span>
+                      <span style="color: #ccc;display: block;">平均課堂效率值: 71.1 %</span>
+                    </Col>
+                  </Row>
+                </div>
+              </div>
+            </div>            
+          </Col>
+        </Row>
+        <Row :gutter="20" type="flex" justify="center" style="padding-top: 10px;height: 100%;margin: 0;"> 
+          <Col :span="10"><div style="width: 100%;height: 100%;background-color: red;"></div></Col>
+          <Col :span="5"><div style="width: 100%;height: 100%;background-color: red;"></div></Col>
+          <Col :span="4"><div style="width: 100%;height: 100%;background-color: red;"></div></Col>
+          <Col :span="3"><div style="width: 100%;height: 100%;background-color: red;"></div></Col>
+        </Row>
+      </div>
+    </div>
+</template>
+
+<script>
+import LegendPie from '@/components/graph/legendPie'
+import RingPie from '@/components/graph/ringPie'
+import ClassLine from '@/components/graph/classline'
+
+
+export default {
+  name:'smart-dashboard',
+  data () {
+    return {
+      classInfoData:[
+        { 
+          'className': 'GE305, 通識教室5',
+          'classProduct': 'Hiteach 3 TBL',
+          'classDetail': '序號使用至2021-09-30, 50人, HBI082, RF05H',
+          'teacherName': '大明星杜美心',
+          'classHot': '7',
+          'classTime': '00:45:03',
+          'classMode': '0'
+        },
+        { 
+          'className': 'GE305, 通識教室5',
+          'classProduct': 'Hiteach 3 TBL',
+          'classDetail': '序號使用至2021-09-30, 50人, HBI082, RF05H',
+          'teacherName': '大明星杜美心',
+          'classHot': '7',
+          'classTime': '--:--:--',
+          'classMode': '1'
+        },
+      ]
+    }
+  },
+  components:{
+    LegendPie,
+    RingPie,
+    ClassLine
+  },
+  methods:{
+    pieHover: function(type, value) {
+      switch (type) {
+        case 'class':
+          
+          break;
+        case 'system':
+          
+          break;
+      }
+    }
+  }
+}
+</script>

+ 149 - 0
TEAMModelOS/Controllers/Common/LoginController.cs

@@ -0,0 +1,149 @@
+using IdentityModel;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using System.Security.Claims;
+using System.Text;
+using System.Threading.Tasks;
+using TEAMModelOS.Model.Common.Dtos;
+using TEAMModelOS.SDK.Context.Constant.Common;
+using TEAMModelOS.SDK.Extension.DataResult.JsonRpcRequest;
+using TEAMModelOS.SDK.Extension.DataResult.JsonRpcResponse;
+using TEAMModelOS.SDK.Extension.JwtAuth.JwtHelper;
+using TEAMModelOS.SDK.Extension.JwtAuth.Models;
+using TEAMModelOS.SDK.Helper.Common.ValidateHelper;
+using TEAMModelOS.Service.Common.Interfaces;
+
+namespace TEAMModelOS.Controllers.Common
+{
+    [Route("api/[controller]")]
+    [ApiController]
+    public class LoginController : Controller
+    {
+        private ILoginInfoService _loginInfoService;
+        public readonly IOptions<JwtSetting> _options;
+        public LoginController(ILoginInfoService loginInfoService , IOptions<JwtSetting> options) {
+            _loginInfoService = loginInfoService;
+            _options = options;
+        }
+
+        /// <summary>
+		/// 登录验证
+		/// </summary>
+		/// <param name="test"></param>
+		/// <returns></returns>
+		[HttpPost("Login")]
+        [AllowAnonymous]
+        public async Task<BaseJosnRPCResponse> CheckLogin(JosnRPCRequest<TicketInfo> request)
+        {
+            JsonRPCResponseBuilder responseBuilder = JsonRPCResponseBuilder.custom();
+            if (ValidateHelper.IsValid(request.@params))
+            {
+                LoginResult result = await  _loginInfoService.CheckLoginAsync(request.@params);
+                if (result.CheckTicket)
+                {
+                    responseBuilder.Data(result);
+                }
+                else
+                {
+                    responseBuilder.Error(false, ResponseCode.FAILED, "ticket error!");
+                }
+            }
+            else
+            {
+                responseBuilder.Error(false, ResponseCode.FAILED, "ticket error!");
+            }
+            return responseBuilder.build();
+        }
+
+        /// <summary>
+        /// 授权
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("authorization")]
+        [AllowAnonymous]
+        public BaseJosnRPCResponse Authorization()
+        {
+           // string jwtToken = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaHVhbmdoYiIsImVtYWlsIjoiaHVhbmdoYkBjZGhhYm9vay5jb20iLCJpYXQiOjE1NDg3MzA2MjMsIm5iZiI6MTU0ODczMDYyMywiZXhwIjoxNTQ4NzMwNjgzLCJhdWQiOiJIYWJvb2suVGVhbU1vZGVsLkNvbnRlc3QiLCJpc3MiOiJIYUJvb2siLCJyb2xlIjoiYWRtaW4scm9vdCJ9.acye5fBHttovBjr0JdDNvm8zVjuLukqyQZ_GPSN7t8aDBiKO8K80dMfefaYC7bF1VodzVFDKKEkieuq4NRnTjw";
+            //ClaimModel claimModel =  JwtHelper.SerializeJWT(jwtToken);
+            JsonRPCResponseBuilder builder = JsonRPCResponseBuilder.custom();
+            ClaimModel claimModel = new ClaimModel
+            {
+                Scope = "WebApp"
+            };
+            claimModel.Claims.Add(new Claim(JwtClaimTypes.Name, "huanghb"));
+            claimModel.Claims.Add(new Claim(JwtClaimTypes.Email, "huanghb@cdhabook.com"));
+            claimModel.Roles.Add("admin,root");
+            Dictionary<string, object> ext = new Dictionary<string, object>
+            {
+                { "claimModel", claimModel }
+            };
+
+            builder.Data(claimModel).CurrPage(1);
+            return builder.Data(JwtHelper.IssueJWT(claimModel, _options.Value)).Extend(ext).build();
+        }
+        /// <summary>
+        /// 认证
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("authentication")]
+        [Authorize]
+        public Object Authentication()
+        {
+            return "";
+        }
+        [AllowAnonymous]
+       
+        [HttpPost("Authenticate")]
+        public IActionResult Authenticate()
+        {
+            var user = new 
+            {
+                id = 111,
+                name = "123",
+                email = "3538@qq.com",
+                phone = "15283771540"
+            };
+            if (user == null)
+                return Unauthorized();
+            var tokenHandler = new JwtSecurityTokenHandler();
+            var key = Encoding.ASCII.GetBytes("JwtBearerSample_11231~#$%#%^2235");
+            var authTime = DateTime.UtcNow;
+            var expiresAt = authTime.AddDays(7);
+            var tokenDescriptor = new SecurityTokenDescriptor
+            {
+                Subject = new ClaimsIdentity(new Claim[]
+                {
+                new Claim(JwtClaimTypes.Audience,"api"),
+                new Claim(JwtClaimTypes.Issuer,"http://localhost:5200"),
+                new Claim(JwtClaimTypes.Id, user.id.ToString()),
+                new Claim(JwtClaimTypes.Name, user.name),
+                new Claim(JwtClaimTypes.Email, user.email),
+                new Claim(JwtClaimTypes.PhoneNumber, user.phone)
+                }),
+                Expires = expiresAt,
+                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
+            };
+            var token = tokenHandler.CreateToken(tokenDescriptor);
+            var tokenString = tokenHandler.WriteToken(token);
+            return Ok(new
+            {
+                access_token = tokenString,
+                token_type = "Bearer",
+                profile = new
+                {
+                    sid = user.id,
+                    user.name,
+                    auth_time = new DateTimeOffset(authTime).ToUnixTimeSeconds(),
+                    expires_at = new DateTimeOffset(expiresAt).ToUnixTimeSeconds()
+                }
+            });
+        }
+
+    }
+}

+ 9 - 0
TEAMModelOS/Controllers/Syllabus/SyllabusController.cs

@@ -8,6 +8,7 @@ using TEAMModelOS.Model.Syllabus.Models;
 using TEAMModelOS.SDK.Context.Attributes.Table;
 using TEAMModelOS.SDK.Extension.DataResult.JsonRpcRequest;
 using TEAMModelOS.SDK.Extension.DataResult.JsonRpcResponse;
+using TEAMModelOS.SDK.Extension.JwtAuth.JwtHelper;
 using TEAMModelOS.SDK.Helper.Common.JsonHelper;
 using TEAMModelOS.Service.Syllabus.Interfaces;
 
@@ -66,5 +67,13 @@ namespace TEAMModelOS.Controllers.Syllabus
             List<PeriodSubjectEditionTerm> data = await syllabusService.FindTermsByDict(request.@params);
             return builder.Data(data).build();
         }
+
+        [HttpGet("RSA")]
+        public  BaseJosnRPCResponse RSA()
+        {
+            JsonRPCResponseBuilder builder = JsonRPCResponseBuilder.custom();
+          //  var data = RsaHelper.GenerateAndSaveKey("E://");
+            return builder.Data(null).build();
+        }
     }
 }

+ 5 - 1
TEAMModelOS/Startup.cs

@@ -36,7 +36,7 @@ namespace TEAMModelOS
         public IConfiguration Configuration { get; }
 
         // This method gets called by the runtime. Use this method to add services to the container.
-        public void ConfigureServices(IServiceCollection services)
+        public void ConfigureServices(IServiceCollection services )
         {
             //services.Configure<CookiePolicyOptions>(options =>
             //{
@@ -102,6 +102,10 @@ namespace TEAMModelOS
                 routes.MapRoute(
                     name: "default",
                     template: "{controller=Home}/{action=Index}/{id?}");
+
+                routes.MapSpaFallbackRoute(
+                    name: "spa-fallback",
+                    defaults: new { controller = "Home", action = "Index" });
             });
             seedDataService.SeedData();
         }

+ 2 - 0
TEAMModelOS/package.json

@@ -22,6 +22,7 @@
     "core-js": "^2.5.3",
     "echarts": "^4.2.1",
     "iview": "^3.3.0",
+    "less": "^3.9.0",
     "vue": "^2.6.9",
     "vue-infinite-loading": "^2.4.3",
     "vue-router": "^3.0.2",
@@ -67,6 +68,7 @@
     "event-source-polyfill": "^1.0.5",
     "file-loader": "^3.0.1",
     "font-awesome": "^4.7.0",
+    "less-loader": "^4.1.0",
     "mini-css-extract-plugin": "^0.5.0",
     "node-sass": "^4.8.2",
     "optimize-css-assets-webpack-plugin": "^5.0.1",

+ 27 - 0
TEAMModelOS/private.pem

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAq8pNyi9hNGbjAU16coA/W6hBQb92YbLFKuePbpmRg7nc/8be
+iGI6ngl0IDmbAcyQEN5IHN8B1xma33RSXdlRO3+fiUlk81gV1uSYTaiRAWL0TryM
+Kf9avQsw+ezP5HVNQRj75lZZxeNg9YTbjVEbVAj/cF/0E8xBdplk1+AyrZTk/0Cl
+04Pr+dM5R+fisgIp3sEULORzhpow38l+S2YjoCagsckPKhSuqLEkB3KoiFJKCoHK
+UJ+dOfYoa6m3N5ylQCm79HzWeS7PSaXVq6Qh+nxNvA2FkSgcSPc1a1vNKXCwHW2H
+ShKsPGugaRCZPTHPDip05suatheVU4AhpVq9MwIDAQABAoIBAEN7ZLNXiUecYu/Q
+D5mK51iJ75h0LXyTTQBdlepMSO0FxdpztFa+v2f1Lri56Dn8sVru9kThFUf5tuyw
+NOrIwzAsIe879XvhzjJr+agH3248ANwtFKIqvOoly0dhzwhfS8mWlt0Ubo9dkmjf
+l58bjTClmDH8f1SHl++Osh5a441rmoTMfx6YyvTp5ekHLGsll24nOVE3w/6MWTvU
+wBoNFFMvkb062l3QSG/3iPgwEsk8VNX3+7MRp40gurEk51wGL91ysKQq6k8xe8JI
+fMN6x+D8ZuVSOzUMwK4JjoXR646bEEmQMTO/uypQqvXav2V2k5pLW40tlirR48il
+9OYCNAECgYEA4tjAqEbnhHuErcgtzQCHD24/vAXuDiKw58Caq+Bae6QgkqjWC2IQ
+FAkLuvZ2tQSx0EVDZ1+1qP9PLVx2Q1DYs+CwiOKaM04M3XAbppgonZ4+PWiSWUKa
+6jKXmHmQeuCWQRlCrinrrPsrRhNbc1UvhjRWFtlOeHEjMp8kkMvqKFMCgYEAwd4y
+TuaV8fAExYsNZtBTx1lyyp+2Yk3zjNG4AsxJZ7QRx5VQVXmh5Y9CMvkwB09KQonF
+IcHYWMuseLvVfweQwH3WS4D8G/zf92GISLtsIUes4bDxs3lFipoCutzJt3hcHYIs
+fcKW29VravnY5P1FBL7Kw1FV9fReC5G8Kr56+6ECgYAToDka1feVyLHz7wu3XsJQ
+nXM6KzErW+j+Q/WdxS1O4SZISf8qwPMvKJExPb2Lb7qKkFOCkA9BbcVF4WgB7u6w
+sVHUpfY9VhVa2yYpRaTN7jDoUzHGMzL/7Bydfnb3ipK1UyAnvUy8jIZz65zP36Da
+9ZA1LdvSI8ety4L4XAwILQKBgEG0D3LEHT9grA1TIhidNDeUrh3T4RkR+B/al7WD
+DMGzum7cwNwnG+JUGghygX+ItM82J/RX4P2/EVXduu6RXtKOE0mFjmvh2PnbOTgF
+JRFmXEYlFNVRnWrq5U/1MfwXM4jgrDl28G/StdQMlyCMYY7JJqjpcOuphRIAszf9
+VGrhAoGBAOEsLNr0BUZncJRgPEr+v1mI89d53WlM13J1YOaw7xVdvAPbOPlUix3f
+/ZGka2N14K4Dt1NjpwpGfMrfZFAlWKIiF9HFaXmTFjK68JK9HFJfW01k6qfiltH1
+0vhPZ/VIj6wrkHB61tuu0J8VFdap2KAtop+9HyIRiVsOjxtrASHP
+-----END RSA PRIVATE KEY-----

+ 9 - 0
TEAMModelOS/public.pem

@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq8pNyi9hNGbjAU16coA/
+W6hBQb92YbLFKuePbpmRg7nc/8beiGI6ngl0IDmbAcyQEN5IHN8B1xma33RSXdlR
+O3+fiUlk81gV1uSYTaiRAWL0TryMKf9avQsw+ezP5HVNQRj75lZZxeNg9YTbjVEb
+VAj/cF/0E8xBdplk1+AyrZTk/0Cl04Pr+dM5R+fisgIp3sEULORzhpow38l+S2Yj
+oCagsckPKhSuqLEkB3KoiFJKCoHKUJ+dOfYoa6m3N5ylQCm79HzWeS7PSaXVq6Qh
++nxNvA2FkSgcSPc1a1vNKXCwHW2HShKsPGugaRCZPTHPDip05suatheVU4AhpVq9
+MwIDAQAB
+-----END PUBLIC KEY-----

+ 2 - 1
TEAMModelOS/webpack.config.js

@@ -46,7 +46,8 @@ module.exports = () => {
         { test: /\.js$/, include: /ClientApp/, use: 'babel-loader' },
         { test: /\.css$/, use: isDevBuild ? ['style-loader', 'css-loader'] : [MiniCssExtractPlugin.loader, 'css-loader'] },
         { test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' },
-        { test: /\.(ttf|woff)$/, loader: 'file-loader' }
+        { test: /\.(ttf|woff)$/, loader: 'file-loader' },
+        { test: /\.less$/, use: [{ loader: "style-loader"}, {loader: "css-loader"}, {loader: "less-loader", options: { javascriptEnabled: true }}] }
       ]
     },
     plugins: [