Explorar el Código

Merge branch 'develop3.0' of http://106.12.23.251:10080/TEAMMODEL/TEAMModelOS into develop3.0

李思淳 hace 5 años
padre
commit
dd4e31247f

+ 2 - 2
TEAMModelGrpc/appsettings.Development.json

@@ -10,9 +10,9 @@
   "urls": "https://*:5000",
   "GrpcServer": {
     //用于grpc启动后注册到服务发现的ip地址段
-    "ServiceAddress": "192.168.*.*:",
+    "ServiceAddress": "*.*.*.*:",
     //是否启用服务注册和服务发现,默认是true
-    "EnableDiscovery": false,
+    "EnableDiscovery": true,
     //服务发现服务器地址
     "DiscoveryUrl": "http://106.12.23.251:8500",
     //注册到服务发现的服务名称

+ 2 - 2
TEAMModelGrpc/appsettings.json

@@ -15,9 +15,9 @@
   "urls": "https://*:5000",
   "GrpcServer": {
     //用于grpc启动后注册到服务发现的ip地址段
-    "ServiceAddress": "192.168.*.*:",
+    "ServiceAddress": "*.*.*.*:",
     //是否启用服务注册和服务发现,默认是true
-    "EnableDiscovery": false,
+    "EnableDiscovery": true,
     //服务发现服务器地址
     "DiscoveryUrl": "http://106.12.23.251:8500",
     //注册到服务发现的服务名称

+ 1 - 0
TEAMModelOS.SDK/Context/Attributes/Azure/CosmosDBAttribute.cs

@@ -10,5 +10,6 @@ namespace TEAMModelOS.SDK.Context.Attributes.Azure
         public int RU { get; set; }
         public string Name { get; set; }
         public bool Cache { get; set; } = false;
+        public bool Monitor { get; set; } = false;
     }
 }

+ 46 - 24
TEAMModelOS.SDK/Module/AzureCosmosDBV3/AzureCosmosDBV3Repository.cs

@@ -11,6 +11,7 @@ using System.Net;
 using System.Reflection;
 using System.Text;
 using System.Text.Json;
+using System.Threading;
 using System.Threading.Tasks;
 using TEAMModelOS.SDK.Context.Attributes.Azure;
 using TEAMModelOS.SDK.Context.Exception;
