Merge branch 'abp' of https://gitee.com/ccnetcore/Yi into abp
This commit is contained in:
@@ -4,9 +4,6 @@
|
|||||||
<h2 align="center">集大成者,终究轮子</h2>
|
<h2 align="center">集大成者,终究轮子</h2>
|
||||||
|
|
||||||
[English](README-en.md) | 简体中文
|
[English](README-en.md) | 简体中文
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
****
|
****
|
||||||
### 简介:
|
### 简介:
|
||||||
YiFramework是一个基于.Net8+Abp.vNext+SqlSugar的DDD领域驱动设计后端开源框架
|
YiFramework是一个基于.Net8+Abp.vNext+SqlSugar的DDD领域驱动设计后端开源框架
|
||||||
@@ -59,12 +56,11 @@ Rbac演示地址:https://ccnetcore.com:1000 (用户cc、密码123456)
|
|||||||
|
|
||||||
****
|
****
|
||||||
### 详细到爆炸的Yi框架教程导航:
|
### 详细到爆炸的Yi框架教程导航:
|
||||||
(既然选择开源,就本该怀揣着最纯粹分享的内心,我们全套打包分享,开源包括且不仅仅是:文档、框架代码、模块代码、运维CICD等,希望能够帮助到您)
|
|
||||||
|
|
||||||
1. [框架快速开始教程](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742)(已完成)
|
1. [框架快速开始教程](https://ccnetcore.com/article/aaa00329-7f35-d3fe-d258-3a0f8380b742)(已完成)
|
||||||
2. [框架功能模块教程](https://ccnetcore.com/article/8c464ab3-8ba5-2761-a4b0-3a0f83a9f312)(已完成)
|
2. [框架功能模块教程](https://ccnetcore.com/article/8c464ab3-8ba5-2761-a4b0-3a0f83a9f312)(已完成)
|
||||||
3. [实战演练开发教程](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)
|
3. [实战演练开发教程](https://ccnetcore.com/article/e89c9593-f337-ada7-d108-3a0f83ae48e6)
|
||||||
4. [橙子运维CICD教程](https://ccnetcore.com/article/6b80ed42-50cd-db15-c073-3a0fa8f7fd77)(已完成)
|
|
||||||
|
|
||||||
****
|
****
|
||||||
### 它的理念:
|
### 它的理念:
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.SqlSugarCo
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "audit-logging", "audit-logging", "{73CCF2C4-B9FD-44AB-8D4B-0A421805B094}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "audit-logging", "audit-logging", "{73CCF2C4-B9FD-44AB-8D4B-0A421805B094}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.AuditLogging.SqlSugarCore", "module\audit-logging\Yi.AuditLogging.SqlSugarCore\Yi.AuditLogging.SqlSugarCore.csproj", "{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.AuditLogging.SqlSugarCore", "module\audit-logging\Yi.AuditLogging.SqlSugarCore\Yi.AuditLogging.SqlSugarCore.csproj", "{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
|
|||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string? Types { get; set; }
|
public string? Types { get; set; }
|
||||||
public string? Introduction { get; set; }
|
public string? Introduction { get; set; }
|
||||||
public int AgreeNum { get; set; }
|
|
||||||
public int SeeNum { get; set; }
|
|
||||||
public string Content { get; set; }
|
public string Content { get; set; }
|
||||||
public string? Color { get; set; }
|
public string? Color { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Quartz.Logging;
|
||||||
|
using Volo.Abp;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Yi.Framework.Bbs.Application.Services.Authentication
|
||||||
|
{
|
||||||
|
public class QQAuthService : IRemoteService, ITransientDependency
|
||||||
|
{
|
||||||
|
private HttpContext HttpContext { get; set; }
|
||||||
|
private ILogger<QQAuthService> _logger;
|
||||||
|
public QQAuthService(IHttpContextAccessor httpContextAccessor, ILogger<QQAuthService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
HttpContext = httpContextAccessor.HttpContext ?? throw new ApplicationException("未注册Http");
|
||||||
|
}
|
||||||
|
[HttpGet("/auth/qq")]
|
||||||
|
public async Task AuthQQAsync()
|
||||||
|
{
|
||||||
|
var data = await HttpContext.AuthenticateAsync("QQ");
|
||||||
|
_logger.LogError($"QQ回调信息:{Newtonsoft.Json.JsonConvert.SerializeObject(data)}");
|
||||||
|
_logger.LogError($"QQ回调身份:{Newtonsoft.Json.JsonConvert.SerializeObject(data.Principal)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,11 @@ namespace Yi.Framework.Bbs.Domain.Entities
|
|||||||
public Guid? LastModifierId { get; set; }
|
public Guid? LastModifierId { get; set; }
|
||||||
|
|
||||||
public DateTime? LastModificationTime { get; set; }
|
public DateTime? LastModificationTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排序
|
||||||
|
/// </summary>
|
||||||
|
public int OrderNum { get; set; } = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ArticleEntityExtensions
|
public static class ArticleEntityExtensions
|
||||||
|
|||||||
@@ -3,20 +3,29 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Yi.Framework.Bbs.Domain.Entities;
|
using Yi.Framework.Bbs.Domain.Entities;
|
||||||
using Yi.Framework.Bbs.Domain.Shared.Model;
|
using Yi.Framework.Bbs.Domain.Shared.Model;
|
||||||
|
using Yi.Framework.Core.Data;
|
||||||
|
|
||||||
namespace Yi.Framework.Bbs.Domain.Managers.ArticleImport
|
namespace Yi.Framework.Bbs.Domain.Managers.ArticleImport
|
||||||
{
|
{
|
||||||
public abstract class AbstractArticleImport
|
public abstract class AbstractArticleImport
|
||||||
{
|
{
|
||||||
public virtual List<ArticleEntity> Import(Guid discussId,Guid articleParentId, List<FileObject> fileObjs)
|
public void SetLogger(ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
LoggerFactory = loggerFactory;
|
||||||
|
}
|
||||||
|
protected ILoggerFactory LoggerFactory { get; set; }
|
||||||
|
public virtual List<ArticleEntity> Import(Guid discussId, Guid articleParentId, List<FileObject> fileObjs)
|
||||||
{
|
{
|
||||||
var articles = Convert(fileObjs);
|
var articles = Convert(fileObjs);
|
||||||
|
var orderNum = 0;
|
||||||
articles.ForEach(article =>
|
articles.ForEach(article =>
|
||||||
{
|
{
|
||||||
article.DiscussId = discussId;
|
article.DiscussId = discussId;
|
||||||
article.ParentId = articleParentId;
|
article.ParentId = articleParentId;
|
||||||
|
article.OrderNum = ++orderNum;
|
||||||
});
|
});
|
||||||
return articles;
|
return articles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Yi.Framework.Bbs.Domain.Entities;
|
using Yi.Framework.Bbs.Domain.Entities;
|
||||||
using Yi.Framework.Bbs.Domain.Shared.Model;
|
using Yi.Framework.Bbs.Domain.Shared.Model;
|
||||||
|
|
||||||
@@ -12,6 +13,8 @@ namespace Yi.Framework.Bbs.Domain.Managers.ArticleImport
|
|||||||
{
|
{
|
||||||
public override List<ArticleEntity> Convert(List<FileObject> fileObjs)
|
public override List<ArticleEntity> Convert(List<FileObject> fileObjs)
|
||||||
{
|
{
|
||||||
|
var logger = LoggerFactory.CreateLogger<VuePressArticleImport>();
|
||||||
|
|
||||||
//排序及处理目录名称
|
//排序及处理目录名称
|
||||||
var fileNameHandler = fileObjs.OrderBy(x => x.FileName).Select(x =>
|
var fileNameHandler = fileObjs.OrderBy(x => x.FileName).Select(x =>
|
||||||
{
|
{
|
||||||
@@ -24,36 +27,44 @@ namespace Yi.Framework.Bbs.Domain.Managers.ArticleImport
|
|||||||
|
|
||||||
|
|
||||||
//处理内容
|
//处理内容
|
||||||
var fileContentHandler= fileNameHandler.Select(x =>
|
var fileContentHandler = fileNameHandler.Select(x =>
|
||||||
{
|
{
|
||||||
var f = new FileObject { FileName = x.FileName };
|
logger.LogError($"老的值:{x.Content}");
|
||||||
var lines = x.Content.SplitToLines();
|
var f = new FileObject { FileName = x.FileName };
|
||||||
|
var lines = x.Content.SplitToLines();
|
||||||
|
|
||||||
var num = 0;
|
var num = 0;
|
||||||
var startIndex = 0;
|
var startIndex = 0;
|
||||||
for (int i = 0; i < lines.Length; i++)
|
for (int i = 0; i < lines.Length; i++)
|
||||||
{
|
{
|
||||||
if (lines[i] == "---")
|
//编码问题
|
||||||
{
|
if (lines[i] == "---")
|
||||||
num++;
|
{
|
||||||
if (num == 2)
|
num++;
|
||||||
{
|
if (num == 2)
|
||||||
startIndex = i;
|
{
|
||||||
|
startIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
var linesRef = lines.ToList();
|
||||||
}
|
|
||||||
var linesRef = lines.ToList();
|
|
||||||
|
|
||||||
linesRef.RemoveRange(0, startIndex+1);
|
|
||||||
var result = string.Join(Environment.NewLine, linesRef);
|
|
||||||
f.Content = result;
|
|
||||||
return f;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
if (startIndex != 0)
|
||||||
|
{
|
||||||
|
linesRef.RemoveRange(0, startIndex + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//去除头部
|
||||||
|
linesRef.RemoveRange(0,6);
|
||||||
|
}
|
||||||
|
var result = string.Join(Environment.NewLine, linesRef);
|
||||||
|
f.Content = result;
|
||||||
|
return f;
|
||||||
|
});
|
||||||
var output = fileContentHandler.Select(x => new ArticleEntity() { Content = x.Content, Name = x.FileName }).ToList();
|
var output = fileContentHandler.Select(x => new ArticleEntity() { Content = x.Content, Name = x.FileName }).ToList();
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace Yi.Framework.Bbs.Domain.Managers
|
|||||||
|
|
||||||
default: abstractArticleImport = new DefaultArticleImport(); break;
|
default: abstractArticleImport = new DefaultArticleImport(); break;
|
||||||
}
|
}
|
||||||
|
abstractArticleImport.SetLogger(LoggerFactory);
|
||||||
var articleHandled = abstractArticleImport.Import(discussId, articleParentId, fileObjs);
|
var articleHandled = abstractArticleImport.Import(discussId, articleParentId, fileObjs);
|
||||||
|
|
||||||
await _articleRepository.InsertManyAsync(articleHandled);
|
await _articleRepository.InsertManyAsync(articleHandled);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Yi.Framework.Bbs.SqlSugarCore.Repositories
|
|||||||
|
|
||||||
public async Task<List<ArticleEntity>> GetTreeAsync(Expression<Func<ArticleEntity, bool>> where)
|
public async Task<List<ArticleEntity>> GetTreeAsync(Expression<Func<ArticleEntity, bool>> where)
|
||||||
{
|
{
|
||||||
return await _DbQueryable.Where(where).OrderBy(x=>x.CreationTime).ToTreeAsync(x => x.Children, x => x.ParentId, Guid.Empty);
|
return await _DbQueryable.Where(where).OrderBy(x=>x.OrderNum).OrderBy(x=>x.CreationTime).ToTreeAsync(x => x.Children, x => x.ParentId, Guid.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Serilog;
|
|||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Yi.Abp.Web;
|
using Yi.Abp.Web;
|
||||||
|
|
||||||
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־,<2C><>ʹ<EFBFBD><CAB9>{SourceContext}<EFBFBD><EFBFBD>¼
|
//创建日志,可使用{SourceContext}记录
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.MinimumLevel.Debug()
|
.MinimumLevel.Debug()
|
||||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||||
@@ -15,7 +15,7 @@ Log.Logger = new LoggerConfiguration()
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Log.Information("Yi<EFBFBD><EFBFBD><EFBFBD><EFBFBD>-Abp.vNext<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
Log.Information("Yi框架-Abp.vNext,启动!");
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.WebHost.UseUrls(builder.Configuration["App:SelfUrl"]);
|
builder.WebHost.UseUrls(builder.Configuration["App:SelfUrl"]);
|
||||||
@@ -28,7 +28,7 @@ try
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Fatal(ex, "Yi<EFBFBD><EFBFBD><EFBFBD><EFBFBD>-Abp.vNext<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ը<EFBFBD><EFBFBD>");
|
Log.Fatal(ex, "Yi框架-Abp.vNext,爆炸!");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ namespace Yi.Abp.Web
|
|||||||
service.AddControllers().AddNewtonsoftJson(options =>
|
service.AddControllers().AddNewtonsoftJson(options =>
|
||||||
{
|
{
|
||||||
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
|
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
|
||||||
options.SerializerSettings.Converters.Add(new StringEnumConverter());
|
options.SerializerSettings.Converters.Add(new StringEnumConverter());
|
||||||
});
|
});
|
||||||
|
|
||||||
Configure<AbpAntiForgeryOptions>(options =>
|
Configure<AbpAntiForgeryOptions>(options =>
|
||||||
@@ -103,33 +103,33 @@ namespace Yi.Abp.Web
|
|||||||
//jwt鉴权
|
//jwt鉴权
|
||||||
var jwtOptions = configuration.GetSection(nameof(JwtOptions)).Get<JwtOptions>();
|
var jwtOptions = configuration.GetSection(nameof(JwtOptions)).Get<JwtOptions>();
|
||||||
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||||
.AddJwtBearer(options =>
|
.AddJwtBearer(options =>
|
||||||
|
{
|
||||||
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
{
|
{
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
ClockSkew = TimeSpan.Zero,
|
||||||
|
ValidateIssuer = true,
|
||||||
|
ValidateAudience = true,
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
ValidIssuer = jwtOptions.Issuer,
|
||||||
|
ValidAudience = jwtOptions.Audience,
|
||||||
|
RequireExpirationTime = true,
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecurityKey))
|
||||||
|
};
|
||||||
|
options.Events = new JwtBearerEvents
|
||||||
|
{
|
||||||
|
OnMessageReceived = context =>
|
||||||
{
|
{
|
||||||
ClockSkew = TimeSpan.Zero,
|
var accessToken = context.Request.Query["access_token"];
|
||||||
ValidateIssuer = true,
|
if (!string.IsNullOrEmpty(accessToken))
|
||||||
ValidateAudience = true,
|
|
||||||
ValidateLifetime = true,
|
|
||||||
ValidateIssuerSigningKey = true,
|
|
||||||
ValidIssuer = jwtOptions.Issuer,
|
|
||||||
ValidAudience = jwtOptions.Audience,
|
|
||||||
RequireExpirationTime = true,
|
|
||||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecurityKey))
|
|
||||||
};
|
|
||||||
options.Events = new JwtBearerEvents
|
|
||||||
{
|
|
||||||
OnMessageReceived = context =>
|
|
||||||
{
|
{
|
||||||
var accessToken = context.Request.Query["access_token"];
|
context.Token = accessToken;
|
||||||
if (!string.IsNullOrEmpty(accessToken))
|
|
||||||
{
|
|
||||||
context.Token = accessToken;
|
|
||||||
}
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
};
|
return Task.CompletedTask;
|
||||||
});
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
//授权
|
//授权
|
||||||
context.Services.AddAuthorization();
|
context.Services.AddAuthorization();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-badge :value="props.badge ?? ''" class="box-card">
|
<el-badge class="box-card">
|
||||||
<el-card shadow="never" :style="{ 'border-color': discuss.color }">
|
<el-card shadow="never" :style="{ 'border-color': discuss.color }">
|
||||||
<el-row>
|
<el-row>
|
||||||
<!-- 头部 -->
|
<!-- 头部 -->
|
||||||
@@ -69,6 +69,12 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
<div class="pinned" v-if="props.badge">
|
||||||
|
<div class="icon">
|
||||||
|
<el-icon><Upload /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="text">{{ props.badge ?? "" }}</div>
|
||||||
|
</div>
|
||||||
</el-badge>
|
</el-badge>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -170,10 +176,28 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.box-card {
|
.box-card {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 15rem;
|
min-height: 15rem;
|
||||||
/* right: calc(1px + var(--el-badge-size)/ 2) !important; */
|
/* right: calc(1px + var(--el-badge-size)/ 2) !important; */
|
||||||
/* top: 0 !important; */
|
/* top: 0 !important; */
|
||||||
|
.pinned {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: rgb(8, 119, 229);
|
||||||
|
background-color: #ecf5ff;
|
||||||
|
font-size: 14px;
|
||||||
|
.icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-title {
|
.item-title {
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ service.interceptors.response.use(
|
|||||||
// 对响应错误做点什么
|
// 对响应错误做点什么
|
||||||
if (error.message.indexOf("timeout") != -1) {
|
if (error.message.indexOf("timeout") != -1) {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: "danger",
|
type: "error",
|
||||||
message: "网络超时",
|
message: "网络超时",
|
||||||
});
|
});
|
||||||
} else if (error.message == "Network Error") {
|
} else if (error.message == "Network Error") {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: "danger",
|
type: "error",
|
||||||
message: "网络连接错误",
|
message: "网络连接错误",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -77,7 +77,7 @@ service.interceptors.response.use(
|
|||||||
if (status !== 200) {
|
if (status !== 200) {
|
||||||
if (status >= 500) {
|
if (status >= 500) {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: "danger",
|
type: "error",
|
||||||
message: "网络开小差了,请稍后再试",
|
message: "网络开小差了,请稍后再试",
|
||||||
});
|
});
|
||||||
return Promise.reject(new Error(message));
|
return Promise.reject(new Error(message));
|
||||||
@@ -85,7 +85,7 @@ service.interceptors.response.use(
|
|||||||
// 避开找不到后端接口的提醒
|
// 避开找不到后端接口的提醒
|
||||||
if (status !== 404) {
|
if (status !== 404) {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: "danger",
|
type: "error",
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ import useUserStore from "@/stores/user.js";
|
|||||||
import useConfigStore from "@/stores/config";
|
import useConfigStore from "@/stores/config";
|
||||||
import useAuths from "@/hooks/useAuths";
|
import useAuths from "@/hooks/useAuths";
|
||||||
import { Session } from "@/utils/storage";
|
import { Session } from "@/utils/storage";
|
||||||
|
import signalR from "@/utils/signalR";
|
||||||
|
|
||||||
const { getToken, clearStorage } = useAuths();
|
const { getToken, clearStorage } = useAuths();
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
@@ -103,6 +104,7 @@ const logout = async () => {
|
|||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
//异步
|
//异步
|
||||||
await userStore.logOut();
|
await userStore.logOut();
|
||||||
|
await signalR.close();
|
||||||
//删除成功后,跳转到主页
|
//删除成功后,跳转到主页
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
ElMessage({
|
ElMessage({
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
name: "discuss",
|
name: "discuss",
|
||||||
path: "/discuss/:plateId?/:isPublish?",
|
path: "/discuss/:plateId?/:isPublish?",
|
||||||
component: () => import("../views/Discuss.vue"),
|
component: () => import("../views/discuss/index.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "板块",
|
title: "板块",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ const socketStore = defineStore("socket", {
|
|||||||
this.onlineNum = value;
|
this.onlineNum = value;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
persist: {
|
|
||||||
key: "onlineInfo",
|
|
||||||
storage: window.sessionStorage,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default socketStore;
|
export default socketStore;
|
||||||
|
|||||||
@@ -327,8 +327,8 @@ const typeOptions = [
|
|||||||
label: "默认",
|
label: "默认",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "Vuepress",
|
value: "VuePress",
|
||||||
label: "Vuepress",
|
label: "VuePress",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const getFile = async (e) => {
|
const getFile = async (e) => {
|
||||||
|
|||||||
84
Yi.Bbs.Vue3/src/views/discuss/components/tabs.vue
Normal file
84
Yi.Bbs.Vue3/src/views/discuss/components/tabs.vue
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tabs">
|
||||||
|
<div class="left">
|
||||||
|
<div
|
||||||
|
class="items"
|
||||||
|
v-for="item in leftList"
|
||||||
|
@click="handleClick(item)"
|
||||||
|
:class="[modelValue === item.name ? 'active-tab' : '']"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="center"></div>
|
||||||
|
<div class="right">
|
||||||
|
<template v-for="(item, index) in rightList">
|
||||||
|
<div
|
||||||
|
class="items"
|
||||||
|
@click="handleClick(item)"
|
||||||
|
:class="[modelValue === item.name ? 'active-tab' : '']"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
<div class="line" v-if="index < rightList.length - 1">|</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, defineProps, defineEmits } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
tabList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue"], ["tab-change"]);
|
||||||
|
|
||||||
|
const leftList = computed(() =>
|
||||||
|
props.tabList.filter((item) => item.position === "left")
|
||||||
|
);
|
||||||
|
const rightList = computed(() =>
|
||||||
|
props.tabList.filter((item) => item.position === "right")
|
||||||
|
);
|
||||||
|
const handleClick = (item) => {
|
||||||
|
emit("update:modelValue", item.name);
|
||||||
|
emit("tab-change", item);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem 0rem;
|
||||||
|
color: #8c8c8c;
|
||||||
|
.left {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
width: 200px;
|
||||||
|
display: flex;
|
||||||
|
.line {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.items {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.active-tab {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -47,28 +47,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
<Tabs v-model="activeName" :tabList="tabList" @tab-change="handleClick" />
|
||||||
<el-tabs v-model="activeName" @tab-change="handleClick">
|
<div class="div-item" v-for="i in topDiscussList">
|
||||||
<el-tab-pane label="最新" name="new"> </el-tab-pane>
|
<DisscussCard :discuss="i" badge="置顶" />
|
||||||
<el-tab-pane label="推荐" name="suggest"> </el-tab-pane>
|
</div>
|
||||||
<el-tab-pane label="最热" name="host"> </el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
|
|
||||||
<el-collapse class="collapse-list" style="background-color: #f0f2f5">
|
|
||||||
<el-collapse-item>
|
|
||||||
<template #title>
|
|
||||||
<div class="collapse-top">
|
|
||||||
已置顶主题<el-icon class="header-icon">
|
|
||||||
<info-filled />
|
|
||||||
</el-icon>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div class="div-item" v-for="i in topDiscussList">
|
|
||||||
<DisscussCard :discuss="i" badge="置顶" />
|
|
||||||
</div>
|
|
||||||
</el-collapse-item>
|
|
||||||
</el-collapse>
|
|
||||||
<el-divider v-show="topDiscussList.length > 0" />
|
|
||||||
<template v-if="isDiscussFinished">
|
<template v-if="isDiscussFinished">
|
||||||
<div class="div-item" v-for="i in discussList">
|
<div class="div-item" v-for="i in discussList">
|
||||||
<DisscussCard :discuss="i" />
|
<DisscussCard :discuss="i" />
|
||||||
@@ -77,6 +59,9 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<Skeleton :isBorder="true" />
|
<Skeleton :isBorder="true" />
|
||||||
</template>
|
</template>
|
||||||
|
<div class="div-item" v-for="i in discussList">
|
||||||
|
<DisscussCard :discuss="i" />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="query.skipCount"
|
v-model:current-page="query.skipCount"
|
||||||
@@ -113,12 +98,13 @@ import { getPermission } from "@/utils/auth";
|
|||||||
import useAuths from "@/hooks/useAuths";
|
import useAuths from "@/hooks/useAuths";
|
||||||
import { Session } from "@/utils/storage";
|
import { Session } from "@/utils/storage";
|
||||||
import Skeleton from "@/components/Skeleton/index.vue";
|
import Skeleton from "@/components/Skeleton/index.vue";
|
||||||
|
import Tabs from "./components/tabs.vue";
|
||||||
|
|
||||||
const { getToken, clearStorage } = useAuths();
|
const { getToken, clearStorage } = useAuths();
|
||||||
//数据定义
|
//数据定义
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const activeName = ref("new");
|
const activeName = ref("suggest");
|
||||||
//主题内容
|
//主题内容
|
||||||
const discussList = ref([]);
|
const discussList = ref([]);
|
||||||
const isDiscussFinished = ref(false);
|
const isDiscussFinished = ref(false);
|
||||||
@@ -133,7 +119,7 @@ const query = reactive({
|
|||||||
type: activeName.value,
|
type: activeName.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = async (tab, event) => {
|
const handleClick = async (item) => {
|
||||||
query.type = activeName.value;
|
query.type = activeName.value;
|
||||||
await loadDiscussList();
|
await loadDiscussList();
|
||||||
};
|
};
|
||||||
@@ -212,6 +198,12 @@ watch(
|
|||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tabList = ref([
|
||||||
|
{ label: "全部文章", name: "suggest", position: "left" },
|
||||||
|
{ label: "最新", name: "new", position: "right" },
|
||||||
|
{ label: "最热", name: "host", position: "right" },
|
||||||
|
]);
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.discuss-box {
|
.discuss-box {
|
||||||
@@ -235,6 +227,10 @@ watch(
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 1rem 0rem;
|
margin: 1rem 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-tab {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
.collapse-top {
|
.collapse-top {
|
||||||
padding-left: 2rem;
|
padding-left: 2rem;
|
||||||
}
|
}
|
||||||
@@ -278,6 +274,7 @@ watch(
|
|||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 禁用状态下的样式 */
|
/* 禁用状态下的样式 */
|
||||||
.el-button.el-button--disabled {
|
.el-button.el-button--disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="name"></div>
|
<div class="name"></div>
|
||||||
<div class="content-box top">
|
<div class="content-box top">
|
||||||
<div class="count">{{ userAnalyseInfo.onlineNumber }}</div>
|
<div class="count">{{ onlineNumber }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -199,6 +199,7 @@ import RecommendFriend from "./components/RecommendFriend/index.vue";
|
|||||||
import ThemeData from "./components/RecommendTheme/index.vue";
|
import ThemeData from "./components/RecommendTheme/index.vue";
|
||||||
import Skeleton from "@/components/Skeleton/index.vue";
|
import Skeleton from "@/components/Skeleton/index.vue";
|
||||||
import useUserStore from "@/stores/user";
|
import useUserStore from "@/stores/user";
|
||||||
|
import useSocketStore from "@/stores/socket";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import signalR from "@/utils/signalR";
|
import signalR from "@/utils/signalR";
|
||||||
|
|
||||||
@@ -218,6 +219,7 @@ const isThemeFinished = ref([]);
|
|||||||
const allDiscussList = ref([]);
|
const allDiscussList = ref([]);
|
||||||
const isAllDiscussFinished = ref(false);
|
const isAllDiscussFinished = ref(false);
|
||||||
const userAnalyseInfo = ref({});
|
const userAnalyseInfo = ref({});
|
||||||
|
const onlineNumber = ref(0);
|
||||||
|
|
||||||
const items = [{ user: "用户1" }, { user: "用户2" }, { user: "用户3" }];
|
const items = [{ user: "用户1" }, { user: "用户2" }, { user: "用户3" }];
|
||||||
//主题查询参数
|
//主题查询参数
|
||||||
@@ -258,21 +260,19 @@ onMounted(async () => {
|
|||||||
isAllDiscussFinished.value = allDiscussConfig.isFinish;
|
isAllDiscussFinished.value = allDiscussConfig.isFinish;
|
||||||
allDiscussList.value = allDiscussData.items;
|
allDiscussList.value = allDiscussData.items;
|
||||||
const { data: userAnalyseInfoData } = await getUserAnalyse();
|
const { data: userAnalyseInfoData } = await getUserAnalyse();
|
||||||
|
onlineNumber.value = userAnalyseInfoData.onlineNumber;
|
||||||
userAnalyseInfo.value = userAnalyseInfoData;
|
userAnalyseInfo.value = userAnalyseInfoData;
|
||||||
// 实时人数
|
// 实时人数
|
||||||
// await signalR.init(`main`);
|
await signalR.init(`main`);
|
||||||
// nextTick(() => {
|
|
||||||
// // 初始化主题样式
|
|
||||||
// handleThemeStyle(useSettingsStore().theme);
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//这里还需要监视token的变化,重新进行signalr连接
|
//这里还需要监视token的变化,重新进行signalr连接
|
||||||
watch();
|
watch(
|
||||||
// () => token.value,
|
() => token.value,
|
||||||
// async (newValue, oldValue) => {
|
async (newValue, oldValue) => {
|
||||||
// await signalR.init(`main`);
|
await signalR.init(`main`);
|
||||||
// }
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const weekXAxis = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
const weekXAxis = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
||||||
// 访问统计
|
// 访问统计
|
||||||
@@ -290,6 +290,15 @@ const statisOptions = computed(() => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 推送的实时人数获取
|
||||||
|
const currentOnlineNum = computed(() => useSocketStore().getOnlineNum());
|
||||||
|
watch(
|
||||||
|
() => currentOnlineNum.value,
|
||||||
|
(val) => {
|
||||||
|
onlineNumber.value = val;
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.home-box {
|
.home-box {
|
||||||
@@ -375,7 +384,6 @@ const statisOptions = computed(() => {
|
|||||||
.text {
|
.text {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 0 5px;
|
|
||||||
top: -10px;
|
top: -10px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
|||||||
// 官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/signalr/javascript-client?view=aspnetcore-6.0&viewFallbackFrom=aspnetcore-2.2&tabs=visual-studio
|
// 官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/signalr/javascript-client?view=aspnetcore-6.0&viewFallbackFrom=aspnetcore-2.2&tabs=visual-studio
|
||||||
import * as signalR from '@microsoft/signalr'
|
import * as signalR from "@microsoft/signalr";
|
||||||
import useSocketStore from '@/store/modules/socket'
|
import useSocketStore from "@/store/modules/socket";
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from "@/utils/auth";
|
||||||
import useUserStore from '@/store/modules/user'
|
import useUserStore from "@/store/modules/user";
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// signalR对象
|
// signalR对象
|
||||||
@@ -12,37 +12,36 @@ export default {
|
|||||||
failNum: 4,
|
failNum: 4,
|
||||||
async init(url) {
|
async init(url) {
|
||||||
const connection = new signalR.HubConnectionBuilder()
|
const connection = new signalR.HubConnectionBuilder()
|
||||||
.withUrl(`${import.meta.env.VITE_APP_BASE_WS}/` + url,
|
.withUrl(`${import.meta.env.VITE_APP_BASE_WS}/` + url, {
|
||||||
{
|
headers: {
|
||||||
headers: {
|
Authorization: `Bearer ${getToken()}`,
|
||||||
'Authorization': `Bearer ${getToken()}`
|
},
|
||||||
},
|
accessTokenFactory: () => {
|
||||||
accessTokenFactory: () => {
|
// 返回授权 token
|
||||||
// 返回授权 token
|
return `${getToken()}`;
|
||||||
return `${getToken()}`;
|
},
|
||||||
}
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
.withAutomaticReconnect()//自动重新连接
|
.withAutomaticReconnect() //自动重新连接
|
||||||
.configureLogging(signalR.LogLevel.Information)
|
.configureLogging(signalR.LogLevel.Information)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
console.log(connection, "connection")
|
console.log(connection, "connection");
|
||||||
|
|
||||||
|
|
||||||
this.SR = connection;
|
this.SR = connection;
|
||||||
// 断线重连
|
// 断线重连
|
||||||
connection.onclose(async () => {
|
connection.onclose(async () => {
|
||||||
console.log('断开连接了')
|
console.log("断开连接了");
|
||||||
console.assert(connection.state === signalR.HubConnectionState.Disconnected);
|
console.assert(
|
||||||
|
connection.state === signalR.HubConnectionState.Disconnected
|
||||||
|
);
|
||||||
// 建议用户重新刷新浏览器
|
// 建议用户重新刷新浏览器
|
||||||
await this.start();
|
await this.start();
|
||||||
})
|
});
|
||||||
|
|
||||||
connection.onreconnected(() => {
|
connection.onreconnected(() => {
|
||||||
console.log('断线重新连接成功')
|
console.log("断线重新连接成功");
|
||||||
})
|
});
|
||||||
this.receiveMsg(connection);
|
this.receiveMsg(connection);
|
||||||
// 启动
|
// 启动
|
||||||
await this.start();
|
await this.start();
|
||||||
@@ -55,14 +54,9 @@ export default {
|
|||||||
try {
|
try {
|
||||||
var that = this;
|
var that = this;
|
||||||
await this.SR.stop();
|
await this.SR.stop();
|
||||||
}
|
} catch {}
|
||||||
catch
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
var that = this;
|
var that = this;
|
||||||
|
|
||||||
@@ -78,7 +72,7 @@ export default {
|
|||||||
//console.log(`失败重试剩余次数${that.failNum}`, error)
|
//console.log(`失败重试剩余次数${that.failNum}`, error)
|
||||||
if (that.failNum > 0) {
|
if (that.failNum > 0) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await this.SR.start()
|
await this.SR.start();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -88,13 +82,15 @@ export default {
|
|||||||
receiveMsg(connection) {
|
receiveMsg(connection) {
|
||||||
connection.on("onlineNum", (data) => {
|
connection.on("onlineNum", (data) => {
|
||||||
const socketStore = useSocketStore();
|
const socketStore = useSocketStore();
|
||||||
socketStore.setOnlineNum(data)
|
socketStore.setOnlineNum(data);
|
||||||
});
|
});
|
||||||
connection.on("forceOut", (msg) => {
|
connection.on("forceOut", (msg) => {
|
||||||
useUserStore().logOut().then(() => {
|
useUserStore()
|
||||||
alert(msg);
|
.logOut()
|
||||||
location.href = '/index';
|
.then(() => {
|
||||||
})
|
alert(msg);
|
||||||
|
location.href = "/index";
|
||||||
|
});
|
||||||
});
|
});
|
||||||
// connection.on("onlineNum", (data) => {
|
// connection.on("onlineNum", (data) => {
|
||||||
// store.dispatch("socket/changeOnlineNum", data);
|
// store.dispatch("socket/changeOnlineNum", data);
|
||||||
@@ -120,5 +116,5 @@ export default {
|
|||||||
// store.dispatch("socket/getNoticeList", data.data);
|
// store.dispatch("socket/getNoticeList", data.data);
|
||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user