diff --git a/Bootstrap.Admin/BootstrapAdmin.db b/Bootstrap.Admin/BootstrapAdmin.db
index 415a47b1..46aab330 100644
Binary files a/Bootstrap.Admin/BootstrapAdmin.db and b/Bootstrap.Admin/BootstrapAdmin.db differ
diff --git a/Bootstrap.Admin/Controllers/AccountController.cs b/Bootstrap.Admin/Controllers/AccountController.cs
index d55d294d..93dccc9b 100644
--- a/Bootstrap.Admin/Controllers/AccountController.cs
+++ b/Bootstrap.Admin/Controllers/AccountController.cs
@@ -33,7 +33,7 @@ namespace Bootstrap.Admin.Controllers
ViewBag.UserName = "Admin";
ViewBag.Password = "123789";
}
- return User.Identity.IsAuthenticated ? (ActionResult)Redirect("~/Home/Index") : View("Login", new ModelBase());
+ return User.Identity.IsAuthenticated ? (ActionResult)Redirect("~/Home/Index") : View("Login", new LoginModel());
}
///
@@ -41,13 +41,14 @@ namespace Bootstrap.Admin.Controllers
///
/// The login.
///
+ ///
/// User name.
/// Password.
/// Remember.
[HttpPost]
- public async Task Login([FromServices]IOnlineUsers onlineUserSvr, string userName, string password, string remember)
+ public async Task Login([FromServices]IOnlineUsers onlineUserSvr, [FromServices]IIPLocatorProvider ipLocator, string userName, string password, string remember)
{
- if (UserHelper.Authenticate(userName, password, loginUser => CreateLoginUser(onlineUserSvr, HttpContext, loginUser)))
+ if (UserHelper.Authenticate(userName, password, loginUser => CreateLoginUser(onlineUserSvr, ipLocator, HttpContext, loginUser)))
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
@@ -55,21 +56,22 @@ namespace Bootstrap.Admin.Controllers
// redirect origin url
var originUrl = Request.Query[CookieAuthenticationDefaults.ReturnUrlParameter].FirstOrDefault() ?? "~/Home/Index";
return Redirect(originUrl);
- }
- return View("Login", new ModelBase());
+ }
+ return View("Login", new LoginModel());
}
///
///
///
///
+ ///
///
///
- internal static void CreateLoginUser(IOnlineUsers onlineUserSvr, HttpContext context, LoginUser loginUser)
+ internal static void CreateLoginUser(IOnlineUsers onlineUserSvr, IIPLocatorProvider ipLocator, HttpContext context, LoginUser loginUser)
{
var agent = new UserAgent(context.Request.Headers["User-Agent"]);
loginUser.Ip = (context.Connection.RemoteIpAddress ?? IPAddress.IPv6Loopback).ToString();
- loginUser.City = onlineUserSvr.RetrieveLocaleByIp(loginUser.Ip);
+ loginUser.City = ipLocator.Locate(loginUser.Ip);
loginUser.Browser = $"{agent.Browser.Name} {agent.Browser.Version}";
loginUser.OS = $"{agent.OS.Name} {agent.OS.Version}";
}
diff --git a/Bootstrap.Admin/Controllers/Api/LoginController.cs b/Bootstrap.Admin/Controllers/Api/LoginController.cs
index 2b5596f9..373e10d1 100644
--- a/Bootstrap.Admin/Controllers/Api/LoginController.cs
+++ b/Bootstrap.Admin/Controllers/Api/LoginController.cs
@@ -1,5 +1,6 @@
using Bootstrap.DataAccess;
using Bootstrap.Security;
+using Longbow.Web;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
@@ -28,17 +29,18 @@ namespace Bootstrap.Admin.Controllers.Api
///
///
///
+ ///
///
///
[AllowAnonymous]
[HttpPost]
- public string Post([FromServices]IOnlineUsers onlineUserSvr, [FromBody]JObject value)
+ public string Post([FromServices]IOnlineUsers onlineUserSvr, [FromServices]IIPLocatorProvider ipLocator, [FromBody]JObject value)
{
string token = null;
dynamic user = value;
string userName = user.userName;
string password = user.password;
- if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password) && UserHelper.Authenticate(userName, password, loginUser => AccountController.CreateLoginUser(onlineUserSvr, HttpContext, loginUser)))
+ if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password) && UserHelper.Authenticate(userName, password, loginUser => AccountController.CreateLoginUser(onlineUserSvr, ipLocator, HttpContext, loginUser)))
{
token = BootstrapAdminJwtTokenHandler.CreateToken(userName);
}
diff --git a/Bootstrap.Admin/Controllers/Api/LogsController.cs b/Bootstrap.Admin/Controllers/Api/LogsController.cs
index 60c74cb3..d96b8ab2 100644
--- a/Bootstrap.Admin/Controllers/Api/LogsController.cs
+++ b/Bootstrap.Admin/Controllers/Api/LogsController.cs
@@ -29,16 +29,17 @@ namespace Bootstrap.Admin.Controllers.Api
///
///
///
+ ///
///
///
[HttpPost]
- public bool Post([FromServices]IOnlineUsers onlineUserSvr, [FromBody]Log value)
+ public bool Post([FromServices]IOnlineUsers onlineUserSvr, [FromServices]IIPLocatorProvider ipLocator, [FromBody]Log value)
{
var agent = new UserAgent(Request.Headers["User-Agent"]);
value.Ip = (HttpContext.Connection.RemoteIpAddress ?? IPAddress.IPv6Loopback).ToString();
value.Browser = $"{agent.Browser.Name} {agent.Browser.Version}";
value.OS = $"{agent.OS.Name} {agent.OS.Version}";
- value.City = onlineUserSvr.RetrieveLocaleByIp(value.Ip);
+ value.City = ipLocator.Locate(value.Ip);
value.UserName = User.Identity.Name;
return LogHelper.Save(value);
}
diff --git a/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs b/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs
index 2d9c7665..3fa52cc7 100644
--- a/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs
+++ b/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs
@@ -1,3 +1,4 @@
+using Longbow.Web;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
diff --git a/Bootstrap.Admin/Models/LoginModel.cs b/Bootstrap.Admin/Models/LoginModel.cs
new file mode 100644
index 00000000..af634097
--- /dev/null
+++ b/Bootstrap.Admin/Models/LoginModel.cs
@@ -0,0 +1,23 @@
+using Bootstrap.DataAccess;
+
+namespace Bootstrap.Admin.Models
+{
+ ///
+ ///
+ ///
+ public class LoginModel : ModelBase
+ {
+ ///
+ ///
+ ///
+ public LoginModel()
+ {
+ ImageLibUrl = DictHelper.RetrieveImagesLibUrl();
+ }
+
+ ///
+ /// 验证码图床地址
+ ///
+ public string ImageLibUrl { get; protected set; }
+ }
+}
diff --git a/Bootstrap.Admin/OnlineUsers/DefaultOnlineUsers.cs b/Bootstrap.Admin/OnlineUsers/DefaultOnlineUsers.cs
deleted file mode 100644
index 6f92fc7b..00000000
--- a/Bootstrap.Admin/OnlineUsers/DefaultOnlineUsers.cs
+++ /dev/null
@@ -1,187 +0,0 @@
-using Bootstrap.DataAccess;
-using Longbow.Cache;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Linq;
-using System.Net.Http;
-using System.Threading.Tasks;
-
-namespace Bootstrap.Admin
-{
- ///
- ///
- ///
- internal class DefaultOnlineUsers : IOnlineUsers
- {
- private readonly ConcurrentDictionary> _onlineUsers = new ConcurrentDictionary>();
- private readonly ConcurrentDictionary> _ipLocator = new ConcurrentDictionary>();
- private readonly HttpClient _client;
- private readonly IEnumerable _local = new string[] { "::1", "127.0.0.1" };
- ///
- ///
- ///
- ///
- public DefaultOnlineUsers(IHttpClientFactory factory)
- {
- _client = factory.CreateClient(OnlineUsersServicesCollectionExtensions.IPSvrHttpClientName);
- }
-
- ///
- ///
- ///
- ///
- public IEnumerable OnlineUsers
- {
- get { return _onlineUsers.Values.Select(v => v.Value); }
- }
-
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public AutoExpireCacheEntry AddOrUpdate(string key, Func> addValueFactory, Func, AutoExpireCacheEntry> updateValueFactory) => _onlineUsers.AddOrUpdate(key, addValueFactory, updateValueFactory);
-
- ///
- ///
- ///
- ///
- ///
- ///
- public bool TryRemove(string key, out AutoExpireCacheEntry onlineUserCache) => _onlineUsers.TryRemove(key, out onlineUserCache);
-
- ///
- ///
- ///
- ///
- ///
- public string RetrieveLocaleByIp(string ip = null) => _ipLocator.GetOrAdd(ip, key => new AutoExpireCacheEntry(IPProxy(key), 1000 * 60, __ => _ipLocator.TryRemove(key, out _))).Value;
-
- private string IPProxy(string ip)
- {
- var ipSvr = DictHelper.RetrieveLocaleIPSvr();
- if (ipSvr.IsNullOrEmpty() || ipSvr.Equals("None", StringComparison.OrdinalIgnoreCase) || ip.IsNullOrEmpty() || _local.Any(p => p == ip)) return "本地连接";
-
- var ipSvrUrl = DictHelper.RetrieveLocaleIPSvrUrl(ipSvr);
- if (ipSvrUrl.IsNullOrEmpty()) return "本地连接";
-
- var url = $"{ipSvrUrl}{ip}";
- var task = ipSvr == "BaiDuIPSvr" ? RetrieveLocator(url) : RetrieveLocator(url);
- task.Wait();
- return task.Result;
- }
-
- private async Task RetrieveLocator(string url)
- {
- var ret = default(T);
- try
- {
- ret = await _client.GetAsJsonAsync(url);
- }
- catch (Exception ex)
- {
- ex.Log(new NameValueCollection()
- {
- ["Url"] = url
- });
- }
- return ret?.ToString();
- }
-
- ///
- ///
- ///
- private class BaiDuIPLocator
- {
- ///
- /// 详细地址信息
- ///
- public string Address { get; set; }
-
- ///
- /// 结果状态返回码
- ///
- public string Status { get; set; }
-
- ///
- ///
- ///
- ///
- public override string ToString()
- {
- return Status == "0" ? string.Join(" ", Address.SpanSplit("|").Skip(1).Take(2)) : "XX XX";
- }
- }
-
- private class JuheIPLocator
- {
- ///
- ///
- ///
- public string ResultCode { get; set; }
-
- ///
- ///
- ///
- public string Reason { get; set; }
-
- ///
- ///
- ///
- public JuheIPLocatorResult Result { get; set; }
-
- ///
- ///
- ///
- ///
- public int Error_Code { get; set; }
-
- ///
- ///
- ///
- ///
- public override string ToString()
- {
- return Error_Code != 0 ? "XX XX" : Result.ToString();
- }
- }
-
- private class JuheIPLocatorResult
- {
- ///
- ///
- ///
- public string Country { get; set; }
-
- ///
- ///
- ///
- public string Province { get; set; }
-
- ///
- ///
- ///
- public string City { get; set; }
-
- ///
- ///
- ///
- public string Isp { get; set; }
-
- ///
- ///
- ///
- ///
- public override string ToString()
- {
- return Country != "中国" ? $"{Country} {Province} {Isp}" : $"{Province} {City} {Isp}";
- }
- }
- }
-}
diff --git a/Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs b/Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs
deleted file mode 100644
index 08d134f2..00000000
--- a/Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Longbow.Cache;
-using System;
-using System.Collections.Generic;
-
-namespace Bootstrap.Admin
-{
- ///
- ///
- ///
- public interface IOnlineUsers
- {
- ///
- ///
- ///
- IEnumerable OnlineUsers { get; }
-
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- AutoExpireCacheEntry AddOrUpdate(string key, Func> addValueFactory, Func, AutoExpireCacheEntry> updateValueFactory);
-
- ///
- ///
- ///
- ///
- ///
- ///
- bool TryRemove(string key, out AutoExpireCacheEntry onlineUserCache);
-
- ///
- ///
- ///
- ///
- ///
- string RetrieveLocaleByIp(string ip = null);
- }
-}
diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUser.cs b/Bootstrap.Admin/OnlineUsers/OnlineUser.cs
deleted file mode 100644
index 35d3f8f4..00000000
--- a/Bootstrap.Admin/OnlineUsers/OnlineUser.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-
-namespace Bootstrap.Admin
-{
-
- ///
- ///
- ///
- public class OnlineUser
- {
- private ConcurrentQueue> _requestUrls = new ConcurrentQueue>();
-
- ///
- ///
- ///
- public string ConnectionId { get; set; }
-
- ///
- ///
- ///
- public string UserName { get; set; }
-
- ///
- ///
- ///
- public string DisplayName { get; set; }
-
- ///
- ///
- ///
- public DateTime FirstAccessTime { get; set; }
-
- ///
- ///
- ///
- public DateTime LastAccessTime { get; set; }
-
- ///
- ///
- ///
- public string Location { get; set; }
-
- ///
- ///
- ///
- public string Method { get; set; }
-
- ///
- ///
- ///
- public string Ip { get; set; }
-
- ///
- ///
- ///
- public string Browser { get; set; }
-
- ///
- ///
- ///
- public string OS { get; set; }
-
- ///
- ///
- ///
- public string RequestUrl { get; set; }
-
- ///
- ///
- ///
- public IEnumerable> RequestUrls
- {
- get
- {
- return _requestUrls.ToArray();
- }
- }
-
- ///
- ///
- ///
- ///
- public void AddRequestUrl(string url)
- {
- _requestUrls.Enqueue(new KeyValuePair(DateTime.Now, url));
- if (_requestUrls.Count > 5)
- {
- _requestUrls.TryDequeue(out _);
- }
- }
- }
-}
diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs b/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs
deleted file mode 100644
index d4792ba8..00000000
--- a/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using Bootstrap.Admin;
-using Bootstrap.DataAccess;
-using Longbow.Cache;
-using Longbow.Web;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Linq;
-using System.Net;
-
-namespace Microsoft.AspNetCore.Builder
-{
- ///
- ///
- ///
- public static class OnlineUsersMiddlewareExtensions
- {
- ///
- ///
- ///
- ///
- ///
- public static IApplicationBuilder UseOnlineUsers(this IApplicationBuilder builder) => builder.UseWhen(context => context.Filter(), app => app.Use(async (context, next) =>
- {
- await System.Threading.Tasks.Task.Run(() =>
- {
- var user = UserHelper.RetrieveUserByUserName(context.User.Identity.Name);
- if (user == null) return;
-
- var onlineUserSvr = context.RequestServices.GetRequiredService();
- var proxy = new Func, Action, AutoExpireCacheEntry>((c, action) =>
- {
- var v = c.Value;
- v.UserName = user.UserName;
- v.DisplayName = user.DisplayName;
- v.LastAccessTime = DateTime.Now;
- v.Method = context.Request.Method;
- v.RequestUrl = context.Request.Path;
- v.AddRequestUrl(context.Request.Path);
- action?.Invoke();
- TraceHelper.Save(new Trace { Ip = v.Ip, RequestUrl = v.RequestUrl, LogTime = v.LastAccessTime, City = v.Location, Browser = v.Browser, OS = v.OS, UserName = v.UserName });
- return c;
- });
- onlineUserSvr.AddOrUpdate(context.Connection.Id ?? "", key =>
- {
- var agent = new UserAgent(context.Request.Headers["User-Agent"]);
- var v = new OnlineUser();
- v.ConnectionId = key;
- v.Ip = (context.Connection.RemoteIpAddress ?? IPAddress.IPv6Loopback).ToString();
- v.Location = onlineUserSvr.RetrieveLocaleByIp(v.Ip);
- v.Browser = $"{agent.Browser.Name} {agent.Browser.Version}";
- v.OS = $"{agent.OS.Name} {agent.OS.Version}";
- v.FirstAccessTime = DateTime.Now;
- return proxy(new AutoExpireCacheEntry(v, 1000 * 60, __ => onlineUserSvr.TryRemove(key, out _)), null);
- }, (key, v) => proxy(v, () => v.Reset()));
- });
- await next();
- }));
-
- private static bool Filter(this HttpContext context)
- {
- var url = context.Request.Path;
- return !new string[] { "/api", "/NotiHub", "/swagger" }.Any(r => url.StartsWithSegments(r, StringComparison.OrdinalIgnoreCase));
- }
- }
-}
diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUsersServicesCollectionExtensions.cs b/Bootstrap.Admin/OnlineUsers/OnlineUsersServicesCollectionExtensions.cs
deleted file mode 100644
index 6bbfa3f5..00000000
--- a/Bootstrap.Admin/OnlineUsers/OnlineUsersServicesCollectionExtensions.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Bootstrap.Admin;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-
-namespace Microsoft.Extensions.DependencyInjection
-{
- ///
- ///
- ///
- public static class OnlineUsersServicesCollectionExtensions
- {
- ///
- ///
- ///
- internal const string IPSvrHttpClientName = "IPSvr";
-
- ///
- ///
- ///
- ///
- ///
- public static IServiceCollection AddOnlineUsers(this IServiceCollection services)
- {
- services.TryAddSingleton();
- services.AddHttpClient(IPSvrHttpClientName, client =>
- {
- client.DefaultRequestHeaders.Connection.Add("keep-alive");
- });
- return services;
- }
- }
-}
diff --git a/Bootstrap.Admin/Startup.cs b/Bootstrap.Admin/Startup.cs
index 7bf5ee87..c95dd5ba 100644
--- a/Bootstrap.Admin/Startup.cs
+++ b/Bootstrap.Admin/Startup.cs
@@ -1,4 +1,4 @@
-using Bootstrap.DataAccess;
+using Bootstrap.DataAccess;
using Bootstrap.Security.Filter;
using Longbow.Web;
using Longbow.Web.SignalR;
@@ -60,6 +60,7 @@ namespace Bootstrap.Admin
services.AddConfigurationManager(Configuration);
services.AddCacheManager(Configuration);
services.AddDbAdapter();
+ services.AddIPLocator(DictHelper.ConfigIPLocator);
services.AddOnlineUsers();
var dataProtectionBuilder = services.AddDataProtection(op => op.ApplicationDiscriminator = Configuration["ApplicationDiscriminator"])
.SetApplicationName(Configuration["ApplicationName"])
@@ -127,7 +128,7 @@ namespace Bootstrap.Admin
app.UseStaticFiles();
app.UseAuthentication();
app.UseBootstrapAdminAuthorization(RoleHelper.RetrieveRolesByUserName, RoleHelper.RetrieveRolesByUrl, AppHelper.RetrievesByUserName);
- app.UseOnlineUsers();
+ app.UseOnlineUsers(callback: TraceHelper.Save);
app.UseCacheManagerCorsHandler();
app.UseSignalR(routes => { routes.MapHub("/NotiHub"); });
app.UseMvc(routes =>
diff --git a/Bootstrap.Admin/Views/Account/Login.cshtml b/Bootstrap.Admin/Views/Account/Login.cshtml
index c60ab59e..1d13da13 100644
--- a/Bootstrap.Admin/Views/Account/Login.cshtml
+++ b/Bootstrap.Admin/Views/Account/Login.cshtml
@@ -1,219 +1,220 @@
-@model ModelBase
-@{
- ViewBag.Title = Model.Title;
- Layout = "_Layout";
-}
-@section css {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @if (!string.IsNullOrEmpty(Model.Theme))
- {
-
- }
-}
-@section javascript {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-}
-
-
-
+@model LoginModel
+@{
+ ViewBag.Title = Model.Title;
+ Layout = "_Layout";
+}
+@section css {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (!string.IsNullOrEmpty(Model.Theme))
+ {
+
+ }
+}
+@section javascript {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+
+
diff --git a/Bootstrap.Admin/wwwroot/css/site.css b/Bootstrap.Admin/wwwroot/css/site.css
index ed01f5f5..f55e0c25 100644
--- a/Bootstrap.Admin/wwwroot/css/site.css
+++ b/Bootstrap.Admin/wwwroot/css/site.css
@@ -34,7 +34,7 @@
.nav [data-toggle="dropdown"] {
border-radius: 4px;
padding: 6px 12px;
- transition: color .25s linear, border-color .25s linear;
+ transition: all .25s linear;
}
.nav .dropdown-menu {
diff --git a/Bootstrap.Admin/wwwroot/js/login.js b/Bootstrap.Admin/wwwroot/js/login.js
index d6de20b6..f2581b3f 100644
--- a/Bootstrap.Admin/wwwroot/js/login.js
+++ b/Bootstrap.Admin/wwwroot/js/login.js
@@ -1,4 +1,5 @@
$(function () {
+ var $imgUrl = $('#imgUrl');
$(".container").autoCenter();
$("a[data-method]").on('click', function () {
@@ -94,8 +95,11 @@
$('#captcha, #regcap, #forgotcap').sliderCaptcha({
width: $.capWidth(),
height: $.capHeight(),
+ localImages: function () {
+ return '../../lib/captcha/images/Pic' + Math.round(Math.random() * 4) + '.jpg';
+ },
setSrc: function () {
- return 'http://pocoafrro.bkt.clouddn.com/Pic' + Math.round(Math.random() * 136) + '.jpg';
+ return $imgUrl.val() + 'Pic' + Math.round(Math.random() * 136) + '.jpg';
},
onSuccess: function () {
var parent = this.parents('.slidercaptcha').removeClass('d-block');
diff --git a/Bootstrap.Admin/wwwroot/images/Pic0.jpg b/Bootstrap.Admin/wwwroot/lib/captcha/images/Pic0.jpg
similarity index 100%
rename from Bootstrap.Admin/wwwroot/images/Pic0.jpg
rename to Bootstrap.Admin/wwwroot/lib/captcha/images/Pic0.jpg
diff --git a/Bootstrap.Admin/wwwroot/images/Pic1.jpg b/Bootstrap.Admin/wwwroot/lib/captcha/images/Pic1.jpg
similarity index 100%
rename from Bootstrap.Admin/wwwroot/images/Pic1.jpg
rename to Bootstrap.Admin/wwwroot/lib/captcha/images/Pic1.jpg
diff --git a/Bootstrap.Admin/wwwroot/images/Pic2.jpg b/Bootstrap.Admin/wwwroot/lib/captcha/images/Pic2.jpg
similarity index 100%
rename from Bootstrap.Admin/wwwroot/images/Pic2.jpg
rename to Bootstrap.Admin/wwwroot/lib/captcha/images/Pic2.jpg
diff --git a/Bootstrap.Admin/wwwroot/images/Pic3.jpg b/Bootstrap.Admin/wwwroot/lib/captcha/images/Pic3.jpg
similarity index 100%
rename from Bootstrap.Admin/wwwroot/images/Pic3.jpg
rename to Bootstrap.Admin/wwwroot/lib/captcha/images/Pic3.jpg
diff --git a/Bootstrap.Admin/wwwroot/images/Pic4.jpg b/Bootstrap.Admin/wwwroot/lib/captcha/images/Pic4.jpg
similarity index 100%
rename from Bootstrap.Admin/wwwroot/images/Pic4.jpg
rename to Bootstrap.Admin/wwwroot/lib/captcha/images/Pic4.jpg
diff --git a/Bootstrap.Admin/wwwroot/lib/captcha/longbow.slidercaptcha.js b/Bootstrap.Admin/wwwroot/lib/captcha/longbow.slidercaptcha.js
index a3677a7c..800c4380 100644
--- a/Bootstrap.Admin/wwwroot/lib/captcha/longbow.slidercaptcha.js
+++ b/Bootstrap.Admin/wwwroot/lib/captcha/longbow.slidercaptcha.js
@@ -1,4 +1,4 @@
-(function ($) {
+(function ($) {
'use strict';
var SliderCaptcha = function (element, options) {
@@ -16,11 +16,15 @@
PI: Math.PI,
sliderL: 42, // 滑块边长
sliderR: 9, // 滑块半径
+ offset: 5, // 容错偏差
loadingText: '正在加载中...',
failedText: '再试一次',
barText: '向右滑动填充拼图',
repeatIcon: 'fa fa-repeat',
- maxLoadCount: 3
+ maxLoadCount: 3,
+ localImages: function () {
+ return 'images/Pic' + Math.round(Math.random() * 4) + '.jpg';
+ }
};
function Plugin(option) {
@@ -40,9 +44,9 @@
var _proto = SliderCaptcha.prototype;
_proto.init = function () {
- this.initDOM()
- this.initImg()
- this.bindEvents()
+ this.initDOM();
+ this.initImg();
+ this.bindEvents();
};
_proto.initDOM = function () {
@@ -127,9 +131,6 @@
var getRandomNumberByRange = function (start, end) {
return Math.round(Math.random() * (end - start) + start);
};
- var localImg = function () {
- return '../images/Pic' + Math.round(Math.random() * 4) + '.jpg';
- };
var img = new Image();
img.crossOrigin = "Anonymous";
var loadCount = 0;
@@ -158,8 +159,8 @@
that.text.text('加载失败').addClass('text-danger');
return;
}
- img.src = localImg();
- }
+ img.src = that.options.localImages();
+ };
img.setSrc = function () {
var src = '';
loadCount = 0;
@@ -274,7 +275,7 @@
var stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length);
var left = parseInt(this.block.style.left);
return {
- spliced: Math.abs(left - this.x) < 4,
+ spliced: Math.abs(left - this.x) < this.options.offset,
verified: stddev !== 0, // 简单验证下拖动轨迹,为零时表示Y轴上下没有波动,可能非人为操作
}
};
diff --git a/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj b/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj
index 466392b1..5ab729f0 100644
--- a/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj
+++ b/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj
@@ -1,21 +1,21 @@
-
-
-
- netstandard2.0
- true
- ..\Keys\Longbow.Utility.snk
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ netstandard2.0
+ true
+ ..\Keys\Longbow.Utility.snk
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Bootstrap.Client.DataAccess/DictHelper.cs b/Bootstrap.Client.DataAccess/DictHelper.cs
index b4ca3970..70db656e 100644
--- a/Bootstrap.Client.DataAccess/DictHelper.cs
+++ b/Bootstrap.Client.DataAccess/DictHelper.cs
@@ -82,5 +82,11 @@ namespace Bootstrap.Client.DataAccess
///
///
public static string RetrieveIconFolderPath() => (RetrieveDicts().FirstOrDefault(d => d.Name == "头像路径" && d.Category == "头像地址" && d.Define == 0) ?? new BootstrapDict() { Code = "~/images/uploader/" }).Code;
+
+ ///
+ /// 获取验证码图床
+ ///
+ ///
+ public static string RetrieveImagesLibUrl() => (RetrieveDicts().FirstOrDefault(d => d.Name == "验证码图床" && d.Category == "系统设置" && d.Define == 0) ?? new BootstrapDict() { Code = "https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/" }).Code;
}
}
diff --git a/Bootstrap.Client/Models/NavigatorBarModel.cs b/Bootstrap.Client/Models/NavigatorBarModel.cs
index 671c38de..27889239 100644
--- a/Bootstrap.Client/Models/NavigatorBarModel.cs
+++ b/Bootstrap.Client/Models/NavigatorBarModel.cs
@@ -17,10 +17,17 @@ namespace Bootstrap.Client.Models
public NavigatorBarModel(ControllerBase controller) : base(controller.User.Identity)
{
Navigations = MenuHelper.RetrieveAppMenus(UserName, $"~/{controller.ControllerContext.ActionDescriptor.ControllerName}/{controller.ControllerContext.ActionDescriptor.ActionName}");
+ ImageLibUrl = DictHelper.RetrieveImagesLibUrl();
}
+
///
///
///
public IEnumerable Navigations { get; }
+
+ ///
+ ///
+ ///
+ public string ImageLibUrl { get; set; }
}
}
\ No newline at end of file
diff --git a/Bootstrap.Client/Views/Home/About.cshtml b/Bootstrap.Client/Views/Home/About.cshtml
index 2711273b..3808999e 100644
--- a/Bootstrap.Client/Views/Home/About.cshtml
+++ b/Bootstrap.Client/Views/Home/About.cshtml
@@ -1,4 +1,4 @@
-@{
- ViewData["Title"] = "测试网页";
-}
-测试网页
\ No newline at end of file
+@{
+ ViewData["Title"] = "关于";
+}
+我是关于网页
\ No newline at end of file
diff --git a/Bootstrap.Client/Views/Home/Index.cshtml b/Bootstrap.Client/Views/Home/Index.cshtml
index 6997f02b..45aa67eb 100644
--- a/Bootstrap.Client/Views/Home/Index.cshtml
+++ b/Bootstrap.Client/Views/Home/Index.cshtml
@@ -1,6 +1,20 @@
-@{
- ViewData["Title"] = "Home Page";
+@model NavigatorBarModel
+@{
+ ViewData["Title"] = "前台首页";
}
-这是测试系统首页,欢迎测试使用
-点击右上角登录信息下拉菜单中的设置按钮进入后台管理
-由于本系统为演示系统,内部对一些敏感操作进行了限制操作,如一些特殊用户不能删除。
\ No newline at end of file
+@section css {
+
+}
+@section javascript {
+
+
+}
+这是开源后台管理框架前台系统首页,欢迎使用
+点击右上角登录信息下拉菜单中的设置按钮进入 后台管理 或者 直接进入
+由于本系统为演示系统,内部对一些敏感操作进行了限制操作,如一些特殊用户不能删除。
+
+
+
+
\ No newline at end of file
diff --git a/Bootstrap.Client/wwwroot/css/theme.css b/Bootstrap.Client/wwwroot/css/theme.css
index 05402d83..44bfd954 100644
--- a/Bootstrap.Client/wwwroot/css/theme.css
+++ b/Bootstrap.Client/wwwroot/css/theme.css
@@ -1,613 +1,612 @@
-html {
- font-size: 16px;
- -ms-overflow-style: auto;
-}
-
-body {
- color: #797979;
- background: #f1f2f7;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -webkit-overflow-scrolling: touch;
-}
-
-a, a:hover, a:focus {
- text-decoration: none;
- outline: none;
-}
-
-body, .form-control, .dropdown-menu, .btn:not(.btn-lg):not(.btn-xs), .input-group-text, .popover-header {
- font-size: 0.875rem;
-}
-
- .form-control:focus {
- border-color: #66afe9;
- -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6);
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6);
- }
-
-.container-fluid {
- padding-top: 15px;
-}
-
-.sidebar-open aside {
- transform: translate(0);
-}
-
-.sidebar-open footer, .sidebar-open .container-fluid {
- display: none;
-}
-
-.sidebar {
- padding: 20px 0;
- background: inherit;
-}
-
- .sidebar .nav-item {
- margin: 5px 10px;
- }
-
- .sidebar .nav-item .nav-link:hover, .sidebar .nav-item .nav-link:focus {
- background: #35404d;
- color: #fff;
- }
-
- .sidebar .nav-item.active > .nav-link {
- color: #FF6C60;
- }
-
- .sidebar .nav-link {
- color: #aeb2b7;
- padding: 15px 10px;
- transition: all .3s linear;
- display: flex;
- align-items: center;
- }
-
- .sidebar .nav-link .dcjq-icon {
- height: 17px;
- width: 17px;
- background: url(../images/nav-expand.png) no-repeat;
- margin-left: auto;
- }
-
- .sidebar .nav-link i {
- width: 22px;
- }
-
- .sidebar .nav-link.active, .sidebar .nav-link.active + .sub,
- .sidebar .sub .sub .nav-item .nav-link:hover, .sidebar .sub .sub .nav-item .nav-link:focus {
- background: #35404D;
- }
-
- .sidebar .nav-link.active .dcjq-icon {
- background-position: bottom;
- }
-
- .sidebar .sub .nav-item.dcjq-parent-li {
- margin-left: 0;
- margin-right: 0;
- }
-
- .sidebar .sub .nav-item.dcjq-parent-li .nav-link {
- padding-left: 20px;
- }
-
- .sidebar .sub .dcjq-parent-li .nav-link.active, .sidebar .sub .dcjq-parent-li .nav-link.active + .sub,
- .sidebar .sub .nav-item .nav-link:hover, .sidebar .sub .nav-item .nav-link:focus {
- background: #3a4756;
- }
-
-.sidebar-toggle-box {
- display: block;
- font-size: 1.25rem;
- color: #eee;
- flex: 1 1 auto;
- padding: 12px 0;
- transition: color .3s linear;
-}
-
- .sidebar-toggle-box:hover {
- color: #fff;
- }
-
- .sidebar-toggle-box span {
- display: none;
- }
-
-aside {
- transition: transform .4s ease-in-out;
- transform: translate(-100%);
- position: absolute;
- top: 92px;
- bottom: 0;
- left: 0;
- right: 0;
- z-index: 4;
- background: #2a3542;
-}
-
-.breadcrumb {
- border-top: solid 1px #ddd;
- background-color: transparent;
- border-radius: 0;
- padding: 8px 10px;
- margin-bottom: 0;
-}
-
- .breadcrumb i {
- padding-right: 6px;
- }
-
-footer {
- background: #5b6e84;
- color: #fff;
- padding: 10px 4px;
- height: 40px;
- left: 0;
- bottom: 0;
- right: 0;
- z-index: 100;
- display: flex;
-}
-
- footer > span {
- flex: 1 1 auto;
- text-align: center;
- margin-left: 4px;
- display: inline-block;
- }
-
-.go-top {
- background: rgba(255,255,255,.5);
- width: 20px;
- height: 20px;
- border-radius: 50%;
- -webkit-border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 4px;
-}
-
- .go-top:hover {
- background-color: white;
- }
-
- .go-top i {
- color: #2A3542;
- }
-
-.bs-bars {
- display: none;
-}
-
-.btn-fill {
- width: 100%;
-}
-
-.toolbar {
- position: relative;
- margin-top: 10px;
- margin-bottom: 10px;
- line-height: 34px;
-}
-
- .toolbar .dropdown-menu a {
- padding: 0 14px;
- display: table-cell;
- color: #504d4d;
- }
-
- .toolbar .dropdown-menu a:not(:first-child) {
- border-left: solid 1px #aeb2b7;
- }
-
- .toolbar .dropdown-menu a:hover {
- color: #235e90;
- }
-
-.input-group .btn:focus, .btn-group .btn:focus, .page-link:focus {
- box-shadow: none;
-}
-
-.input-group .btn:not(.btn-secondary):not(.btn-primary):not(.btn-warning):not(.btn-info):not(.btn-danger) {
- background-color: #e9ecef;
- border-color: #ced4da;
-}
-
-.nav-link {
- transition: color .25s linear;
-}
-
-.close {
- transition: all .25s linear;
-}
-
- .close:focus {
- outline: none;
- }
-
-.dropdown-select + .dropdown-menu a:hover {
- background: #007AC0;
- color: #fff;
-}
-
-.dropdown-menu {
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.176);
- padding: 0;
-}
-
- .dropdown-menu a {
- transition: all .25s linear;
- padding: 6px 20px;
- display: block;
- }
-
-.dropdown-divider {
- margin: 0.125rem 0;
-}
-
-.dropdown-item.active, .dropdown-item:active {
- color: rgba(0, 0, 0, 0.9);
- background-color: rgba(0, 0, 0, 0.05);
-}
-
-.dropdown-item i + span {
- margin-left: 6px;
-}
-
-.tooltip-inner {
- max-width: 768px;
-}
-
-.is-invalid .tooltip-inner {
- background-color: #dc3545;
-}
-
-.is-invalid .arrow:before {
- border-top-color: #dc3545;
- border-bottom-color: #dc3545;
-}
-
-.radioGroup, .checkGroup, [radioGroup="true"], [checkGroup="true"] {
- border: solid 1px transparent;
- border-radius: 4px;
- padding: 1px 4px;
-}
-
- .radioGroup.is-invalid, .checkGroup.is-invalid, [radioGroup="true"].is-invalid, [checkGroup="true"].is-invalid {
- border: solid 1px #dc3545;
- }
-
- .radioGroup.is-invalid .form-check-input ~ .form-check-label,
- .checkGroup.is-invalid .form-check-input ~ .form-check-label,
- [radioGroup="true"].is-invalid .form-check-input ~ .form-check-label,
- [checkGroup="true"].is-invalid .form-check-input ~ .form-check-label {
- color: #dc3545;
- }
-
- .radioGroup.is-valid .form-check-input ~ .form-check-label,
- [radioGroup="true"].is-valid .form-check-input ~ .form-check-label {
- color: #28a745;
- }
-
- .checkGroup.is-valid .form-check-input ~ .form-check-label,
- [checkGroup="true"].is-valid .form-check-input ~ .form-check-label {
- color: unset;
- }
-
-
-input.is-invalid {
- background-repeat: no-repeat;
- background-image: url('../images/error.png');
- background-position: right 8px center;
-}
-
-input.is-valid {
- background-repeat: no-repeat;
- background-image: url('../images/success.png');
- background-position: right 8px center;
-}
-
-input.pending {
- background-repeat: no-repeat;
- background-image: url(../images/loading-sm.gif);
- background-position: right 8px center;
-}
-
-.form-inline .form-group {
- margin-bottom: 15px;
-}
-
- .form-inline .form-group .control-label {
- margin-right: 10px;
- width: 90px;
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
- justify-content: flex-start;
- }
-
-.form-inline .form-row, .form-inline .row {
- flex: 1 1 auto;
-}
-
-.modal-header {
- background-color: #f5f5f5;
- padding: 10px 15px;
-}
-
-.modal-body, .modal-footer, .popover-body {
- background-color: #fff;
-}
-
-.modal-title {
- overflow-x: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.modal-body, .card-body {
- padding: 15px 15px 0 15px;
-}
-
-.modal-footer .btn span:last-child {
- display: inline;
-}
-
-.card {
- margin-bottom: 15px;
-}
-
- .card .card-header a {
- color: #797979;
- }
-
- .card .card-header a[data-toggle="collapse"] {
- display: block;
- }
-
- .card .card-header a[data-toggle="collapse"] i {
- transition: all .25s linear;
- }
-
- .card .card-header a[data-toggle="collapse"].collapsed i {
- transform: rotate(-90deg);
- }
-
- .card .card-header i + span {
- margin-left: 6px;
- }
-
-.btn i.fa + span {
- margin-left: 4px;
- display: none;
-}
-
-.form-check-label {
- cursor: pointer;
-}
-
-.form-check-input + span, .form-check-input + label {
- max-width: 98px;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- display: inline-block;
-}
-
-.bootstrap-table .table thead > tr > th {
- border-top: none;
-}
-
-.fixed-table-toolbar .bs-bars, .fixed-table-toolbar .search, .fixed-table-toolbar .columns {
- line-height: normal;
-}
-
-.table-condensed > thead > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > tbody > tr > td {
- padding: 2px 0;
-}
-
-.fixed-table-loading {
- padding: 8px;
-}
-
-.bootstrap-table {
- margin-bottom: 15px;
-}
-
- .bootstrap-table .fixed-table-body .table {
- border-bottom: none;
- }
-
- .bootstrap-table .fixed-table-body .table td > a {
- display: block;
- }
-
- .bootstrap-table .fixed-table-toolbar .columns-right .btn-group .dropdown-menu {
- padding-top: 8px;
- }
-
- .bootstrap-table .fixed-table-container .card-view .value {
- word-break: break-all;
- }
-
- .bootstrap-table .fixed-table-container .card-view .value > a, .bootstrap-table .fixed-table-container .card-view .value > div:not(.form-check) {
- min-width: 60px;
- display: inline-block;
- }
-
- .bootstrap-table .card-view:not(:last-child) {
- margin-bottom: 6px;
- }
-
-.card-body:not(.nobar) .bootstrap-table {
- margin-top: -10px;
-}
-
-.card-header a.fa {
- transition: color .3s linear;
-}
-
- .card-header a.fa:hover {
- color: #333;
- }
-
-.fixed-table-toolbar .dropdown-menu {
- min-width: unset;
-}
-
-.popover {
- max-width: 320px;
- padding: 0;
-}
-
-.popover-content {
- max-height: 240px;
- overflow-y: auto;
- overflow-x: hidden;
-}
-
-.file-drop-zone.clickable:hover {
- border: 1px dashed #999;
-}
-
-[data-toggle="LgbValidate"] input[type="text"], [data-toggle="LgbValidate"] input[type="password"] {
- padding-right: 30px;
-}
-
-.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {
- border-color: #dc3545;
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(220, 53, 69,.6);
-}
-
-.was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {
- border-color: #28a745;
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(40, 167, 69,.6);
-}
-
-.arrow-primary {
- border-color: transparent transparent #007bff;
-}
-
-.arrow-success {
- border-color: transparent transparent #28a745;
-}
-
-.arrow-info {
- border-color: transparent transparent #17a2b8;
-}
-
-.arrow-warning {
- border-color: transparent transparent #ffc107;
-}
-
-.arrow-danger {
- border-color: transparent transparent #dc3545;
-}
-
-.show .shadow-primary, .shadow-primary:hover {
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(0, 123, 255, 0.5);
- border-color: #007bff !important;
-}
-
-.show .shadow-success, .shadow-success:hover {
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(40, 167, 69, 0.5);
- border-color: #28a745 !important;
-}
-
-.show .shadow-info, .shadow-info:hover {
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(23, 162, 184, 0.5);
- border-color: #17a2b8 !important;
-}
-
-.show .shadow-warning, .shadow-warning:hover {
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(255, 193, 7, 0.5);
- border-color: #ffc107 !important;
-}
-
-.show .shadow-danger, .shadow-danger:hover {
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(220, 53, 69, 0.5);
- border: 1px solid #dc3545 !important;
-}
-
-.show .shadow-default, .shadow-default:hover {
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6);
- border: 1px solid #337ab7;
-}
-
-.mCSB_scrollTools {
- width: 10px;
-}
-
-.control-label > a {
- margin-left: 6px;
- color: #5bc0de;
-}
-
-.date > input {
- cursor: pointer;
-}
-
-.datetimepicker {
- padding: 4px;
-}
-
- .datetimepicker table tr td span {
- white-space: nowrap;
- }
-
-.form-inline .form-group-dropdown {
- display: flex;
- flex-wrap: nowrap;
-}
-
- .form-inline .form-group-dropdown .control-label {
- padding: 6px 0;
- margin: 0 10px 0 0;
- }
-
- .form-inline .form-group-dropdown .dropdown, .form-inline .form-group-dropdown .dropup {
- display: inline-block;
- }
-
-.toggle .btn-default, .toggle.btn-default {
- text-shadow: 0 1px 0 #fff;
- background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
- background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
- background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
- background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
- border-color: #ccc;
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
-}
-
- .toggle .btn-default.active {
- background-image: none;
- background-color: #e0e0e0;
- border-color: #dbdbdb;
- }
-
- .toggle .btn-default.active:hover {
- color: #333;
- background-color: #d4d4d4;
- border-color: #8c8c8c;
- }
-
-.toggle.btn .toggle-handle {
- display: inline-block;
- margin-left: 0;
-}
-
-.sweet-alert h2 {
- margin-top: 32px;
- margin-bottom: 16px;
- font-size: 1.5rem;
-}
-
-.sweet-alert .sa-button-container .btn-lg {
- padding: 0.375rem 1.5rem;
- font-size: 1rem;
-}
+html {
+ font-size: 16px;
+ -ms-overflow-style: auto;
+}
+
+body {
+ color: #797979;
+ background: #f1f2f7;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -webkit-overflow-scrolling: touch;
+}
+
+a, a:hover, a:focus {
+ text-decoration: none;
+ outline: none;
+}
+
+body, .form-control, .dropdown-menu, .btn:not(.btn-lg):not(.btn-xs), .input-group-text, .popover-header {
+ font-size: 0.875rem;
+}
+
+ .form-control:focus {
+ border-color: #66afe9;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6);
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6);
+ }
+
+.container-fluid {
+ padding-top: 15px;
+}
+
+.sidebar-open aside {
+ transform: translate(0);
+}
+
+.sidebar-open footer, .sidebar-open .container-fluid {
+ display: none;
+}
+
+.sidebar {
+ padding: 20px 0;
+ background: inherit;
+}
+
+ .sidebar .nav-item {
+ margin: 5px 10px;
+ }
+
+ .sidebar .nav-item .nav-link:hover, .sidebar .nav-item .nav-link:focus {
+ background: #35404d;
+ color: #fff;
+ }
+
+ .sidebar .nav-item.active > .nav-link {
+ color: #FF6C60;
+ }
+
+ .sidebar .nav-link {
+ color: #aeb2b7;
+ padding: 15px 10px;
+ transition: all .3s linear;
+ display: flex;
+ align-items: center;
+ }
+
+ .sidebar .nav-link .dcjq-icon {
+ height: 17px;
+ width: 17px;
+ background: url(../images/nav-expand.png) no-repeat;
+ margin-left: auto;
+ }
+
+ .sidebar .nav-link i {
+ width: 22px;
+ }
+
+ .sidebar .nav-link.active, .sidebar .nav-link.active + .sub,
+ .sidebar .sub .sub .nav-item .nav-link:hover, .sidebar .sub .sub .nav-item .nav-link:focus {
+ background: #35404D;
+ }
+
+ .sidebar .nav-link.active .dcjq-icon {
+ background-position: bottom;
+ }
+
+ .sidebar .sub .nav-item.dcjq-parent-li {
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ .sidebar .sub .nav-item.dcjq-parent-li .nav-link {
+ padding-left: 20px;
+ }
+
+ .sidebar .sub .dcjq-parent-li .nav-link.active, .sidebar .sub .dcjq-parent-li .nav-link.active + .sub,
+ .sidebar .sub .nav-item .nav-link:hover, .sidebar .sub .nav-item .nav-link:focus {
+ background: #3a4756;
+ }
+
+.sidebar-toggle-box {
+ display: block;
+ font-size: 1.25rem;
+ color: #eee;
+ flex: 1 1 auto;
+ padding: 12px 0;
+ transition: color .3s linear;
+}
+
+ .sidebar-toggle-box:hover {
+ color: #fff;
+ }
+
+ .sidebar-toggle-box span {
+ display: none;
+ }
+
+aside {
+ transition: transform .4s ease-in-out;
+ transform: translate(-100%);
+ position: absolute;
+ top: 92px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 4;
+ background: #2a3542;
+}
+
+.breadcrumb {
+ border-top: solid 1px #ddd;
+ background-color: transparent;
+ border-radius: 0;
+ padding: 8px 10px;
+ margin-bottom: 0;
+}
+
+ .breadcrumb i {
+ padding-right: 6px;
+ }
+
+footer {
+ background: #5b6e84;
+ color: #fff;
+ padding: 10px 4px;
+ height: 40px;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ z-index: 100;
+ display: flex;
+}
+
+ footer > span {
+ flex: 1 1 auto;
+ text-align: center;
+ margin-left: 4px;
+ display: inline-block;
+ }
+
+.go-top {
+ background: rgba(255,255,255,.5);
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ -webkit-border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 4px;
+}
+
+ .go-top:hover {
+ background-color: white;
+ }
+
+ .go-top i {
+ color: #2A3542;
+ }
+
+.bs-bars {
+ display: none;
+}
+
+.btn-fill {
+ width: 100%;
+}
+
+.toolbar {
+ position: relative;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ line-height: 34px;
+}
+
+ .toolbar .dropdown-menu a {
+ padding: 0 14px;
+ display: table-cell;
+ color: #504d4d;
+ }
+
+ .toolbar .dropdown-menu a:not(:first-child) {
+ border-left: solid 1px #aeb2b7;
+ }
+
+ .toolbar .dropdown-menu a:hover {
+ color: #235e90;
+ }
+
+.input-group .btn:focus, .btn-group .btn:focus, .page-link:focus {
+ box-shadow: none;
+}
+
+.input-group .btn:not(.btn-secondary):not(.btn-primary):not(.btn-warning):not(.btn-info):not(.btn-danger) {
+ background-color: #e9ecef;
+ border-color: #ced4da;
+}
+
+.nav-link {
+ transition: color .25s linear;
+}
+
+.close {
+ transition: all .25s linear;
+}
+
+ .close:focus {
+ outline: none;
+ }
+
+.dropdown-select + .dropdown-menu a:hover {
+ background: #007AC0;
+ color: #fff;
+}
+
+.dropdown-menu {
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.176);
+ padding: 0;
+}
+
+ .dropdown-menu a {
+ transition: all .25s linear;
+ padding: 6px 20px;
+ display: block;
+ }
+
+.dropdown-divider {
+ margin: 0.125rem 0;
+}
+
+.dropdown-item.active, .dropdown-item:active {
+ color: rgba(0, 0, 0, 0.9);
+ background-color: rgba(0, 0, 0, 0.05);
+}
+
+.dropdown-item i + span {
+ margin-left: 6px;
+}
+
+.tooltip-inner {
+ max-width: 768px;
+}
+
+.is-invalid .tooltip-inner {
+ background-color: #dc3545;
+}
+
+.is-invalid .arrow:before {
+ border-top-color: #dc3545;
+ border-bottom-color: #dc3545;
+}
+
+.radioGroup, .checkGroup, [radioGroup="true"], [checkGroup="true"] {
+ border: solid 1px transparent;
+ border-radius: 4px;
+ padding: 1px 4px;
+}
+
+ .radioGroup.is-invalid, .checkGroup.is-invalid, [radioGroup="true"].is-invalid, [checkGroup="true"].is-invalid {
+ border: solid 1px #dc3545;
+ }
+
+ .radioGroup.is-invalid .form-check-input ~ .form-check-label,
+ .checkGroup.is-invalid .form-check-input ~ .form-check-label,
+ [radioGroup="true"].is-invalid .form-check-input ~ .form-check-label,
+ [checkGroup="true"].is-invalid .form-check-input ~ .form-check-label {
+ color: #dc3545;
+ }
+
+ .radioGroup.is-valid .form-check-input ~ .form-check-label,
+ [radioGroup="true"].is-valid .form-check-input ~ .form-check-label {
+ color: #28a745;
+ }
+
+ .checkGroup.is-valid .form-check-input ~ .form-check-label,
+ [checkGroup="true"].is-valid .form-check-input ~ .form-check-label {
+ color: unset;
+ }
+
+
+input.is-invalid {
+ background-repeat: no-repeat;
+ background-image: url('../images/error.png');
+ background-position: right 8px center;
+}
+
+input.is-valid {
+ background-repeat: no-repeat;
+ background-image: url('../images/success.png');
+ background-position: right 8px center;
+}
+
+input.pending {
+ background-repeat: no-repeat;
+ background-image: url(../images/loading-sm.gif);
+ background-position: right 8px center;
+}
+
+.form-inline .form-group {
+ margin-bottom: 15px;
+}
+
+ .form-inline .form-group .control-label {
+ margin-right: 10px;
+ width: 90px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ justify-content: flex-start;
+ }
+
+.form-inline .form-row, .form-inline .row {
+ flex: 1 1 auto;
+}
+
+.modal-header {
+ background-color: #f5f5f5;
+ padding: 10px 15px;
+}
+
+.modal-body, .modal-footer, .popover-body {
+ background-color: #fff;
+}
+
+.modal-title {
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.modal-body, .card-body {
+ padding: 15px 15px 0 15px;
+}
+
+.modal-footer .btn span:last-child {
+ display: inline;
+}
+
+.card {
+ margin-bottom: 15px;
+}
+
+ .card .card-header a {
+ color: #797979;
+ }
+
+ .card .card-header a[data-toggle="collapse"] {
+ display: block;
+ }
+
+ .card .card-header a[data-toggle="collapse"] i {
+ transition: all .25s linear;
+ }
+
+ .card .card-header a[data-toggle="collapse"].collapsed i {
+ transform: rotate(-90deg);
+ }
+
+ .card .card-header i + span {
+ margin-left: 6px;
+ }
+
+.btn i.fa + span {
+ margin-left: 4px;
+}
+
+.form-check-label {
+ cursor: pointer;
+}
+
+.form-check-input + span, .form-check-input + label {
+ max-width: 98px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ display: inline-block;
+}
+
+.bootstrap-table .table thead > tr > th {
+ border-top: none;
+}
+
+.fixed-table-toolbar .bs-bars, .fixed-table-toolbar .search, .fixed-table-toolbar .columns {
+ line-height: normal;
+}
+
+.table-condensed > thead > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > tbody > tr > td {
+ padding: 2px 0;
+}
+
+.fixed-table-loading {
+ padding: 8px;
+}
+
+.bootstrap-table {
+ margin-bottom: 15px;
+}
+
+ .bootstrap-table .fixed-table-body .table {
+ border-bottom: none;
+ }
+
+ .bootstrap-table .fixed-table-body .table td > a {
+ display: block;
+ }
+
+ .bootstrap-table .fixed-table-toolbar .columns-right .btn-group .dropdown-menu {
+ padding-top: 8px;
+ }
+
+ .bootstrap-table .fixed-table-container .card-view .value {
+ word-break: break-all;
+ }
+
+ .bootstrap-table .fixed-table-container .card-view .value > a, .bootstrap-table .fixed-table-container .card-view .value > div:not(.form-check) {
+ min-width: 60px;
+ display: inline-block;
+ }
+
+ .bootstrap-table .card-view:not(:last-child) {
+ margin-bottom: 6px;
+ }
+
+.card-body:not(.nobar) .bootstrap-table {
+ margin-top: -10px;
+}
+
+.card-header a.fa {
+ transition: color .3s linear;
+}
+
+ .card-header a.fa:hover {
+ color: #333;
+ }
+
+.fixed-table-toolbar .dropdown-menu {
+ min-width: unset;
+}
+
+.popover {
+ max-width: 320px;
+ padding: 0;
+}
+
+.popover-content {
+ max-height: 240px;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.file-drop-zone.clickable:hover {
+ border: 1px dashed #999;
+}
+
+[data-toggle="LgbValidate"] input[type="text"], [data-toggle="LgbValidate"] input[type="password"] {
+ padding-right: 30px;
+}
+
+.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {
+ border-color: #dc3545;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(220, 53, 69,.6);
+}
+
+.was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {
+ border-color: #28a745;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(40, 167, 69,.6);
+}
+
+.arrow-primary {
+ border-color: transparent transparent #007bff;
+}
+
+.arrow-success {
+ border-color: transparent transparent #28a745;
+}
+
+.arrow-info {
+ border-color: transparent transparent #17a2b8;
+}
+
+.arrow-warning {
+ border-color: transparent transparent #ffc107;
+}
+
+.arrow-danger {
+ border-color: transparent transparent #dc3545;
+}
+
+.show .shadow-primary, .shadow-primary:hover {
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(0, 123, 255, 0.5);
+ border-color: #007bff !important;
+}
+
+.show .shadow-success, .shadow-success:hover {
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(40, 167, 69, 0.5);
+ border-color: #28a745 !important;
+}
+
+.show .shadow-info, .shadow-info:hover {
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(23, 162, 184, 0.5);
+ border-color: #17a2b8 !important;
+}
+
+.show .shadow-warning, .shadow-warning:hover {
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(255, 193, 7, 0.5);
+ border-color: #ffc107 !important;
+}
+
+.show .shadow-danger, .shadow-danger:hover {
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(220, 53, 69, 0.5);
+ border: 1px solid #dc3545 !important;
+}
+
+.show .shadow-default, .shadow-default:hover {
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6);
+ border: 1px solid #337ab7;
+}
+
+.mCSB_scrollTools {
+ width: 10px;
+}
+
+.control-label > a {
+ margin-left: 6px;
+ color: #5bc0de;
+}
+
+.date > input {
+ cursor: pointer;
+}
+
+.datetimepicker {
+ padding: 4px;
+}
+
+ .datetimepicker table tr td span {
+ white-space: nowrap;
+ }
+
+.form-inline .form-group-dropdown {
+ display: flex;
+ flex-wrap: nowrap;
+}
+
+ .form-inline .form-group-dropdown .control-label {
+ padding: 6px 0;
+ margin: 0 10px 0 0;
+ }
+
+ .form-inline .form-group-dropdown .dropdown, .form-inline .form-group-dropdown .dropup {
+ display: inline-block;
+ }
+
+.toggle .btn-default, .toggle.btn-default {
+ text-shadow: 0 1px 0 #fff;
+ background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
+ background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
+ background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ border-color: #ccc;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
+}
+
+ .toggle .btn-default.active {
+ background-image: none;
+ background-color: #e0e0e0;
+ border-color: #dbdbdb;
+ }
+
+ .toggle .btn-default.active:hover {
+ color: #333;
+ background-color: #d4d4d4;
+ border-color: #8c8c8c;
+ }
+
+.toggle.btn .toggle-handle {
+ display: inline-block;
+ margin-left: 0;
+}
+
+.sweet-alert h2 {
+ margin-top: 32px;
+ margin-bottom: 16px;
+ font-size: 1.5rem;
+}
+
+.sweet-alert .sa-button-container .btn-lg {
+ padding: 0.375rem 1.5rem;
+ font-size: 1rem;
+}
diff --git a/Bootstrap.Client/wwwroot/js/index.js b/Bootstrap.Client/wwwroot/js/index.js
new file mode 100644
index 00000000..a0048af4
--- /dev/null
+++ b/Bootstrap.Client/wwwroot/js/index.js
@@ -0,0 +1,26 @@
+$(function () {
+ var $captcha = $('#captcha');
+
+ $captcha.sliderCaptcha({
+ localImages: function () {
+ return '../../lib/captcha/images/Pic' + Math.round(Math.random() * 4) + '.jpg';
+ },
+ setSrc: function () {
+ return $captcha.attr('data-imageLibUrl') + 'Pic' + Math.round(Math.random() * 136) + '.jpg';
+ },
+ onSuccess: function () {
+ var that = this;
+ setTimeout(() => {
+ that.parent().removeClass('d-inline-block');
+ that.sliderCaptcha('reset');
+ $('.userinfo .dropdown-menu a:first')[0].click();
+ }, 1000);
+ }
+ });
+
+ $('#btnCaptcha').on('click', function () {
+ $('#captcha').parent().addClass('d-inline-block');
+ });
+
+ $.footer();
+});
\ No newline at end of file
diff --git a/Bootstrap.Client/wwwroot/lib/captcha/images/Pic0.jpg b/Bootstrap.Client/wwwroot/lib/captcha/images/Pic0.jpg
new file mode 100644
index 00000000..ccfbedd1
Binary files /dev/null and b/Bootstrap.Client/wwwroot/lib/captcha/images/Pic0.jpg differ
diff --git a/Bootstrap.Client/wwwroot/lib/captcha/images/Pic1.jpg b/Bootstrap.Client/wwwroot/lib/captcha/images/Pic1.jpg
new file mode 100644
index 00000000..689cc20c
Binary files /dev/null and b/Bootstrap.Client/wwwroot/lib/captcha/images/Pic1.jpg differ
diff --git a/Bootstrap.Client/wwwroot/lib/captcha/images/Pic2.jpg b/Bootstrap.Client/wwwroot/lib/captcha/images/Pic2.jpg
new file mode 100644
index 00000000..6ec84255
Binary files /dev/null and b/Bootstrap.Client/wwwroot/lib/captcha/images/Pic2.jpg differ
diff --git a/Bootstrap.Client/wwwroot/lib/captcha/images/Pic3.jpg b/Bootstrap.Client/wwwroot/lib/captcha/images/Pic3.jpg
new file mode 100644
index 00000000..d1d1553d
Binary files /dev/null and b/Bootstrap.Client/wwwroot/lib/captcha/images/Pic3.jpg differ
diff --git a/Bootstrap.Client/wwwroot/lib/captcha/images/Pic4.jpg b/Bootstrap.Client/wwwroot/lib/captcha/images/Pic4.jpg
new file mode 100644
index 00000000..d718c2bb
Binary files /dev/null and b/Bootstrap.Client/wwwroot/lib/captcha/images/Pic4.jpg differ
diff --git a/Bootstrap.Client/wwwroot/lib/captcha/longbow.slidercaptcha.js b/Bootstrap.Client/wwwroot/lib/captcha/longbow.slidercaptcha.js
new file mode 100644
index 00000000..800c4380
--- /dev/null
+++ b/Bootstrap.Client/wwwroot/lib/captcha/longbow.slidercaptcha.js
@@ -0,0 +1,293 @@
+(function ($) {
+ 'use strict';
+
+ var SliderCaptcha = function (element, options) {
+ this.$element = $(element);
+ this.options = $.extend({}, SliderCaptcha.DEFAULTS, options);
+ this.$element.css({ 'position': 'relative', 'width': this.options.width + 'px', 'margin': '0 auto' });
+ this.init();
+ };
+
+ SliderCaptcha.VERSION = '1.0';
+ SliderCaptcha.Author = 'argo@163.com';
+ SliderCaptcha.DEFAULTS = {
+ width: 280, // canvas宽度
+ height: 155, // canvas高度
+ PI: Math.PI,
+ sliderL: 42, // 滑块边长
+ sliderR: 9, // 滑块半径
+ offset: 5, // 容错偏差
+ loadingText: '正在加载中...',
+ failedText: '再试一次',
+ barText: '向右滑动填充拼图',
+ repeatIcon: 'fa fa-repeat',
+ maxLoadCount: 3,
+ localImages: function () {
+ return 'images/Pic' + Math.round(Math.random() * 4) + '.jpg';
+ }
+ };
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this);
+ var data = $this.data('lgb.SliderCaptcha');
+ var options = typeof option === 'object' && option;
+
+ if (!data && /init|reset|verify/.test(option)) return;
+ if (!data) $this.data('lgb.SliderCaptcha', data = new SliderCaptcha(this, options));
+ if (typeof option === 'string') data[option]();
+ });
+ }
+
+ $.fn.sliderCaptcha = Plugin;
+ $.fn.sliderCaptcha.Constructor = SliderCaptcha;
+
+ var _proto = SliderCaptcha.prototype;
+ _proto.init = function () {
+ this.initDOM();
+ this.initImg();
+ this.bindEvents();
+ };
+
+ _proto.initDOM = function () {
+ var createElement = function (tagName, className) {
+ var elment = document.createElement(tagName);
+ elment.className = className;
+ return elment;
+ };
+
+ var createCanvas = function (width, height) {
+ var canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ return canvas;
+ };
+
+ var canvas = createCanvas(this.options.width - 2, this.options.height) // 画布
+ var block = canvas.cloneNode(true) // 滑块
+ var sliderContainer = createElement('div', 'sliderContainer');
+ var refreshIcon = createElement('i', 'refreshIcon ' + this.options.repeatIcon);
+ var sliderMask = createElement('div', 'sliderMask');
+ var sliderbg = createElement('div', 'sliderbg');
+ var slider = createElement('div', 'slider');
+ var sliderIcon = createElement('i', 'fa fa-arrow-right sliderIcon');
+ var text = createElement('span', 'sliderText');
+
+ block.className = 'block'
+ text.innerHTML = this.options.barText;
+
+ var el = this.$element;
+ el.append($(canvas));
+ el.append($(refreshIcon));
+ el.append($(block));
+ slider.appendChild(sliderIcon);
+ sliderMask.appendChild(slider);
+ sliderContainer.appendChild(sliderbg);
+ sliderContainer.appendChild(sliderMask);
+ sliderContainer.appendChild(text);
+ el.append($(sliderContainer));
+
+ Object.assign(this, {
+ canvas,
+ block,
+ sliderContainer: $(sliderContainer),
+ refreshIcon,
+ slider,
+ sliderMask,
+ sliderIcon,
+ text: $(text),
+ canvasCtx: canvas.getContext('2d'),
+ blockCtx: block.getContext('2d')
+ })
+ };
+
+ _proto.initImg = function () {
+ var that = this;
+ var isIE = window.navigator.userAgent.indexOf('Trident') > -1;
+ var L = this.options.sliderL + this.options.sliderR * 2 + 3; // 滑块实际边长
+ var drawImg = function (ctx, operation) {
+ var l = that.options.sliderL;
+ var r = that.options.sliderR;
+ var PI = that.options.PI;
+ var x = that.x;
+ var y = that.y;
+ ctx.beginPath()
+ ctx.moveTo(x, y)
+ ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI)
+ ctx.lineTo(x + l, y)
+ ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI)
+ ctx.lineTo(x + l, y + l)
+ ctx.lineTo(x, y + l)
+ ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true)
+ ctx.lineTo(x, y)
+ ctx.lineWidth = 2
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'
+ ctx.stroke()
+ ctx[operation]()
+ ctx.globalCompositeOperation = isIE ? 'xor' : 'overlay'
+ }
+
+ var getRandomNumberByRange = function (start, end) {
+ return Math.round(Math.random() * (end - start) + start);
+ };
+ var img = new Image();
+ img.crossOrigin = "Anonymous";
+ var loadCount = 0;
+ img.onload = function () {
+ // 随机创建滑块的位置
+ that.x = getRandomNumberByRange(L + 10, that.options.width - (L + 10));
+ that.y = getRandomNumberByRange(10 + that.options.sliderR * 2, that.options.height - (L + 10));
+ drawImg(that.canvasCtx, 'fill');
+ drawImg(that.blockCtx, 'clip');
+
+ that.canvasCtx.drawImage(img, 0, 0, that.options.width - 2, that.options.height);
+ that.blockCtx.drawImage(img, 0, 0, that.options.width - 2, that.options.height);
+ var y = that.y - that.options.sliderR * 2 - 1;
+ var ImageData = that.blockCtx.getImageData(that.x - 3, y, L, L);
+ that.block.width = L;
+ that.blockCtx.putImageData(ImageData, 0, y);
+ that.text.text(that.text.attr('data-text'));
+ };
+ img.onerror = function () {
+ loadCount++;
+ if (window.location.protocol === 'file:') {
+ loadCount = that.options.maxLoadCount;
+ console.error("can't load pic resource file from File protocal. Please try http or https");
+ }
+ if (loadCount >= that.options.maxLoadCount) {
+ that.text.text('加载失败').addClass('text-danger');
+ return;
+ }
+ img.src = that.options.localImages();
+ };
+ img.setSrc = function () {
+ var src = '';
+ loadCount = 0;
+ that.text.removeClass('text-danger');
+ if ($.isFunction(that.options.setSrc)) src = that.options.setSrc();
+ if (!src || src === '') src = 'https://picsum.photos/' + that.options.width + '/' + that.options.height + '/?image=' + Math.round(Math.random() * 20);
+ if (isIE) { // IE浏览器无法通过img.crossOrigin跨域,使用ajax获取图片blob然后转为dataURL显示
+ var xhr = new XMLHttpRequest()
+ xhr.onloadend = function (e) {
+ var file = new FileReader(); // FileReader仅支持IE10+
+ file.readAsDataURL(e.target.response);
+ file.onloadend = function (e) {
+ img.src = e.target.result;
+ }
+ }
+ xhr.open('GET', src);
+ xhr.responseType = 'blob';
+ xhr.send();
+ } else img.src = src;
+ };
+ img.setSrc();
+ this.text.attr('data-text', this.options.barText);
+ this.text.text(this.options.loadingText);
+ this.img = img
+ };
+
+ _proto.clean = function () {
+ this.canvasCtx.clearRect(0, 0, this.options.width, this.options.height);
+ this.blockCtx.clearRect(0, 0, this.options.width, this.options.height);
+ this.block.width = this.options.width;
+ };
+
+ _proto.bindEvents = function () {
+ var that = this;
+ this.$element.on('selectstart', function () {
+ return false;
+ });
+
+ $(this.refreshIcon).on('click', function () {
+ that.text.text(that.options.barText);
+ that.reset();
+ if ($.isFunction(that.options.onRefresh)) that.options.onRefresh.call(that.$element);
+ });
+
+ var originX, originY, trail = [],
+ isMouseDown = false
+
+ var handleDragStart = function (e) {
+ if (that.text.hasClass('text-danger')) return;
+ originX = e.clientX || e.touches[0].clientX;
+ originY = e.clientY || e.touches[0].clientY;
+ isMouseDown = true;
+ };
+
+ var handleDragMove = function (e) {
+ if (!isMouseDown) return false;
+ var eventX = e.clientX || e.touches[0].clientX;
+ var eventY = e.clientY || e.touches[0].clientY;
+ var moveX = eventX - originX;
+ var moveY = eventY - originY;
+ if (moveX < 0 || moveX + 40 > that.options.width) return false;
+ that.slider.style.left = (moveX - 1) + 'px';
+ var blockLeft = (that.options.width - 40 - 20) / (that.options.width - 40) * moveX;
+ that.block.style.left = blockLeft + 'px';
+
+ that.sliderContainer.addClass('sliderContainer_active');
+ that.sliderMask.style.width = (moveX + 4) + 'px';
+ trail.push(moveY);
+ };
+
+ var handleDragEnd = function (e) {
+ if (!isMouseDown) return false
+ isMouseDown = false
+ var eventX = e.clientX || e.changedTouches[0].clientX
+ if (eventX == originX) return false
+ that.sliderContainer.removeClass('sliderContainer_active');
+ that.trail = trail
+ var {
+ spliced,
+ verified
+ } = that.verify()
+ if (spliced && verified) {
+ that.sliderContainer.addClass('sliderContainer_success');
+ if ($.isFunction(that.options.onSuccess)) that.options.onSuccess.call(that.$element);
+ } else {
+ that.sliderContainer.addClass('sliderContainer_fail');
+ if ($.isFunction(that.options.onFail)) that.options.onFail.call(that.$element);
+ setTimeout(() => {
+ that.text.text(that.options.failedText);
+ that.reset();
+ }, 1000);
+ }
+ };
+
+ this.slider.addEventListener('mousedown', handleDragStart);
+ this.slider.addEventListener('touchstart', handleDragStart);
+ document.addEventListener('mousemove', handleDragMove);
+ document.addEventListener('touchmove', handleDragMove);
+ document.addEventListener('mouseup', handleDragEnd);
+ document.addEventListener('touchend', handleDragEnd);
+
+ document.addEventListener('mousedown', function () { return false; });
+ document.addEventListener('touchstart', function () { return false; });
+ };
+
+ _proto.verify = function () {
+ var sum = function (x, y) { return x + y; };
+ var square = function (x) { return x * x; };
+ var arr = this.trail // 拖动时y轴的移动距离
+ var average = arr.reduce(sum) / arr.length;
+ var deviations = arr.map(function (x) { return x - average; });
+ var stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length);
+ var left = parseInt(this.block.style.left);
+ return {
+ spliced: Math.abs(left - this.x) < this.options.offset,
+ verified: stddev !== 0, // 简单验证下拖动轨迹,为零时表示Y轴上下没有波动,可能非人为操作
+ }
+ };
+
+ _proto.reset = function () {
+ this.sliderContainer.removeClass('sliderContainer_fail sliderContainer_success');
+ this.slider.style.left = 0
+ this.block.style.left = 0
+ this.sliderMask.style.width = 0
+ this.clean()
+ this.text.attr('data-text', this.text.text());
+ this.text.text(this.options.loadingText);
+ this.img.setSrc();
+ };
+})(jQuery);
\ No newline at end of file
diff --git a/Bootstrap.Client/wwwroot/lib/captcha/slidercaptcha.css b/Bootstrap.Client/wwwroot/lib/captcha/slidercaptcha.css
new file mode 100644
index 00000000..ac97699d
--- /dev/null
+++ b/Bootstrap.Client/wwwroot/lib/captcha/slidercaptcha.css
@@ -0,0 +1,131 @@
+body {
+ overflow-x: hidden;
+}
+
+.block {
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+
+.sliderContainer {
+ position: relative;
+ text-align: center;
+ line-height: 40px;
+ background: #f7f9fa;
+ color: #45494c;
+ border-radius: 2px;
+}
+
+.sliderbg {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ background-color: #f7f9fa;
+ height: 40px;
+ border-radius: 2px;
+ border: 1px solid #e6e8eb;
+}
+
+.sliderContainer_active .slider {
+ top: -1px;
+ border: 1px solid #1991FA;
+}
+
+.sliderContainer_active .sliderMask {
+ border-width: 1px 0 1px 1px;
+}
+
+.sliderContainer_success .slider {
+ top: -1px;
+ border: 1px solid #52CCBA;
+ background-color: #52CCBA !important;
+}
+
+.sliderContainer_success .sliderMask {
+ border: 1px solid #52CCBA;
+ border-width: 1px 0 1px 1px;
+ background-color: #D2F4EF;
+}
+
+.sliderContainer_success .sliderIcon:before {
+ content: "\f00c";
+}
+
+.sliderContainer_fail .slider {
+ top: -1px;
+ border: 1px solid #f57a7a;
+ background-color: #f57a7a !important;
+}
+
+.sliderContainer_fail .sliderMask {
+ border: 1px solid #f57a7a;
+ background-color: #fce1e1;
+ border-width: 1px 0 1px 1px;
+}
+
+.sliderContainer_fail .sliderIcon:before {
+ content: "\f00d";
+}
+.sliderContainer_active .sliderText, .sliderContainer_success .sliderText, .sliderContainer_fail .sliderText {
+ display: none;
+}
+
+.sliderMask {
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 40px;
+ border: 0 solid #1991FA;
+ background: #D1E9FE;
+ border-radius: 2px;
+}
+
+.slider {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 40px;
+ height: 40px;
+ background: #fff;
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
+ cursor: pointer;
+ transition: background .2s linear;
+ border-radius: 2px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.slider:hover {
+ background: #1991FA;
+}
+
+.slider:hover .sliderIcon {
+ background-position: 0 -13px;
+}
+
+.sliderText {
+ position: relative;
+}
+
+.sliderIcon {
+
+}
+
+.refreshIcon {
+ position: absolute;
+ right: 0;
+ top: 0;
+ cursor: pointer;
+ margin: 6px;
+ color: rgba(0,0,0,.25);
+ font-size: 1rem;
+ z-index: 5;
+ transition: color .3s linear;
+}
+
+ .refreshIcon:hover {
+ color: #6c757d;
+ }
diff --git a/Bootstrap.DataAccess.MongoDB/Bootstrap.DataAccess.MongoDB.csproj b/Bootstrap.DataAccess.MongoDB/Bootstrap.DataAccess.MongoDB.csproj
index 5da20f34..6560d353 100644
--- a/Bootstrap.DataAccess.MongoDB/Bootstrap.DataAccess.MongoDB.csproj
+++ b/Bootstrap.DataAccess.MongoDB/Bootstrap.DataAccess.MongoDB.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj b/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj
index 7a9fb04e..8f0ca2ee 100644
--- a/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj
+++ b/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj
@@ -1,23 +1,23 @@
-
-
-
- netstandard2.0
- true
- ..\Keys\Longbow.Utility.snk
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ netstandard2.0
+ true
+ ..\Keys\Longbow.Utility.snk
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Bootstrap.DataAccess/Dict.cs b/Bootstrap.DataAccess/Dict.cs
index ae20f704..46908bec 100644
--- a/Bootstrap.DataAccess/Dict.cs
+++ b/Bootstrap.DataAccess/Dict.cs
@@ -1,180 +1,186 @@
-using Bootstrap.Security;
-using Bootstrap.Security.DataAccess;
-using Longbow;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Bootstrap.DataAccess
-{
- ///
- ///
- ///
- public class Dict : BootstrapDict
- {
- ///
- /// 删除字典中的数据
- ///
- /// 需要删除的IDs
- ///
- public virtual bool Delete(IEnumerable value)
- {
- if (!value.Any()) return true;
- var ids = string.Join(",", value);
- string sql = $"where ID in ({ids})";
- DbManager.Create().Delete(sql);
- return true;
- }
-
- ///
- /// 保存新建/更新的字典信息
- ///
- ///
- ///
- public virtual bool Save(BootstrapDict dict)
- {
- if (dict.Category.Length > 50) dict.Category = dict.Category.Substring(0, 50);
- if (dict.Name.Length > 50) dict.Name = dict.Name.Substring(0, 50);
- if (dict.Code.Length > 50) dict.Code = dict.Code.Substring(0, 50);
-
- DbManager.Create().Save(dict);
- return true;
- }
-
- ///
- /// 保存网站个性化设置
- ///
- ///
- ///
- public virtual bool SaveSettings(BootstrapDict dict)
- {
- DbManager.Create().Update("set Code = @Code where Category = @Category and Name = @Name", dict);
- return true;
- }
-
- ///
- /// 获取字典分类名称
- ///
- ///
- public virtual IEnumerable RetrieveCategories() => DictHelper.RetrieveDicts().OrderBy(d => d.Category).Select(d => d.Category).Distinct();
-
- ///
- ///
- ///
- ///
- public virtual string RetrieveWebTitle() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "网站标题" && d.Category == "网站设置" && d.Define == 0) ?? new BootstrapDict() { Code = "后台管理系统" }).Code;
-
- ///
- ///
- ///
- ///
- public virtual string RetrieveWebFooter() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "网站页脚" && d.Category == "网站设置" && d.Define == 0) ?? new BootstrapDict() { Code = "2016 © 通用后台管理系统" }).Code;
-
- ///
- /// 获得系统中配置的可以使用的网站样式
- ///
- ///
- public virtual IEnumerable RetrieveThemes() => DictHelper.RetrieveDicts().Where(d => d.Category == "网站样式");
-
- ///
- /// 获得网站设置中的当前样式
- ///
- ///
- public virtual string RetrieveActiveTheme()
- {
- var theme = DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "使用样式" && d.Category == "当前样式" && d.Define == 0);
- return theme == null ? string.Empty : (theme.Code.Equals("site.css", StringComparison.OrdinalIgnoreCase) ? string.Empty : theme.Code);
- }
-
- ///
- /// 获取头像路径
- ///
- ///
- public virtual string RetrieveIconFolderPath() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "头像路径" && d.Category == "头像地址" && d.Define == 0) ?? new BootstrapDict { Code = "~/images/uploader/" }).Code;
-
- ///
- /// 获得默认的前台首页地址,默认为~/Home/Index
- ///
- ///
- ///
- public virtual string RetrieveHomeUrl(string appCode)
- {
- // https://gitee.com/LongbowEnterprise/dashboard/issues?id=IS0WK
- var url = "~/Home/Index";
- var dicts = DictHelper.RetrieveDicts();
- if (appCode != "0")
- {
- var appUrl = dicts.FirstOrDefault(d => d.Name.Equals(appCode, StringComparison.OrdinalIgnoreCase) && d.Category == "应用首页" && d.Define == 0)?.Code;
- if (!string.IsNullOrEmpty(appUrl))
- {
- url = appUrl;
- return url;
- }
- }
- var defaultUrl = dicts.FirstOrDefault(d => d.Name == "前台首页" && d.Category == "网站设置" && d.Define == 0)?.Code;
- if (!string.IsNullOrEmpty(defaultUrl)) url = defaultUrl;
- return url;
- }
-
- ///
- ///
- ///
- ///
- public virtual IEnumerable> RetrieveApps() => DictHelper.RetrieveDicts().Where(d => d.Category == "应用程序" && d.Define == 0).Select(d => new KeyValuePair(d.Code, d.Name)).OrderBy(d => d.Key);
-
- ///
- /// 通过数据库获得所有字典表配置信息,缓存Key=DictHelper-RetrieveDicts
- ///
- ///
- public virtual IEnumerable RetrieveDicts() => DbHelper.RetrieveDicts();
-
- ///
- /// 程序异常时长 默认1月
- ///
- ///
- public int RetrieveExceptionsLogPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "程序异常保留时长" && d.Define == 0)?.Code, 1);
-
- ///
- /// 操作日志时长 默认12月
- ///
- ///
- public int RetrieveLogsPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "操作日志保留时长" && d.Define == 0)?.Code, 12);
-
- ///
- /// 登录日志时长 默认12月
- ///
- ///
- public int RetrieveLoginLogsPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "登录日志保留时长" && d.Define == 0)?.Code, 12);
-
- ///
- /// Cookie保存时长 默认7天
- ///
- ///
- public int RetrieveCookieExpiresPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "Cookie保留时长" && d.Define == 0)?.Code, 7);
-
- ///
- /// 获得 IP地理位置
- ///
- ///
- public string RetrieveLocaleIPSvr() => DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "IP地理位置接口" && d.Define == 0)?.Code;
-
- ///
- /// 获得 项目是否获取登录地点 默认为false
- ///
- /// 服务提供名称
- ///
- public string RetrieveLocaleIPSvrUrl(string ipSvr) => DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == ipSvr && d.Define == 0)?.Code;
-
- ///
- /// 获得 访问日志保留时长 默认为1个月
- ///
- ///
- public int RetrieveAccessLogPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "访问日志保留时长" && d.Define == 0)?.Code, 1);
-
- ///
- /// 获得 是否为演示系统 默认为 false 不是演示系统
- ///
- ///
- public bool RetrieveSystemModel() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "演示系统" && d.Define == 0)?.Code, "0") == "1";
- }
-}
+using Bootstrap.Security;
+using Bootstrap.Security.DataAccess;
+using Longbow;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Bootstrap.DataAccess
+{
+ ///
+ ///
+ ///
+ public class Dict : BootstrapDict
+ {
+ ///
+ /// 删除字典中的数据
+ ///
+ /// 需要删除的IDs
+ ///
+ public virtual bool Delete(IEnumerable value)
+ {
+ if (!value.Any()) return true;
+ var ids = string.Join(",", value);
+ string sql = $"where ID in ({ids})";
+ DbManager.Create().Delete(sql);
+ return true;
+ }
+
+ ///
+ /// 保存新建/更新的字典信息
+ ///
+ ///
+ ///
+ public virtual bool Save(BootstrapDict dict)
+ {
+ if (dict.Category.Length > 50) dict.Category = dict.Category.Substring(0, 50);
+ if (dict.Name.Length > 50) dict.Name = dict.Name.Substring(0, 50);
+ if (dict.Code.Length > 50) dict.Code = dict.Code.Substring(0, 50);
+
+ DbManager.Create().Save(dict);
+ return true;
+ }
+
+ ///
+ /// 保存网站个性化设置
+ ///
+ ///
+ ///
+ public virtual bool SaveSettings(BootstrapDict dict)
+ {
+ DbManager.Create().Update("set Code = @Code where Category = @Category and Name = @Name", dict);
+ return true;
+ }
+
+ ///
+ /// 获取字典分类名称
+ ///
+ ///
+ public virtual IEnumerable RetrieveCategories() => DictHelper.RetrieveDicts().OrderBy(d => d.Category).Select(d => d.Category).Distinct();
+
+ ///
+ ///
+ ///
+ ///
+ public virtual string RetrieveWebTitle() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "网站标题" && d.Category == "网站设置" && d.Define == 0) ?? new BootstrapDict() { Code = "后台管理系统" }).Code;
+
+ ///
+ ///
+ ///
+ ///
+ public virtual string RetrieveWebFooter() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "网站页脚" && d.Category == "网站设置" && d.Define == 0) ?? new BootstrapDict() { Code = "2016 © 通用后台管理系统" }).Code;
+
+ ///
+ /// 获得系统中配置的可以使用的网站样式
+ ///
+ ///
+ public virtual IEnumerable RetrieveThemes() => DictHelper.RetrieveDicts().Where(d => d.Category == "网站样式");
+
+ ///
+ /// 获得网站设置中的当前样式
+ ///
+ ///
+ public virtual string RetrieveActiveTheme()
+ {
+ var theme = DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "使用样式" && d.Category == "当前样式" && d.Define == 0);
+ return theme == null ? string.Empty : (theme.Code.Equals("site.css", StringComparison.OrdinalIgnoreCase) ? string.Empty : theme.Code);
+ }
+
+ ///
+ /// 获取头像路径
+ ///
+ ///
+ public virtual string RetrieveIconFolderPath() => (DictHelper.RetrieveDicts().FirstOrDefault(d => d.Name == "头像路径" && d.Category == "头像地址" && d.Define == 0) ?? new BootstrapDict { Code = "~/images/uploader/" }).Code;
+
+ ///
+ /// 获得默认的前台首页地址,默认为~/Home/Index
+ ///
+ ///
+ ///
+ public virtual string RetrieveHomeUrl(string appCode)
+ {
+ // https://gitee.com/LongbowEnterprise/dashboard/issues?id=IS0WK
+ var url = "~/Home/Index";
+ var dicts = DictHelper.RetrieveDicts();
+ if (appCode != "0")
+ {
+ var appUrl = dicts.FirstOrDefault(d => d.Name.Equals(appCode, StringComparison.OrdinalIgnoreCase) && d.Category == "应用首页" && d.Define == 0)?.Code;
+ if (!string.IsNullOrEmpty(appUrl))
+ {
+ url = appUrl;
+ return url;
+ }
+ }
+ var defaultUrl = dicts.FirstOrDefault(d => d.Name == "前台首页" && d.Category == "网站设置" && d.Define == 0)?.Code;
+ if (!string.IsNullOrEmpty(defaultUrl)) url = defaultUrl;
+ return url;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public virtual IEnumerable> RetrieveApps() => DictHelper.RetrieveDicts().Where(d => d.Category == "应用程序" && d.Define == 0).Select(d => new KeyValuePair(d.Code, d.Name)).OrderBy(d => d.Key);
+
+ ///
+ /// 通过数据库获得所有字典表配置信息,缓存Key=DictHelper-RetrieveDicts
+ ///
+ ///
+ public virtual IEnumerable RetrieveDicts() => DbHelper.RetrieveDicts();
+
+ ///
+ /// 程序异常时长 默认1月
+ ///
+ ///
+ public int RetrieveExceptionsLogPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "程序异常保留时长" && d.Define == 0)?.Code, 1);
+
+ ///
+ /// 操作日志时长 默认12月
+ ///
+ ///
+ public int RetrieveLogsPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "操作日志保留时长" && d.Define == 0)?.Code, 12);
+
+ ///
+ /// 登录日志时长 默认12月
+ ///
+ ///
+ public int RetrieveLoginLogsPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "登录日志保留时长" && d.Define == 0)?.Code, 12);
+
+ ///
+ /// Cookie保存时长 默认7天
+ ///
+ ///
+ public int RetrieveCookieExpiresPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "Cookie保留时长" && d.Define == 0)?.Code, 7);
+
+ ///
+ /// 获得 IP地理位置
+ ///
+ ///
+ public string RetrieveLocaleIPSvr() => DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "IP地理位置接口" && d.Define == 0)?.Code;
+
+ ///
+ /// 获得 项目是否获取登录地点 默认为false
+ ///
+ /// 服务提供名称
+ ///
+ public string RetrieveLocaleIPSvrUrl(string ipSvr) => DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == ipSvr && d.Define == 0)?.Code;
+
+ ///
+ /// 获得 访问日志保留时长 默认为1个月
+ ///
+ ///
+ public int RetrieveAccessLogPeriod() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "访问日志保留时长" && d.Define == 0)?.Code, 1);
+
+ ///
+ /// 获得 是否为演示系统 默认为 false 不是演示系统
+ ///
+ ///
+ public bool RetrieveSystemModel() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "演示系统" && d.Define == 0)?.Code, "0") == "1";
+
+ ///
+ /// 获得 验证码图床地址
+ ///
+ ///
+ public string RetrieveImagesLibUrl() => LgbConvert.ReadValue(DictHelper.RetrieveDicts().FirstOrDefault(d => d.Category == "系统设置" && d.Name == "验证码图床" && d.Define == 0)?.Code, "https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/");
+ }
+}
diff --git a/Bootstrap.DataAccess/Helper/DictHelper.cs b/Bootstrap.DataAccess/Helper/DictHelper.cs
index d68fabaa..0f704c63 100644
--- a/Bootstrap.DataAccess/Helper/DictHelper.cs
+++ b/Bootstrap.DataAccess/Helper/DictHelper.cs
@@ -1,6 +1,8 @@
using Bootstrap.Security;
using Longbow.Cache;
using Longbow.Data;
+using Longbow.Web;
+using System;
using System.Collections.Generic;
using System.Linq;
@@ -75,6 +77,21 @@ namespace Bootstrap.DataAccess
return ret;
}
+ ///
+ ///
+ ///
+ ///
+ public static void ConfigIPLocator(IPLocatorOption op)
+ {
+ var name = RetrieveLocaleIPSvr();
+ if (!string.IsNullOrEmpty(name) && !name.Equals("None", StringComparison.OrdinalIgnoreCase))
+ {
+ var url = RetrieveLocaleIPSvrUrl(name);
+ op.Locator = string.IsNullOrEmpty(url) ? null : DefaultIPLocatorProvider.CreateLocator(name);
+ op.Url = string.IsNullOrEmpty(url) ? string.Empty : $"{url}{op.IP}";
+ }
+ }
+
///
/// 保存网站个性化设置
///
@@ -183,5 +200,11 @@ namespace Bootstrap.DataAccess
///
///
public static bool RetrieveSystemModel() => DbContextManager.Create().RetrieveSystemModel();
+
+ ///
+ /// 获得验证码图床地址
+ ///
+ ///
+ public static string RetrieveImagesLibUrl() => DbContextManager.Create().RetrieveImagesLibUrl();
}
}
diff --git a/Bootstrap.DataAccess/Helper/TraceHelper.cs b/Bootstrap.DataAccess/Helper/TraceHelper.cs
index d4c31808..d85bec1c 100644
--- a/Bootstrap.DataAccess/Helper/TraceHelper.cs
+++ b/Bootstrap.DataAccess/Helper/TraceHelper.cs
@@ -1,5 +1,7 @@
using Longbow.Data;
+using Longbow.Web;
using Longbow.Web.Mvc;
+using Microsoft.AspNetCore.Http;
using PetaPoco;
using System;
@@ -11,8 +13,27 @@ namespace Bootstrap.DataAccess
///
/// 保存访问历史记录
///
- ///
- public static void Save(Trace p) => DbContextManager.Create().Save(p);
+ ///
+ ///
+ public static void Save(HttpContext context, OnlineUser v)
+ {
+ if (context.User.Identity.IsAuthenticated)
+ {
+ var user = UserHelper.RetrieveUserByUserName(context.User.Identity.Name);
+ v.UserName = user.UserName;
+ v.DisplayName = user.DisplayName;
+ DbContextManager.Create().Save(new Trace
+ {
+ Ip = v.Ip,
+ RequestUrl = v.RequestUrl,
+ LogTime = v.LastAccessTime,
+ City = v.Location,
+ Browser = v.Browser,
+ OS = v.OS,
+ UserName = v.UserName
+ });
+ }
+ }
///
/// 获得指定IP历史访问记录
diff --git a/DatabaseScripts/InitData.sql b/DatabaseScripts/InitData.sql
index 836f9c2d..cf6e47d4 100644
--- a/DatabaseScripts/InitData.sql
+++ b/DatabaseScripts/InitData.sql
@@ -44,6 +44,7 @@ INSERT [dbo].[Dicts] ([Category], [Name], [Code], [Define]) VALUES (N'系统设
INSERT [dbo].[Dicts] ([Category], [Name], [Code], [Define]) VALUES (N'系统设置', N'BaiDuIPSvr', 'http://api.map.baidu.com/location/ip?ak=6lvVPMDlm2gjLpU0aiqPsHXi2OiwGQRj&ip=', 0)
INSERT [dbo].[Dicts] ([Category], [Name], [Code], [Define]) VALUES (N'系统设置', N'JuheIPSvr', 'http://apis.juhe.cn/ip/ipNew?key=f57102d1b9fadd3f4a1c29072d0c0206&ip=', 0)
INSERT [dbo].[Dicts] ([Category], [Name], [Code], [Define]) VALUES (N'系统设置', N'演示系统', '0', 0)
+INSERT [dbo].[Dicts] ([Category], [Name], [Code], [Define]) VALUES (N'系统设置', N'验证码图床', 'https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/', 0)
DELETE FROM Navigations Where Category = N'0'
INSERT [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, N'后台管理', 10, N'fa fa-gear', N'~/Admin/Index', N'0')
diff --git a/DatabaseScripts/MongoDB/BootstrapAdmin.Dicts.json b/DatabaseScripts/MongoDB/BootstrapAdmin.Dicts.json
index 83a3db13..9cf9fbf8 100644
--- a/DatabaseScripts/MongoDB/BootstrapAdmin.Dicts.json
+++ b/DatabaseScripts/MongoDB/BootstrapAdmin.Dicts.json
@@ -341,5 +341,12 @@
"Name": "演示系统",
"Code": "0",
"Define": NumberInt(0)
+ },
+ {
+ "_id": ObjectId("5bd6c73d5fa31256f77e4a49"),
+ "Category": "系统设置",
+ "Name": "验证码图床",
+ "Code": "https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/",
+ "Define": NumberInt(0)
}
]
\ No newline at end of file
diff --git a/DatabaseScripts/MySQL/initData.sql b/DatabaseScripts/MySQL/initData.sql
index e4ba462a..348e6142 100644
--- a/DatabaseScripts/MySQL/initData.sql
+++ b/DatabaseScripts/MySQL/initData.sql
@@ -41,6 +41,7 @@ INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'IP地
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'BaiDuIPSvr', 'http://api.map.baidu.com/location/ip?ak=6lvVPMDlm2gjLpU0aiqPsHXi2OiwGQRj&ip=', 0);
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'JuheIPSvr', 'http://apis.juhe.cn/ip/ipNew?key=f57102d1b9fadd3f4a1c29072d0c0206&ip=', 0);
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', '演示系统', '0', 0);
+INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', '验证码图床', 'https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/', 0);
DELETE FROM Navigations Where Category = '0';
INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category) VALUES (0, '后台管理', 10, 'fa fa-gear', '~/Admin/Index', '0');
diff --git a/DatabaseScripts/Postgresql/initData.sql b/DatabaseScripts/Postgresql/initData.sql
index e82d2ca7..0489a044 100644
--- a/DatabaseScripts/Postgresql/initData.sql
+++ b/DatabaseScripts/Postgresql/initData.sql
@@ -40,6 +40,7 @@ INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'IP地
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'BaiDuIPSvr', 'http://api.map.baidu.com/location/ip?ak=6lvVPMDlm2gjLpU0aiqPsHXi2OiwGQRj&ip=', 0);
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', 'JuheIPSvr', 'http://apis.juhe.cn/ip/ipNew?key=f57102d1b9fadd3f4a1c29072d0c0206&ip=', 0);
INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', '演示系统', '0', 0);
+INSERT INTO Dicts (Category, Name, Code, Define) VALUES ('系统设置', '验证码图床', 'https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/', 0);
DELETE FROM Navigations Where Category = '0';
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '后台管理', 10, 'fa fa-gear', '~/Admin/Index', '0');
diff --git a/DatabaseScripts/SQLite/InitData.sql b/DatabaseScripts/SQLite/InitData.sql
index d05ec895..bc3d0596 100644
--- a/DatabaseScripts/SQLite/InitData.sql
+++ b/DatabaseScripts/SQLite/InitData.sql
@@ -39,6 +39,7 @@ INSERT INTO [Dicts] ([Category], [Name], [Code], [Define]) VALUES ('系统设置
INSERT INTO [Dicts] ([Category], [Name], [Code], [Define]) VALUES ('系统设置', 'BaiDuIPSvr', 'http://api.map.baidu.com/location/ip?ak=6lvVPMDlm2gjLpU0aiqPsHXi2OiwGQRj&ip=', 0);
INSERT INTO [Dicts] ([Category], [Name], [Code], [Define]) VALUES ('系统设置', 'JuheIPSvr', 'http://apis.juhe.cn/ip/ipNew?key=f57102d1b9fadd3f4a1c29072d0c0206&ip=', 0);
INSERT INTO [Dicts] ([Category], [Name], [Code], [Define]) VALUES ('系统设置', '演示系统', '0', 0);
+INSERT INTO [Dicts] ([Category], [Name], [Code], [Define]) VALUES ('系统设置', '验证码图床', 'https://longbow-1258823021.cos.ap-shanghai.myqcloud.com/pic/280/150/', 0);
DELETE FROM Navigations Where Category = '0';
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, '后台管理', 10, 'fa fa-gear', '~/Admin/Index', '0');
@@ -136,6 +137,8 @@ INSERT into [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category
INSERT into [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], [Application]) VALUES (0, '测试页面', 20, 'fa fa-fa', '#', '1', 2);
INSERT into [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], [Application]) VALUES (last_insert_rowid(), '关于', 10, 'fa fa-fa', '~/Home/About', '1', 2);
+INSERT into [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], [Application]) VALUES (0, '返回码云', 20, 'fa fa-fa', 'https://gitee.com/LongbowEnterprise/BootstrapAdmin', '1', 2);
+
-- 菜单授权
DELETE FROM NavigationRole Where NavigationID in (Select ID From Navigations Where [Application] = 2);
INSERT INTO NavigationRole SELECT NULL, ID, 2 FROM Navigations Where [Application] = 2;
diff --git a/UnitTest/Bootstrap.Admin/Api/OnlineTest.cs b/UnitTest/Bootstrap.Admin/Api/OnlineTest.cs
index 7398c049..72982baf 100644
--- a/UnitTest/Bootstrap.Admin/Api/OnlineTest.cs
+++ b/UnitTest/Bootstrap.Admin/Api/OnlineTest.cs
@@ -1,32 +1,33 @@
-using System;
-using System.Collections.Generic;
-using Xunit;
-
-namespace Bootstrap.Admin.Api
-{
- public class OnlineTest : ControllerTest
- {
- public OnlineTest(BAWebHost factory) : base(factory, "api/OnlineUsers") { }
-
- [Fact]
- public async void Get_Ok()
- {
- var users = await Client.GetAsJsonAsync>();
- Assert.Single(users);
- }
-
- [Fact]
- public async void GetById_Ok()
- {
- var urls = await Client.GetAsJsonAsync>>("UnitTest");
- Assert.Empty(urls);
+using Longbow.Web;
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Bootstrap.Admin.Api
+{
+ public class OnlineTest : ControllerTest
+ {
+ public OnlineTest(BAWebHost factory) : base(factory, "api/OnlineUsers") { }
+
+ [Fact]
+ public async void Get_Ok()
+ {
+ var users = await Client.GetAsJsonAsync>();
+ Assert.Single(users);
}
- [Fact]
- public async void Put_Ok()
- {
- var ret = await Client.PutAsJsonAsync("");
- Assert.False(ret);
+ [Fact]
+ public async void GetById_Ok()
+ {
+ var urls = await Client.GetAsJsonAsync>>("UnitTest");
+ Assert.Empty(urls);
}
- }
-}
+
+ [Fact]
+ public async void Put_Ok()
+ {
+ var ret = await Client.PutAsJsonAsync("");
+ Assert.False(ret);
+ }
+ }
+}
diff --git a/UnitTest/Bootstrap.Admin/Api/UsersTest.cs b/UnitTest/Bootstrap.Admin/Api/UsersTest.cs
index 57606f61..da8b9dc0 100644
--- a/UnitTest/Bootstrap.Admin/Api/UsersTest.cs
+++ b/UnitTest/Bootstrap.Admin/Api/UsersTest.cs
@@ -1,12 +1,10 @@
using Bootstrap.DataAccess;
-using Longbow.Data;
using Longbow.Web.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Xunit;
-using DbManager = Longbow.Data.DbManager;
namespace Bootstrap.Admin.Api
{
diff --git a/UnitTest/UnitTest.csproj b/UnitTest/UnitTest.csproj
index e3f5367d..dd5e3b10 100644
--- a/UnitTest/UnitTest.csproj
+++ b/UnitTest/UnitTest.csproj
@@ -9,10 +9,10 @@
-
-
+
+
-
+
all