@@ -20,9 +21,11 @@ using TEAMModelOS.SDK.Module.AzureCosmosDB.Configuration;
 
 namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
 {
-    internal class CosmosModelInfo {
+    public class CosmosModelInfo {
         public Container container{ get; set; }
         public bool cache { get; set; }
+        public bool monitor { get; set; } = false;
+        public Type type { get; set; }
     }
 
     public class AzureCosmosDBV3Repository : IAzureCosmosDBV3Repository
@@ -41,15 +44,15 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
         private const string CacheCosmosPrefix = "cosmos:";
         private string[] ScanModel { get; set; }
         private const int timeoutSeconds = 86400;
+
+        private string leaseId = "leaseContainer";
         public AzureCosmosDBV3Repository(AzureCosmosDBOptions options, CosmosSerializer cosmosSerializer)
         {
             try
             {
                 if (!string.IsNullOrEmpty(options.ConnectionString))
                 {
-
                     CosmosClient = CosmosDBV3ClientSingleton.getInstance(options.ConnectionString, options.ConnectionKey, cosmosSerializer).GetCosmosDBClient();
-
                 }
                 else
                 {
@@ -102,9 +105,10 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
                 {
                     foreach (ContainerProperties container in await resultSetIterator.ReadNextAsync())
                     {
-                        DocumentCollectionDict.TryAdd(container.Id, new CosmosModelInfo { container= database.GetContainer(container.Id) ,cache=false});
+                            DocumentCollectionDict.TryAdd(container.Id, new CosmosModelInfo { container = database.GetContainer(container.Id), cache = false,monitor=false });
                     }
                 }
+                bool isMonitor = false;
                 //获取数据库所有的表
                 List<Type> types = ReflectorExtensions.GetAllTypeAsAttribute<CosmosDBAttribute>(ScanModel);
                 foreach (Type type in types)
@@ -113,6 +117,7 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
                     string CollectionName = "";
                     int RU = 0;
                     bool cache = false;
+                    bool monitor = false;
                     IEnumerable<CosmosDBAttribute> attributes = type.GetCustomAttributes<CosmosDBAttribute>(true);
                     if (!string.IsNullOrEmpty(attributes.First<CosmosDBAttribute>().Name))
                     {
@@ -126,6 +131,13 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
                     {
                         cache = attributes.First<CosmosDBAttribute>().Cache;
                     }
+                    if (attributes.First<CosmosDBAttribute>().Monitor)
+                    {
+                        monitor = attributes.First<CosmosDBAttribute>().Monitor;
+                        if (monitor) {
+                            isMonitor = true;
+                        }
+                    }
                     //else
                     //{
                     //    cache = false;
@@ -143,11 +155,13 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
                     { //更新RU
 
                         cosmosModelInfo.cache = cache;
-                        int? throughputResponse = await CosmosClient.GetDatabase(DatabaseId).GetContainer(cosmosModelInfo.container.Id).ReadThroughputAsync();
+                        Container container =   CosmosClient.GetDatabase(DatabaseId).GetContainer(cosmosModelInfo.container.Id);
+                        int? throughputResponse = await container.ReadThroughputAsync();
                         if (throughputResponse < RU)
                         {
                             await CosmosClient.GetDatabase(DatabaseId).GetContainer(cosmosModelInfo.container.Id).ReplaceThroughputAsync(RU);
                         }
+                        DocumentCollectionDict[CollectionName] = new CosmosModelInfo { container = container, cache = cache,monitor= monitor ,type= type } ;
                     }
                     else
                     {
@@ -162,9 +176,14 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
                             CollectionThroughput = RU;
                         }
                         Container containerWithConsistentIndexing = await database.CreateContainerIfNotExistsAsync(containerProperties, throughput: CollectionThroughput);
-                        DocumentCollectionDict.TryAdd(CollectionName, new CosmosModelInfo { container= containerWithConsistentIndexing ,cache=cache});
+                        DocumentCollectionDict.TryAdd(CollectionName, new CosmosModelInfo { container= containerWithConsistentIndexing ,cache=cache  , monitor = monitor ,type=type });
                     }
                 }
+                if (isMonitor) {
+                    ContainerProperties leaseProperties = new ContainerProperties { Id = leaseId, PartitionKeyPath = "/id" };
+                    Container leaseContainer = await database.CreateContainerIfNotExistsAsync(leaseProperties, throughput: CollectionThroughput);
+                    DocumentCollectionDict.TryAdd(leaseId, new CosmosModelInfo { container = leaseContainer, cache = false, monitor = false });
+                }
             }
             catch (CosmosException e)
             {
@@ -172,7 +191,10 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
             }
         }
 
-
+        public Dictionary<string, CosmosModelInfo> GetCosmosModelInfo() {
+            return this.DocumentCollectionDict;
+        }
+ 
 
 
         private string GetPartitionKey<T>()
@@ -430,13 +452,13 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
 
             return results;
         }
-        public async Task<List<dynamic>> FindByDict(string CollectionName, Dictionary<string, object> dict, int itemsPerPage = -1, int? maxItemCount = null, string partitionKey = null, List<string> propertys = null)
+        public async Task<List<dynamic>> FindByDict(string CollectionName, Dictionary<string, object> dict, string partitionKey = null, List<string> propertys = null)
         {
             if (DocumentCollectionDict.TryGetValue(CollectionName, out CosmosModelInfo container))
             {
                 //StringBuilder sql = new StringBuilder("select value(c) from c");
                 //SQLHelper.GetSQL(dict, ref sql);
-                //CosmosDbQuery cosmosDbQuery = new CosmosDbQuery
+                //CosmosDbQuery cosmosDbQuery = new CosmosDbQuery, int itemsPerPage = -1, int? 
                 //{
                 //    QueryText = sql.ToString()
 
@@ -444,9 +466,9 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
                 StringBuilder sql;
                 sql = SQLHelperParametric.GetSQLSelect(propertys);
                 CosmosDbQuery cosmosDbQuery = SQLHelperParametric.GetSQL(dict, sql);
-                QueryRequestOptions queryRequestOptions = GetDefaultQueryRequestOptions(itemsPerPage: GetEffectivePageSize(itemsPerPage, maxItemCount));
+                QueryRequestOptions queryRequestOptions = GetDefaultQueryRequestOptions(itemsPerPage: GetEffectivePageSize(-1,null));
                 FeedIterator<dynamic> query = container.container.GetItemQueryIterator<dynamic>(queryDefinition: cosmosDbQuery.CosmosQueryDefinition, requestOptions: queryRequestOptions);
-                return await ResultsFromFeedIterator(query, maxItemCount);
+                return await ResultsFromFeedIterator(query);
             }
             else
             {
@@ -455,7 +477,7 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
 
         }
 
-        public async Task<List<dynamic>> FindCountByDict(string CollectionName, Dictionary<string, object> dict, int itemsPerPage = -1, int? maxItemCount = null, string partitionKey = null)
+        public async Task<List<dynamic>> FindCountByDict(string CollectionName, Dictionary<string, object> dict, string partitionKey = null)
         {
             if (DocumentCollectionDict.TryGetValue(CollectionName, out CosmosModelInfo container))
             {
@@ -465,9 +487,9 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
                 dict.Remove("@DESC");
                 StringBuilder sql = new StringBuilder("select  value count(c)  from c");
                 CosmosDbQuery cosmosDbQuery = SQLHelperParametric.GetSQL(dict, sql);
-                QueryRequestOptions queryRequestOptions = GetDefaultQueryRequestOptions(itemsPerPage: GetEffectivePageSize(itemsPerPage, maxItemCount));
+                QueryRequestOptions queryRequestOptions = GetDefaultQueryRequestOptions(itemsPerPage: GetEffectivePageSize(-1,null));
                 FeedIterator<dynamic> query = container.container.GetItemQueryIterator<dynamic>(queryDefinition: cosmosDbQuery.CosmosQueryDefinition, requestOptions: queryRequestOptions);
-                return await ResultsFromFeedIterator(query, maxItemCount);
+                return await ResultsFromFeedIterator(query);
             }
             else
             {
@@ -475,29 +497,29 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
             }
         }
 
-        public async Task<List<T>> FindByParams<T>(Dictionary<string, object> dict, int itemsPerPage = -1, int? maxItemCount = null, string partitionKey = null, List<string> propertys = null) where T : ID
+        public async Task<List<T>> FindByParams<T>(Dictionary<string, object> dict, string partitionKey = null, List<string> propertys = null) where T : ID
         {
-            return await FindByDict<T>(dict, itemsPerPage, maxItemCount, partitionKey, propertys);
+            return await FindByDict<T>(dict,partitionKey, propertys);
         }
-        public async Task<List<T>> FindByDict<T>(Dictionary<string, object> dict, int itemsPerPage = -1, int? maxItemCount = null, string partitionKey = null,List<string> propertys = null) where T : ID
+        public async Task<List<T>> FindByDict<T>(Dictionary<string, object> dict, string partitionKey = null,List<string> propertys = null) where T : ID
         {
             StringBuilder sql;
             sql = SQLHelperParametric.GetSQLSelect(propertys);
 
             CosmosDbQuery cosmosDbQuery = SQLHelperParametric.GetSQL(dict, sql);
-            QueryRequestOptions queryRequestOptions = GetDefaultQueryRequestOptions(itemsPerPage: GetEffectivePageSize(itemsPerPage, maxItemCount));
+            QueryRequestOptions queryRequestOptions = GetDefaultQueryRequestOptions(itemsPerPage: GetEffectivePageSize(-1, null));
             return await ResultsFromQueryAndOptions<T>(cosmosDbQuery, queryRequestOptions);
         }
         
 
-        private async Task<List<T>> ResultsFromQueryAndOptions<T>(CosmosDbQuery cosmosDbQuery, QueryRequestOptions queryOptions, int? maxItemCount = null)
+        private async Task<List<T>> ResultsFromQueryAndOptions<T>(CosmosDbQuery cosmosDbQuery, QueryRequestOptions queryOptions)
         {
             CosmosModelInfo container = await InitializeCollection<T>();
             FeedIterator<T> query = container.container.GetItemQueryIterator<T>(
                 queryDefinition: cosmosDbQuery.CosmosQueryDefinition,
                 requestOptions: queryOptions);
 
-            return await ResultsFromFeedIterator(query, maxItemCount);
+            return await ResultsFromFeedIterator(query);
         }
         private int GetEffectivePageSize(int itemsPerPage, int? maxItemCount)
         {
@@ -537,10 +559,10 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
 
             return queryRequestOptions;
         }
-        public async Task<List<T>> FindLinq<T>(Expression<Func<T, bool>> query = null, Expression<Func<T, object>> order = null, bool isDesc = false, int itemsPerPage = -1, int? maxItemCount = null) where T : ID
+        public async Task<List<T>> FindLinq<T>(Expression<Func<T, bool>> query = null, Expression<Func<T, object>> order = null, bool isDesc = false) where T : ID
         {
             //QueryRequestOptions queryRequestOptions = GetQueryRequestOptions(itemsPerPage);
-            QueryRequestOptions queryRequestOptions = GetDefaultQueryRequestOptions(itemsPerPage: GetEffectivePageSize(itemsPerPage, maxItemCount));
+            QueryRequestOptions queryRequestOptions = GetDefaultQueryRequestOptions(itemsPerPage: GetEffectivePageSize(-1,null));
             FeedIterator<T> feedIterator;
             CosmosModelInfo container = await InitializeCollection<T>();
 
@@ -598,10 +620,10 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
             return await ResultsFromFeedIterator<T>(feedIterator);
         }
 
-        public async Task<List<T>> FindSQL<T>(string sql, Dictionary<string, object> Parameters = null, int itemsPerPage = -1, int? maxItemCount = null) where T : ID
+        public async Task<List<T>> FindSQL<T>(string sql, Dictionary<string, object> Parameters = null) where T : ID
         {
             CosmosModelInfo container = await InitializeCollection<T>();
-            QueryRequestOptions queryOptions = GetQueryRequestOptions(GetEffectivePageSize(itemsPerPage, maxItemCount));
+            QueryRequestOptions queryOptions = GetQueryRequestOptions(GetEffectivePageSize(-1, null));
             if (Parameters != null)
             {
                 CosmosDbQuery cosmosDbQuery = new CosmosDbQuery

+ 7 - 7
TEAMModelOS.SDK/Module/AzureCosmosDBV3/IAzureCosmosDBV3Repository.cs

@@ -24,7 +24,6 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
     /// </summary>
     public interface IAzureCosmosDBV3Repository
     {
-
         Task<T> Save<T>(T entity) where T : ID;
         Task<List<T>> SaveAll<T>(List<T> enyites) where T : ID;
         Task<IdPk> DeleteAsync<T>(T entity) where T : ID;
@@ -56,7 +55,7 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
         /// <param name="sql"></param>
         /// <param name="Parameters"></param>
         /// <returns></returns>
-        Task<List<T>> FindSQL<T>(string sql, Dictionary<string, object> Parameters = null, int itemsPerPage = -1, int? maxItemCount = null) where T : ID;
+        Task<List<T>> FindSQL<T>(string sql, Dictionary<string, object> Parameters = null) where T : ID;
         //Task<List<T>> FindSQL<T>(string sql, bool isPK) where T : ID;
 
         /// <summary>
@@ -74,15 +73,16 @@ namespace TEAMModelOS.SDK.Module.AzureCosmosDBV3
         /// <param name="itemsPerPage"></param>
         /// <param name="query"></param>
         /// <returns></returns>
-        Task<List<T>> FindLinq<T>(Expression<Func<T, bool>> query = null, Expression<Func<T, object>> order = null, bool isDesc = false, int itemsPerPage = -1, int? maxItemCount = null) where T : ID;
-        Task<List<T>> FindByParams<T>(Dictionary<string, object> dict, int itemsPerPage = -1, int? maxItemCount = null, string partitionKey = null, List<string> propertys = null) where T : ID;
-        Task<List<T>> FindByDict<T>(Dictionary<string, object> dict, int itemsPerPage = -1, int? maxItemCount = null, string partitionKey = null, List<string> propertys = null) where T : ID;
-        Task<List<dynamic>> FindByDict(string CollectionName, Dictionary<string, object> dict, int itemsPerPage = -1, int? maxItemCount = null, string partitionKey = null, List<string> propertys = null);
-        Task<List<dynamic>> FindCountByDict(string CollectionName, Dictionary<string, object> dict, int itemsPerPage = -1, int? maxItemCount = null, string partitionKey = null);
+        Task<List<T>> FindLinq<T>(Expression<Func<T, bool>> query = null, Expression<Func<T, object>> order = null, bool isDesc = false) where T : ID;
+        Task<List<T>> FindByParams<T>(Dictionary<string, object> dict, string partitionKey = null, List<string> propertys = null) where T : ID;
+        Task<List<T>> FindByDict<T>(Dictionary<string, object> dict, string partitionKey = null, List<string> propertys = null) where T : ID;
+        Task<List<dynamic>> FindByDict(string CollectionName, Dictionary<string, object> dict, string partitionKey = null, List<string> propertys = null);
+        Task<List<dynamic>> FindCountByDict(string CollectionName, Dictionary<string, object> dict, string partitionKey = null);
         Task InitializeDatabase();
         Task<T>FindById<T>(string id) where T : ID;
         Task<List<T>> FindByIds<T>(List<string> ids) where T : ID;
         Task<dynamic> FindById(string CollectionName, string id);
         Task<List<dynamic>> FindByIds (string CollectionName, List<string> ids);
+        Dictionary<string, CosmosModelInfo> GetCosmosModelInfo();
     }
 }

+ 1 - 1
TEAMModelOS.Service/Models/Syllabus/Knowledge.cs

@@ -13,7 +13,7 @@ namespace TEAMModelOS.Service.Models.Syllabus
     /// <summary>
     /// 知识点
     /// </summary>
-    [CosmosDB(RU = 400, Name = "Knowledge", Cache = true)]
+    [CosmosDB(RU = 400, Name = "Knowledge", Cache = true ,Monitor =true)]
     [ProtoContract]
     public class Knowledge: ID
     {

+ 82 - 0
TEAMModelOS.Service/Services/ChangeFeed/ChangeFeedInvoke.cs

@@ -0,0 +1,82 @@
+using DocumentFormat.OpenXml.Drawing;
+using Microsoft.Azure.Cosmos;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Context.Configuration;
+using TEAMModelOS.SDK.Helper.Common.JsonHelper;
+using TEAMModelOS.SDK.Module.AzureCosmosDBV3;
+
+namespace TEAMModelOS.Service.Services.ChangeFeed
+{
+    public class ChangeFeedInvoke : IChangeFeedInvoke
+    {
+        private readonly IAzureCosmosDBV3Repository azureCosmosDBV3Repository;
+        public ChangeFeedInvoke(IAzureCosmosDBV3Repository _IAzureCosmosDBV3Repository) {
+            azureCosmosDBV3Repository = _IAzureCosmosDBV3Repository;
+        }
+
+        public async  Task MonitorChangeFeed() {
+           Dictionary<string,CosmosModelInfo> dict =  azureCosmosDBV3Repository.GetCosmosModelInfo();
+            foreach (string CollectionName in dict.Keys)
+            {
+                if (CollectionName.Equals("leaseContainer")) 
+                { 
+                    continue; 
+                }
+                if (dict[CollectionName].monitor) {
+                    ChangeFeedProcessor changeFeedProcessor = dict[CollectionName]
+                        .container
+                        .GetChangeFeedProcessorBuilder<object>(CollectionName, async (changes, token) => await ProcessChanges(changes, CollectionName, dict[CollectionName].type))
+                        .WithInstanceName(CollectionName)
+                        .WithLeaseContainer(dict["leaseContainer"].container)
+                        .Build();
+                    await changeFeedProcessor.StartAsync();
+                }
+               
+            }
+        }
+        /// <summary>
+        /// 获取到监听更改的数据
+        /// </summary>
+        /// <param name="changes"></param>
+        /// <param name="type"></param>
+        /// <param name="CollectionName"></param>
+        /// <returns></returns>
+        private async Task ProcessChanges(IReadOnlyCollection<object> changes,  string CollectionName,Type type)
+        {
+            Assembly assembly = Assembly.GetAssembly(typeof(IChangeFeedService<>));
+            Type[] types = assembly.GetTypes();
+            List<Type> list = new List<Type>();
+            foreach (Type item in types)
+            {
+                if (item.IsInterface) continue;//判断是否是接口
+                Type[] ins = item.GetInterfaces();
+                foreach (Type ty in ins)
+                {
+                    Type t = typeof(IChangeFeedService<>);
+                    t = t.MakeGenericType(type);
+                    if (ty == t)
+                    {
+                        list.Add(item);
+                    }
+                }
+            }
+            foreach (Type item in list) {
+                object obj = Activator.CreateInstance(item);//创建一个obj对象
+                MethodInfo mi = item.GetMethod("Processor");
+                Type t = typeof(IReadOnlyCollection<>);
+                t = t.MakeGenericType(type);
+               // object objt = Activator.CreateInstance(t);
+                object bjt= JsonSerializer.Deserialize(changes.ToApiJson(), t);
+                mi.Invoke(obj, new object []{ bjt });//调用方法
+               // mi = item.GetMethod("BaseClass_VoidPublic");
+               // string s = mi.Invoke(obj, null) as string;//调用有返回值的方法,使用 as 关键字 转换返回的类型
+            }
+        }
+    }
+}

+ 13 - 0
TEAMModelOS.Service/Services/ChangeFeed/IChangeFeedInvoke.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Context.Configuration;
+
+namespace TEAMModelOS.Service.Services.ChangeFeed
+{
+   public interface IChangeFeedInvoke //:IBusinessService
+    {
+          Task MonitorChangeFeed();
+    }
+}

+ 12 - 0
TEAMModelOS.Service/Services/ChangeFeed/IChangeFeedService.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TEAMModelOS.Service.Services.ChangeFeed
+{
+    public interface IChangeFeedService<T>
+    {
+        void Processor(IReadOnlyCollection<T> changes);
+    }
+}

+ 17 - 0
TEAMModelOS.Service/Services/ChangeFeed/KnowledgeChangeFeed.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Helper.Common.JsonHelper;
+using TEAMModelOS.Service.Models.Syllabus;
+
+namespace TEAMModelOS.Service.Services.ChangeFeed
+{
+    public class KnowledgeChangeFeed : IChangeFeedService<Knowledge>
+    {
+        public void Processor(IReadOnlyCollection<Knowledge> changes)
+        {
+            Console.WriteLine(changes.ToApiJson());
+        }
+    }
+}

+ 9 - 0
TEAMModelOS.Service/TEAMModelOS.Model.xml

@@ -1112,6 +1112,15 @@
             1默认为普通页面,2为题目
             </summary>
         </member>
+        <member name="M:TEAMModelOS.Service.Services.ChangeFeed.ChangeFeedInvoke.ProcessChanges(System.Collections.Generic.IReadOnlyCollection{System.Object},System.String,System.Type)">
+            <summary>
+            获取到监听更改的数据
+            </summary>
+            <param name="changes"></param>
+            <param name="type"></param>
+            <param name="CollectionName"></param>
+            <returns></returns>
+        </member>
         <member name="M:TEAMModelOS.Service.Services.Exam.Implements.HtmlAnalyzeService.ConvertTest(System.String)">
             <summary>
             解析题型

+ 287 - 269
TEAMModelOS/ClientApp/src/common/UploadFile.vue

@@ -1,284 +1,302 @@
 <template>
-  <div>
-    <Upload type="drag" action="" multiple :before-upload="beforeUpload" class="upload-wrap">
-      <p style="margin:20px 0px; margin-top:80px;font-size:30px;">{{$t('teachContent.uploadText')}}</p>
-      <Icon type="ios-cloud-upload" size="80" style="margin-bottom:80px;" />
-    </Upload>
-    <div class="upload-file-box">
-      <div class="upload-file-item" v-for="(item,index) in uploadedList">
-        <Icon type="ios-folder" />
-        <p class="upload-file-name">{{item.fileName}}</p>
-        <img width="25" style="float:right;" src="@/assets/loading/loading3.svg" v-if="item.status == 0"/>
-        <Icon type="md-checkmark" style="float:right;" color="aqua" size="20" v-else-if="item.status == 1"/>
-        <Icon type="md-close" style="float:right;" color="red" size="20" v-else-if="item.status == 2"/>
-      </div>
+    <div>
+        <Upload type="drag" action="" multiple :before-upload="beforeUpload" class="upload-wrap" :format="format" :max-size="maxSize" :on-format-error="onFormatError" :on-exceeded-size="sizeError">
+            <p style="margin:20px 0px; margin-top:80px;font-size:30px;">{{$t('teachContent.uploadText')}}</p>
+            <Icon type="ios-cloud-upload" size="80" style="margin-bottom:80px;" />
+        </Upload>
+        <div class="upload-file-box">
+            <div class="upload-file-item" v-for="(item,index) in uploadedList">
+                <Icon type="ios-folder" />
+                <p class="upload-file-name">{{item.fileName}}</p>
+                <img width="25" style="float:right;" src="@/assets/loading/loading3.svg" v-if="item.status == 0" />
+                <Icon type="md-checkmark" style="float:right;" color="aqua" size="20" v-else-if="item.status == 1" />
+                <Icon type="md-close" style="float:right;" color="red" size="20" v-else-if="item.status == 2" />
+            </div>
+        </div>
     </div>
-  </div>
 </template>
 <script>
-  import sha1 from 'js-sha1'
-  require('@/utils/azure-storage-blob.js')
-  export default {
-    data() {
-      return {
-        urlString: '',
-        containerURL: undefined,
-        sasString: '',
-        uploadedList: [],
-        contentTypes: [
-          ['JPG', 'JPEG', 'PNG', 'GIF'],
-          ['AVI', 'MP4'],
-          ['PPT', 'PPTX', 'DOC', 'DOCX', 'PDF', 'XLS', 'XLSX', 'HTE', 'HETX']
-        ]
-      }
-    },
-    props: {
-      accountName: {
-        default: 'teammodelostest',
-        type: String
-      },
-      containerName: {
-        default: 'teammodelos',
-        type: String
-      },
-      pathName: {
-        default: '',
-        type: String
-      },
-      quality: {
-        default: 0.2,
-        type: Number
-      }
+    import sha1 from 'js-sha1'
+    require('@/utils/azure-storage-blob.js')
+    export default {
+        data() {
+            return {
+                urlString: '',
+                containerURL: undefined,
+                sasString: '',
+                uploadedList: [],
+                contentTypes: [
+                    ['JPG', 'JPEG', 'PNG', 'GIF'],
+                    ['AVI', 'MP4'],
+                    ['PPT', 'PPTX', 'DOC', 'DOCX', 'PDF', 'XLS', 'XLSX', 'HTE', 'HETX']
+                ],
 
-    },
-    methods: {
-      checkSize() {
-
-      },
-      initBlob() {
-        this.containerURL = new window.azblob.ContainerURL(
-          // `https://${this.accountName}.blob.core.chinacloudapi.cn/${this.containerName}/${this.pathName}?${this.sasString}`,
-          this.urlString + '/' + this.pathName + this.sasString,
-          window.azblob.StorageURL.newPipeline(new window.azblob.AnonymousCredential())
-        )
-      },
-      dataURLtoFile(dataurl, filename) {
-        var arr = dataurl.split(','); var mime = arr[0].match(/:(.*?);/)[1]
-          var bstr = atob(arr[1]); var n = bstr.length; var u8arr = new Uint8Array(n)
-        while (n--) {
-          u8arr[n] = bstr.charCodeAt(n)
-        }
-        return new File([u8arr], filename, { type: mime })
-      },
-      compressFile(file, fileInfo) {
-        if (fileInfo.type == 'picture') {
-          let reader = new FileReader()
-          reader.readAsDataURL(file)
-          reader.onload = (e) => {
-            let dataUrl = e.target.result
-            let img = new Image()
-            img.src = dataUrl
-            img.onload = () => {
-              let canvas = document.createElement('canvas')
-              canvas.width = 300
-              canvas.height = 300 * img.height / img.width
-              let ctx = canvas.getContext('2d')
-              ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height)
-              let newImgData = canvas.toDataURL(file.type, this.quality)
-              var resultFile = this.dataURLtoFile(newImgData, file.name)
-              this.initBlob()
-              const compressBlobURL = window.azblob.BlockBlobURL.fromContainerURL(
-                this.containerURL,
-                'compress' + file.name
-              )
-              let options = {
-                blockSize: 10 * 1024 * 1024
-              }
-              window.azblob.uploadBrowserDataToBlockBlob(
-                window.azblob.Aborter.none,
-                resultFile,
-                compressBlobURL,
-                options
-              ).then(
-                res => {
-                  // fileInfo['compressUrl'] = `https://${this.accountName}.blob.core.chinacloudapi.cn/${this.containerName}/${this.pathName}/compress${file.name}`
-                  fileInfo['compressUrl'] = this.urlString + '/' + this.pathName + '/compress' + file.name
-                  this.$emit('successData', fileInfo)
-                },
-                err => {
-                  fileInfo['status'] = 2
-                  console.log(err)
-                }
-              )
             }
-          }
-        } else {
-          let video = document.createElement('video')
-          video.setAttribute('width', '300')
-          video.setAttribute('controls', 'controls')
-          video.setAttribute('crossOrigin', '*')
-          video.setAttribute('src', fileInfo.blobUrl + this.sasString)
-          video.addEventListener('loadeddata', () => {
-            let canvas = document.createElement('canvas')
-            canvas.width = 300
-            canvas.height = 300 * video.videoHeight / video.videoWidth
-            let ctx = canvas.getContext('2d')
-            ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, canvas.width, canvas.height)
-            let newVideoData = canvas.toDataURL('image/png', this.quality)
-            let resultFile = this.dataURLtoFile(newVideoData, file.name)
-            this.initBlob()
-            let posterName = 'compress' + file.name.replace(fileInfo.extension, 'png')
-            const compressBlobURL = window.azblob.BlockBlobURL.fromContainerURL(
-              this.containerURL,
-              posterName
-            )
-            window.azblob.uploadBrowserDataToBlockBlob(
-              window.azblob.Aborter.none,
-              resultFile,
-              compressBlobURL
-            ).then(
-              res => {
-                // fileInfo['compressUrl'] = `https://${this.accountName}.blob.core.chinacloudapi.cn/${this.containerName}/${this.pathName}/${posterName}`
-                fileInfo['compressUrl'] = this.urlString + '/' + this.pathName + '/' + posterName
-                this.$emit('successData', fileInfo)
-              },
-              err => {
-                fileInfo['status'] = 2
-                console.log(err)
-              }
-            )
-          })
-        }
-      },
-      uploadToBlob(file) {
-        const blockBlobURL = window.azblob.BlockBlobURL.fromContainerURL(
-          this.containerURL,
-          file.name
-        )
-        let fileItem = {}
-        fileItem['fileName'] = file.name
-        // fileItem['blobUrl'] = `https://${this.accountName}.blob.core.chinacloudapi.cn/${this.containerName}/${this.pathName}/${file.name}`
-        fileItem['blobUrl'] = this.urlString + '/' + this.pathName + '/' + file.name
-        fileItem['extension'] = file.name.substring(file.name.lastIndexOf('.') + 1)
-        fileItem['size'] = file.size
-        fileItem['status'] = 0
-        fileItem['contentType'] = file.type
-        let type = 'other'
-        for (let item in this.contentTypes) {
-          if (this.contentTypes[item].indexOf(fileItem.extension.toUpperCase()) !== -1) {
-            switch (true) {
-              case item == 0:
-                type = 'picture'
-                break
-              case item == 1:
-                type = 'video'
-                break
-              case item == 2:
-                type = 'document'
-                break
-              default:
-                break
+        },
+        props: {
+            accountName: {
+                default: 'teammodelostest',
+                type: String
+            },
+            containerName: {
+                default: 'teammodelos',
+                type: String
+            },
+            pathName: {
+                default: '',
+                type: String
+            },
+            quality: {
+                default: 0.2,
+                type: Number
+            },
+            maxSize: {
+                default: 4,
+                type: Number
+            },
+            format: {
+                default: () => {
+                    return []
+                },
+                type: Array
             }
-            break
-          }
-        }
-        fileItem['type'] = type
-        this.uploadedList.push(fileItem)
-        window.azblob.uploadBrowserDataToBlockBlob(
-          window.azblob.Aborter.none,
-          file,
-          blockBlobURL
-        ).then(
-          res => {
-            let reader = new FileReader()
-            reader.readAsArrayBuffer(file)
-            // reader.readAsArrayBuffer(file)
-            reader.onload = (ev) => {
-              try {
-                let fileRes = ev.target.result
-                let str = sha1(fileRes)
-                let index = this.getIndex(this.uploadedList, fileItem)
-                this.uploadedList[index].status = 1
-                this.uploadedList[index]['createTime'] = Date.parse(res.lastModified)
-                this.uploadedList[index]['sha1Code'] = str
-                if (this.uploadedList[index].type == 'picture' || this.uploadedList[index].type == 'video') {
-                  this.compressFile(file, this.uploadedList[index])
+        },
+        methods: {
+            onFormatError() {
+                this.$Message.error("含不支持的文件类型")
+            },
+            sizeError() {
+                this.$Message.error("文件大小超出限制")
+            },
+            checkSize() {
+
+            },
+            initBlob() {
+                this.containerURL = new window.azblob.ContainerURL(
+                    // `https://${this.accountName}.blob.core.chinacloudapi.cn/${this.containerName}/${this.pathName}?${this.sasString}`,
+                    this.urlString + '/' + this.pathName + this.sasString,
+                    window.azblob.StorageURL.newPipeline(new window.azblob.AnonymousCredential())
+                )
+            },
+            dataURLtoFile(dataurl, filename) {
+                var arr = dataurl.split(','); var mime = arr[0].match(/:(.*?);/)[1]
+                var bstr = atob(arr[1]); var n = bstr.length; var u8arr = new Uint8Array(n)
+                while (n--) {
+                    u8arr[n] = bstr.charCodeAt(n)
+                }
+                return new File([u8arr], filename, { type: mime })
+            },
+            compressFile(file, fileInfo) {
+                if (fileInfo.type == 'picture') {
+                    let reader = new FileReader()
+                    reader.readAsDataURL(file)
+                    reader.onload = (e) => {
+                        let dataUrl = e.target.result
+                        let img = new Image()
+                        img.src = dataUrl
+                        img.onload = () => {
+                            let canvas = document.createElement('canvas')
+                            canvas.width = 300
+                            canvas.height = 300 * img.height / img.width
+                            let ctx = canvas.getContext('2d')
+                            ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height)
+                            let newImgData = canvas.toDataURL(file.type, this.quality)
+                            var resultFile = this.dataURLtoFile(newImgData, file.name)
+                            this.initBlob()
+                            const compressBlobURL = window.azblob.BlockBlobURL.fromContainerURL(
+                                this.containerURL,
+                                'compress' + file.name
+                            )
+                            let options = {
+                                blockSize: 10 * 1024 * 1024
+                            }
+                            window.azblob.uploadBrowserDataToBlockBlob(
+                                window.azblob.Aborter.none,
+                                resultFile,
+                                compressBlobURL,
+                                options
+                            ).then(
+                                res => {
+                                    // fileInfo['compressUrl'] = `https://${this.accountName}.blob.core.chinacloudapi.cn/${this.containerName}/${this.pathName}/compress${file.name}`
+                                    fileInfo['compressUrl'] = this.urlString + '/' + this.pathName + '/compress' + file.name
+                                    this.$emit('successData', fileInfo)
+                                },
+                                err => {
+                                    fileInfo['status'] = 2
+                                    console.log(err)
+                                }
+                            )
+                        }
+                    }
                 } else {
-                  this.$emit('successData', this.uploadedList[index])
+                    let video = document.createElement('video')
+                    video.setAttribute('width', '300')
+                    video.setAttribute('controls', 'controls')
+                    video.setAttribute('crossOrigin', '*')
+                    video.setAttribute('src', fileInfo.blobUrl + this.sasString)
+                    video.addEventListener('loadeddata', () => {
+                        let canvas = document.createElement('canvas')
+                        canvas.width = 300
+                        canvas.height = 300 * video.videoHeight / video.videoWidth
+                        let ctx = canvas.getContext('2d')
+                        ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, canvas.width, canvas.height)
+                        let newVideoData = canvas.toDataURL('image/png', this.quality)
+                        let resultFile = this.dataURLtoFile(newVideoData, file.name)
+                        this.initBlob()
+                        let posterName = 'compress' + file.name.replace(fileInfo.extension, 'png')
+                        const compressBlobURL = window.azblob.BlockBlobURL.fromContainerURL(
+                            this.containerURL,
+                            posterName
+                        )
+                        window.azblob.uploadBrowserDataToBlockBlob(
+                            window.azblob.Aborter.none,
+                            resultFile,
+                            compressBlobURL
+                        ).then(
+                            res => {
+                                // fileInfo['compressUrl'] = `https://${this.accountName}.blob.core.chinacloudapi.cn/${this.containerName}/${this.pathName}/${posterName}`
+                                fileInfo['compressUrl'] = this.urlString + '/' + this.pathName + '/' + posterName
+                                this.$emit('successData', fileInfo)
+                            },
+                            err => {
+                                fileInfo['status'] = 2
+                                console.log(err)
+                            }
+                        )
+                    })
                 }
-              } catch (e) {
-                console.log(e)
-              }
-            }
-          },
-          err => {
-            fileItem['status'] = 2
-            console.log(err)
-          }
-        )
-        return false
-      },
-      beforeUpload(file) {
-        let url = file.name
-        this.$api.uploadFile.getContainerSAS().then(
-          (res) => {
-            if (res.error == null) {
-              this.sasString = res.result.data.SAS
-              this.urlString = res.result.data.Url
-              this.initBlob()
-              this.uploadToBlob(file)
-            } else {
-              alert('API error!')
+            },
+            uploadToBlob(file) {
+                const blockBlobURL = window.azblob.BlockBlobURL.fromContainerURL(
+                    this.containerURL,
+                    file.name
+                )
+                let fileItem = {}
+                fileItem['fileName'] = file.name
+                // fileItem['blobUrl'] = `https://${this.accountName}.blob.core.chinacloudapi.cn/${this.containerName}/${this.pathName}/${file.name}`
+                fileItem['blobUrl'] = this.urlString + '/' + this.pathName + '/' + file.name
+                fileItem['extension'] = file.name.substring(file.name.lastIndexOf('.') + 1)
+                fileItem['size'] = file.size
+                fileItem['status'] = 0
+                fileItem['contentType'] = file.type
+                let type = 'other'
+                for (let item in this.contentTypes) {
+                    if (this.contentTypes[item].indexOf(fileItem.extension.toUpperCase()) !== -1) {
+                        switch (true) {
+                            case item == 0:
+                                type = 'picture'
+                                break
+                            case item == 1:
+                                type = 'video'
+                                break
+                            case item == 2:
+                                type = 'document'
+                                break
+                            default:
+                                break
+                        }
+                        break
+                    }
+                }
+                fileItem['type'] = type
+                this.uploadedList.push(fileItem)
+                window.azblob.uploadBrowserDataToBlockBlob(
+                    window.azblob.Aborter.none,
+                    file,
+                    blockBlobURL
+                ).then(
+                    res => {
+                        let reader = new FileReader()
+                        reader.readAsArrayBuffer(file)
+                        // reader.readAsArrayBuffer(file)
+                        reader.onload = (ev) => {
+                            try {
+                                let fileRes = ev.target.result
+                                let str = sha1(fileRes)
+                                let index = this.getIndex(this.uploadedList, fileItem)
+                                this.uploadedList[index].status = 1
+                                this.uploadedList[index]['createTime'] = Date.parse(res.lastModified)
+                                this.uploadedList[index]['sha1Code'] = str
+                                if (this.uploadedList[index].type == 'picture' || this.uploadedList[index].type == 'video') {
+                                    this.compressFile(file, this.uploadedList[index])
+                                } else {
+                                    this.$emit('successData', this.uploadedList[index])
+                                }
+                            } catch (e) {
+                                console.log(e)
+                            }
+                        }
+                    },
+                    err => {
+                        fileItem['status'] = 2
+                        console.log(err)
+                    }
+                )
+                return false
+            },
+            beforeUpload(file) {
+                let url = file.name
+                this.$api.uploadFile.getContainerSAS().then(
+                    (res) => {
+                        if (res.error == null) {
+                            this.sasString = res.result.data.SAS
+                            this.urlString = res.result.data.Url
+                            this.initBlob()
+                            this.uploadToBlob(file)
+                        } else {
+                            alert('API error!')
+                        }
+                    },
+                    (err) => {
+                        alert('API error!')
+                    }
+                )
+                return false
+            },
+            getIndex(_arr, _obj) {
+                var len = _arr.length
+                for (let i = 0; i < len; i++) {
+                    if (this.isObjEqual(_arr[i], _obj)) {
+                        return parseInt(i)
+                    }
+                }
+                return -1
+            },
+            isObjEqual(o1, o2) {
+                var props1 = Object.keys(o1)
+                var props2 = Object.keys(o2)
+                if (props1.length != props2.length) {
+                    return false
+                }
+                for (var i = 0, max = props1.length; i < max; i++) {
+                    var propName = props1[i]
+                    if (o1[propName] !== o2[propName]) {
+                        return false
+                    }
+                }
+                return true
             }
-          },
-          (err) => {
-            alert('API error!')
-          }
-        )
-        return false
-      },
-      getIndex(_arr, _obj) {
-        var len = _arr.length
-        for (let i = 0; i < len; i++) {
-          if (this.isObjEqual(_arr[i], _obj)) {
-            return parseInt(i)
-          }
-        }
-        return -1
-      },
-      isObjEqual(o1, o2) {
-        var props1 = Object.keys(o1)
-        var props2 = Object.keys(o2)
-        if (props1.length != props2.length) {
-          return false
-        }
-        for (var i = 0, max = props1.length; i < max; i++) {
-          var propName = props1[i]
-          if (o1[propName] !== o2[propName]) {
-            return false
-          }
-        }
-        return true
-      }
-    },
-    mounted() {
-      this.uploadedList = []
-    },
-    created() {}
-  }
+        },
+        mounted() {
+            this.uploadedList = []
+        },
+        created() { }
+    }
 
 </script>
 <style scoped>
-  .upload-file-item {
-    cursor:pointer;
-    color:#909090;
-    margin-top:5px;
-  }
-    .upload-file-item:hover {
-      color:white;
+    .upload-file-item {
+        cursor: pointer;
+        color: #909090;
+        margin-top: 5px;
+    }
+
+        .upload-file-item:hover {
+            color: white;
+        }
+
+    .upload-file-name {
+        display: inline-block;
+        margin-left: 10px;
     }
-  .upload-file-name {
-    display:inline-block;
-    margin-left:10px;
-  }
 </style>

+ 7 - 3
TEAMModelOS/ClientApp/src/view/selflearning/CreateSelfLearn.vue

@@ -187,9 +187,9 @@
                     isOrder: [
                         { required: true, message: '请选择模式', trigger: 'change' }
                     ],
-                    target: [
-                        { required: true, message: '请选择发布对象', trigger: 'change' }
-                    ],
+                    //target: [
+                    //    { required: true, message: '请选择发布对象', trigger: 'change' }
+                    //],
                     introduce: [
                         { required: true, message: '请完善说明信息', trigger: 'change' }
                     ]
@@ -473,6 +473,10 @@
             }
         },
         created() {
+            let routerData = this.$route.params.selfLearnInfo
+            if (routerData != undefined) {
+                this.selfLearnInfo = routerData
+            }
         }
     }
 </script>

+ 5 - 3
TEAMModelOS/ClientApp/src/view/selflearning/LearnProgress.vue

@@ -3,11 +3,11 @@
         <div class="learn-progress-filter dark-iview-select">
             <span class="filter-label">搜学生:</span>
             <Select v-model="model11" filterable style="display:inline-block;width:200px;" size="small">
-                <Option v-for="item in cityList" :value="item.value" :key="item.value">{{ item.label }}</Option>
+                <Option v-for="item in studentList" :value="item.value" :key="item.value">{{ item.label }}</Option>
             </Select>
             <span  class="filter-label" style="margin-left:30px;">选班级:</span>
             <Select v-model="model11" filterable style="display:inline-block;width:200px;" size="small">
-                <Option v-for="item in cityList" :value="item.value" :key="item.value">{{ item.label }}</Option>
+                <Option v-for="item in classroomList" :value="item.value" :key="item.value">{{ item.label }}</Option>
             </Select>
             <div class="right-filter-wrap">
                 <div class="radio-box-wrap">
@@ -18,7 +18,7 @@
                     </div>
                     <div :class="currentView == 1 ? 'radio-box-item active-radio-box-item':'radio-box-item'" @click="setCurrentView(1)">
                         <Tooltip content="学习自学概况" placement="bottom" theme="light">
-                            <Icon type="md-people" size="15" />
+                            <Icon type="md-person" size="15" />
                         </Tooltip>
                     </div>
                 </div>
@@ -41,6 +41,8 @@
         data() {
             return {
                 currentView: 0,
+                studentList: [],
+                classroomList: [],
                 columns: [
                     {
                         title: '学习阶段',

+ 105 - 5
TEAMModelOS/ClientApp/src/view/selflearning/ManageSelfLearn.less

@@ -66,16 +66,33 @@
 .self-learn-main {
     width: 100%;
     height: 100%;
-    .self-learn-main-body{
-        width:100%;
-        height:~"calc(100% - 45px)";
-    } 
+
+    .self-learn-main-body {
+        width: 100%;
+        height: ~"calc(100% - 45px)";
+    }
+
     .self-learn-main-header {
         height: 45px;
         width: 100%;
         line-height: 45px;
         border-bottom: 1px solid @borderColor;
 
+        .self-learn-header-right {
+            float: right;
+
+            .main-bar-action-btn {
+                color: white;
+                float: right;
+                margin-right: 50px;
+                cursor: pointer;
+
+                &:hover {
+                    color: aqua;
+                }
+            }
+        }
+
         .main-header-tab {
             color: @second-textColor;
             margin-right: 35px;
@@ -85,4 +102,87 @@
             line-height: 25px;
         }
     }
-}
+}
+
+.box-heaher {
+    height: 40px;
+    line-height: 40px;
+    color: @second-textColor;
+    border-bottom: 1px solid @borderColor;
+}
+
+.self-learn-main-body {
+    .base-info-box {
+        color: white;
+
+        .base-info-box-header {
+            .box-heaher;
+        }
+
+        .base-info-detail {
+            padding-top: 10px;
+
+            .base-info-item {
+                margin-bottom: 20px;
+            }
+
+            .base-info-label {
+                color: @second-textColor;
+                display: inline-block;
+                width: 80px;
+                font-size: 15px;
+            }
+
+            .base-info-value {
+                color: white;
+                font-size: 16px;
+                border-bottom: 1px solid #202020;
+                padding-bottom: 4px;
+            }
+        }
+    }
+
+    .learn-steps-box {
+        color: white;
+        padding-left: 15px;
+
+        .learn-steps-box-header {
+            .box-heaher;
+        }
+
+        .learn-steps-detail {
+            .self-learn-step-item {
+                padding: 10px 0px;
+                font-size: 16px;
+                cursor: pointer;
+                border-bottom: 1px solid @borderColor;
+            }
+        }
+    }
+
+    .learn-content-box {
+        color: white;
+        padding-left: 15px;
+        width: 100%;
+        height: 100%;
+
+        .learn-content-box-header {
+            .box-heaher;
+        }
+
+        .learn-content-detail {
+            width: 100%;
+            height: ~"calc(100% - 40px)";
+            padding-top: 15px;
+
+            .self-learn-content-label {
+                color: white;
+                line-height: 13px;
+                padding: 5px;
+                padding-left: 8px;
+                border-left: 2px solid white;
+                background: #333333;
+            }
+        }
+    }
+}

+ 171 - 11
TEAMModelOS/ClientApp/src/view/selflearning/ManageSelfLearn.vue

@@ -6,47 +6,205 @@
                     <span>自学活动列表</span>
                     <Icon type="md-add" class="to-create-icon" @click="goToCreate" />
                 </div>
-                <div :class="currentLearnIndex == index ? 'order-learn-item block-bg block-bg-active':'order-learn-item block-bg' " v-for="(item,index) in selfLearnList" @click="selectOrderLearn(index)">
+                <div v-for="(item,index) in selfLearnList" @click="selectOrderLearn(index)" :class="currentLearnIndex == index ? 'order-learn-item block-bg block-bg-active':'order-learn-item block-bg' ">
                     <p class="order-learn-name">{{item.name}}</p>
-                    <div class="order-learn-steps">
-                        <span><Icon type="md-filing" style="margin-right:8px;" />闯关模式:</span>
-                        <span>{{item.isOrder == 1 ? '是':'否'}}</span>
-                    </div>
                     <div class="order-learn-steps">
                         <span><Icon type="ios-cube" style="margin-right:8px;" />学习阶段:</span>
                         <span>{{item.steps.length}}</span>
                     </div>
+                    <div class="order-learn-steps">
+                        <span><Icon type="ios-cube" style="margin-right:8px;" />活动状态:</span>
+                        <span style="color:aqua;">{{index % 3 == 0 ? '未发布': index % 3 == 1 ? '进行中':'已结束' }}</span>
+                    </div>
                 </div>
                 <NoData v-if="selfLearnList.length == 0"></NoData>
             </vuescroll>
         </div>
         <div class="self-learn-main">
             <div class="self-learn-main-header">
-                <span :class="currentTabIndex == 0 ? 'main-header-tab line-bottom line-bottom-active':'main-header-tab line-bottom'" @click="selectTab(0)">基础信息</span>
+                <span :class="currentTabIndex == 0 ? 'main-header-tab line-bottom line-bottom-active':'main-header-tab line-bottom'" @click="selectTab(0)">活动信息</span>
                 <span :class="currentTabIndex == 1 ? 'main-header-tab line-bottom line-bottom-active':'main-header-tab line-bottom'" @click="selectTab(1)">学习进度</span>
                 <span :class="currentTabIndex == 2 ? 'main-header-tab line-bottom line-bottom-active':'main-header-tab line-bottom'" @click="selectTab(2)">学习数据</span>
+                <div class="self-learn-header-right">
+                    <span class="main-bar-action-btn" @click="editInfo()">
+                        <Icon type="ios-create-outline" size="20" />
+                        编辑内容
+                    </span>
+                </div>
             </div>
             <div class="self-learn-main-body">
+                <div v-if="currentTabIndex == 0" class="dark-iview-split" style="width:100%;height:100%;">
+                    <Split v-model="split1">
+                        <div slot="left" class="base-info-box">
+                            <div class="base-info-box-header">
+                                <span>基础信息</span>
+                            </div>
+                            <div class="base-info-detail">
+                                <div class="base-info-item">
+                                    <span class="base-info-label">
+                                        名称:
+                                    </span>
+                                    <p class="base-info-value">
+                                        {{selfLearnList[currentLearnIndex].name}}
+                                    </p>
+                                </div>
+                                <div class="base-info-item">
+                                    <span class="base-info-label">
+                                        学段:
+                                    </span>
+
+                                    <p class="base-info-value">
+                                        <!--{{selfLearnList[currentLearnIndex].periodCode}}-->
+                                        小学
+                                    </p>
+                                </div>
+                                <div class="base-info-item">
+                                    <span class="base-info-label">
+                                        学科:
+                                        
+
+                                        
+
+                                    </span>
+                                    
+
+                                    
+                                    <p class="base-info-value">
+                                        {{selfLearnList[currentLearnIndex].subjectCode}}
+                                    </p>
+                                </div>
+                                <div class="base-info-item">
+                                    <span class="base-info-label">
+                                        学习对象:
+                                    </span>
+                                    <p class="base-info-value">
+                                        {{selfLearnList[currentLearnIndex].target.join(',')}}
+                                    </p>
+                                </div>
+                                <div class="base-info-item">
+                                    <span class="base-info-label">
+                                        闯关模式:
+                                    </span>
+                                    <p class="base-info-value">
+                                        {{selfLearnList[currentLearnIndex].isOrder == 1 ? '是':'否'}}
+                                    </p>
+                                </div>
+                                <div class="base-info-item">
+                                    <span class="base-info-label">
+                                        结束时间:
+                                    </span>
+                                    <p class="base-info-value">
+                                        暂未设置
+                                    </p>
+                                </div>
+                                <div class="base-info-item">
+                                    <p class="base-info-label">
+                                        备注说明:
+                                    </p>
+                                    <p class="base-info-value">
+                                        {{selfLearnList[currentLearnIndex].introduce}}
+                                    </p>
+                                </div>
+                            </div>
+                        </div>
+                        <div slot="right" class="demo-split-pane" style="width:100%;height:100%;">
+                            <Split v-model="split2">
+                                <div slot="left" class="learn-steps-box">
+                                    <div class="learn-steps-box-header">
+                                        <span>
+                                            学习阶段
+                                        </span>
+                                    </div>
+                                    <div class="learn-steps-detail">
+                                        <div v-for="(item,index) in selfLearnList[currentLearnIndex].steps" @click="selectStep(index)" :class="currentStepIndex == index ? 'self-learn-step-item block-bg block-bg-active':'self-learn-step-item block-bg'">
+                                            <span>
+                                                {{item.name}}
+                                            </span>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div slot="right" class="learn-content-box">
+                                    <div class="learn-content-box-header">
+                                        <span>
+                                            学习内容
+                                        </span>
+                                    </div>
+                                    <div class="learn-content-detail">
+                                        <vuescroll>
+                                            <p class="self-learn-content-label">
+                                                内容:{{selfLearnList.length == 0 ? 0 : selfLearnList[currentLearnIndex].steps[currentStepIndex].resource.length}}
+                                            </p>
+                                            <div style="min-height:220px;">
+                                                <Loading v-show="isLoading"></Loading>
+                                                <ContentFileList v-show="!isLoading" :class="isLoading ? '':'animated fadeIn'" :resources="selfLearnList[currentLearnIndex].steps[currentStepIndex].resource"></ContentFileList>
+                                            </div>
+                                            <p class="self-learn-content-label" style="margin-bottom:15px;">
+                                                题目:{{selfLearnList.length == 0 ? 0 : selfLearnList[currentLearnIndex].steps[currentStepIndex].item.length}}
+                                            </p>
+                                            <div>
+                                                <NoData v-if="selfLearnList.length == 0" style="margin-top:120px;"></NoData>
+                                                <Loading v-show="isLoading"></Loading>
+                                                <QuestionList v-show="!isLoading" :class="isLoading ? '':'animated fadeIn'" :questions="selfLearnList[currentLearnIndex].steps[currentStepIndex].item" style="margin-top:12px;"></QuestionList>
+                                            </div>
+                                        </vuescroll>
+                                    </div>
+                                </div>
+                            </Split>
+                        </div>
+                    </Split>
+                </div>
                 <LearnProgress v-if="currentTabIndex == 1"></LearnProgress>
             </div>
         </div>
+        <Modal v-model="editStatus"
+               title="编辑内容"
+               @on-ok="confirmEdit">
+            <p>确认跳转到自主学习活动编辑页面?</p>
+        </Modal>
     </div>
 </template>
 <script>
     import NoData from '@/common/NoData.vue'
     import LearnProgress from './LearnProgress.vue'
+    import Loading from '@/common/Loading.vue'
+    import QuestionList from '@/components/learnactivity/QuestionList.vue'
+    import ContentFileList from '@/components/learnactivity/ContentFileList.vue'
     export default {
         components: {
-            NoData,LearnProgress
+            NoData, LearnProgress, Loading, QuestionList, ContentFileList
         },
         data() {
             return {
-                currentTabIndex:0,
+                isLoading: false,
+                editStatus: false,
+                split1: 0.18,
+                split2: 0.2,
+                currentTabIndex: 0,
                 currentLearnIndex: 0,
+                currentStepIndex: 0,
                 selfLearnList: []
             }
         },
         methods: {
+            confirmEdit() {
+                let selfLearnInfo = this.selfLearnList[this.currentLearnIndex]
+                this.$router.push({
+                    name: 'createSelfLearn',
+                    params: {
+                        selfLearnInfo
+                    }
+                })
+            },
+            editInfo() {
+                this.editStatus = true
+            },
+            selectStep(index) {
+                this.currentStepIndex = index
+                this.isLoading = true
+                setTimeout(() => {
+                    this.isLoading = false
+                },300)
+            },
             selectTab(index) {
                 this.currentTabIndex = index
             },
@@ -60,7 +218,7 @@
             },
             findSelfLearn() {
                 let requestData = {
-                    creator:this.$store.state.userInfo.TEAMModelId
+                    creator: this.$store.state.userInfo.TEAMModelId
                 }
                 this.$api.learnActivity.FindSelfLearn(requestData).then(
                     res => {
@@ -82,8 +240,10 @@
     }
 </script>
 <style lang="less" scoped>
-@import "./ManageSelfLearn.less";
+    @import "./ManageSelfLearn.less";
 </style>
 <style>
-
+    .learn-content-detail #loadingBox {
+        margin-top: 70px !important;
+    }
 </style>

+ 1 - 1
TEAMModelOS/ClientApp/src/view/teachcontent/ManageOrderLearn.vue

@@ -138,7 +138,7 @@
         <Modal v-model="editStatus"
                title="编辑内容"
                @on-ok="confirmEdit">
-            <p>确认跳转到编序式自主学习活动编辑页面?</p>
+            <p>确认跳转到编序式教材编辑页面?</p>
         </Modal>
     </div>
 </template>

+ 8 - 3
TEAMModelOS/Startup.cs

@@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Features;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.SpaServices;
+using Microsoft.Azure.Cosmos;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
@@ -24,6 +25,8 @@ using TEAMModelOS.SDK.Module.AzureCosmosDB.Configuration;
 using TEAMModelOS.SDK.Module.AzureCosmosDBV3;
 using TEAMModelOS.SDK.Module.AzureTable.Implements;
 using TEAMModelOS.SDK.Module.AzureTable.Interfaces;
+using TEAMModelOS.Service.Models.Syllabus;
+using TEAMModelOS.Service.Services.ChangeFeed;
 using VueCliMiddleware;
 
 namespace TEAMModelOS
@@ -129,15 +132,17 @@ namespace TEAMModelOS
                    .AsImplementedInterfaces()
                    .WithScopedLifetime());
         }
-     
+
         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
-        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAzureCosmosDBV3Repository cosmosDBV3Repository)
+        public async void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAzureCosmosDBV3Repository cosmosDBV3Repository)
         {
             if (env.IsDevelopment())
             {
                 app.UseDeveloperExceptionPage();
             }
-            cosmosDBV3Repository.InitializeDatabase();
+
+            await cosmosDBV3Repository.InitializeDatabase();
+         //   await changeFeedInvoke.MonitorChangeFeed();
             app.UseMiddleware<HttpGlobalExceptionInvoke>();
             //以下需要按照順序載入中間件  如果应用调用 UseStaticFiles,请将 UseStaticFiles 置于 UseRouting之前。
             app.UseStaticFiles();