diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCrudAppService.cs b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCrudAppService.cs index 31700d81..ba23f367 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCrudAppService.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCrudAppService.cs @@ -62,6 +62,11 @@ namespace Yi.Framework.Ddd.Application { } + public override Task> GetListAsync(TGetListInput input) + { + throw new NotImplementedException($"【{typeof(TEntity)}】实体的CrudAppService,查询为具体业务,通用查询几乎无实际场景,请重写实现!"); + } + /// /// 偷梁换柱 /// @@ -72,7 +77,7 @@ namespace Yi.Framework.Ddd.Application { await Repository.DeleteManyAsync(id); } - [RemoteService(isEnabled:false)] + [RemoteService(isEnabled: false)] public override Task DeleteAsync(TKey id) { return base.DeleteAsync(id); diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetListInputVo.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetListInputVo.cs index dec2f195..b10278a1 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetListInputVo.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetListInputVo.cs @@ -1,8 +1,9 @@ using Volo.Abp.Application.Dtos; +using Yi.Framework.Ddd.Application.Contracts; namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article { - public class ArticleGetListInputVo : PagedAndSortedResultRequestDto + public class ArticleGetListInputVo : PagedAllResultRequestDto { public string? Content { get; set; } public string? Name { get; set; } diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetListOutputDto.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetListOutputDto.cs index 735dfac5..b63cd27e 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetListOutputDto.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetListOutputDto.cs @@ -10,5 +10,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article public Guid DiscussId { get; set; } public List? Children { get; set; } + + public DateTime CreationTime { get; set; } } } diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetOutputDto.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetOutputDto.cs index 85825322..0d974c80 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetOutputDto.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Article/ArticleGetOutputDto.cs @@ -8,5 +8,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Article public string Name { get; set; } public Guid DiscussId { get; set; } public Guid ParentId { get; set; } + + public DateTime CreationTime { get; set; } } } diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateCreateInputVo.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateCreateInputVo.cs index 0280902b..143bb793 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateCreateInputVo.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateCreateInputVo.cs @@ -8,5 +8,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Plate public string Name { get; set; } public string? Logo { get; set; } public string? Introduction { get; set; } + + public string Code { get; set; } } } diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetListInputVo.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetListInputVo.cs index b7a6ec3d..b424154c 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetListInputVo.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetListInputVo.cs @@ -1,11 +1,11 @@ using Volo.Abp.Application.Dtos; +using Yi.Framework.Ddd.Application.Contracts; namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Plate { - public class PlateGetListInputVo : PagedAndSortedResultRequestDto + public class PlateGetListInputVo : PagedAllResultRequestDto { public string? Name { get; set; } - public string? Logo { get; set; } - public string? Introduction { get; set; } + public string? Code { get; set; } } } diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetListOutputDto.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetListOutputDto.cs index 1f2970c7..f3621635 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetListOutputDto.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetListOutputDto.cs @@ -8,5 +8,9 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Plate public string Name { get; set; } public string? Logo { get; set; } public string? Introduction { get; set; } + + public string Code { get; set; } + + public DateTime CreationTime { get; set; } } } diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetOutputDto.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetOutputDto.cs index 24ba4208..ed0b9ad3 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetOutputDto.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateGetOutputDto.cs @@ -7,5 +7,8 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Plate public string Name { get; set; } public string? Logo { get; set; } public string? Introduction { get; set; } + public string Code { get; set; } + + public DateTime CreationTime { get; set; } } } diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateUpdateInputVo.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateUpdateInputVo.cs index 16a55e14..d5dfb272 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateUpdateInputVo.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application.Contracts/Dtos/Plate/PlateUpdateInputVo.cs @@ -5,5 +5,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Plate public string? Name { get; set; } public string? Logo { get; set; } public string? Introduction { get; set; } + + public string? Code { get; set; } } } diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application/Services/ArticleService.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application/Services/ArticleService.cs index 12fa9001..83400b0d 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application/Services/ArticleService.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application/Services/ArticleService.cs @@ -3,8 +3,12 @@ using Mapster; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; +using SqlSugar; using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Domain.Repositories; using Yi.Framework.Bbs.Application.Contracts.Dtos.Article; +using Yi.Framework.Bbs.Application.Contracts.Dtos.Plate; using Yi.Framework.Bbs.Application.Contracts.IServices; using Yi.Framework.Bbs.Domain.Entities; using Yi.Framework.Bbs.Domain.Repositories; @@ -37,6 +41,19 @@ namespace Yi.Framework.Bbs.Application.Services private IArticleRepository _articleRepository { get; set; } private ISqlSugarRepository _discussRepository { get; set; } private IDiscussService _discussService { get; set; } + + public override async Task> GetListAsync(ArticleGetListInputVo input) + { + RefAsync total = 0; + + var entities = await _articleRepository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.Name), x => x.Name.Contains(input.Name!)) + //.WhereIF(!string.IsNullOrEmpty(input.Code), x => x.Name.Contains(input.Code!)) + .WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime) + .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); + return new PagedResultDto(total, await MapToGetListOutputDtosAsync(entities)); + } + + /// /// 获取文章全部平铺信息 /// diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application/Services/PlateService.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application/Services/PlateService.cs index b438a99a..6ef7b09b 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application/Services/PlateService.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Application/Services/PlateService.cs @@ -1,8 +1,12 @@ +using SqlSugar; +using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Repositories; using Yi.Framework.Bbs.Application.Contracts.Dtos.Plate; using Yi.Framework.Bbs.Application.Contracts.IServices; using Yi.Framework.Bbs.Domain.Entities; using Yi.Framework.Ddd.Application; +using Yi.Framework.Rbac.Application.Contracts.Dtos.Config; +using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.Bbs.Application.Services { @@ -12,8 +16,21 @@ namespace Yi.Framework.Bbs.Application.Services public class PlateService : YiCrudAppService, IPlateService { - public PlateService(IRepository repository) : base(repository) + private ISqlSugarRepository _repository; + public PlateService(ISqlSugarRepository repository) : base(repository) { + _repository= repository; + } + + public override async Task> GetListAsync(PlateGetListInputVo input) + { + RefAsync total = 0; + + var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.Name), x => x.Name.Contains(input.Name!)) + .WhereIF(!string.IsNullOrEmpty(input.Code), x => x.Name.Contains(input.Code!)) + .WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime) + .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); + return new PagedResultDto(total, await MapToGetListOutputDtosAsync(entities)); } } } diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Domain/Entities/ArticleEntity.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Domain/Entities/ArticleEntity.cs index 58eef9c5..21652fc6 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Domain/Entities/ArticleEntity.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Domain/Entities/ArticleEntity.cs @@ -1,11 +1,12 @@ using SqlSugar; using Volo.Abp; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; namespace Yi.Framework.Bbs.Domain.Entities { [SugarTable("Article")] - public class ArticleEntity : Entity, ISoftDelete + public class ArticleEntity : Entity, ISoftDelete,IAuditedObject { [SugarColumn(ColumnName = "Id", IsPrimaryKey = true)] public override Guid Id { get; protected set; } @@ -23,6 +24,15 @@ namespace Yi.Framework.Bbs.Domain.Entities [SugarColumn(IsIgnore = true)] public List? Children { get; set; } + + + public DateTime CreationTime { get; set; } + + public Guid? CreatorId { get; set; } + + public Guid? LastModifierId { get; set; } + + public DateTime? LastModificationTime { get; set; } } public static class ArticleEntityExtensions diff --git a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Domain/Entities/PlateEntity.cs b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Domain/Entities/PlateEntity.cs index 693690ff..3d2f171e 100644 --- a/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Domain/Entities/PlateEntity.cs +++ b/Yi.Abp.Net8/module/bbs/Yi.Framework.Bbs.Domain/Entities/PlateEntity.cs @@ -1,18 +1,31 @@ using SqlSugar; using Volo.Abp.Domain.Entities; using Volo.Abp; +using Volo.Abp.Auditing; namespace Yi.Framework.Bbs.Domain.Entities { [SugarTable("Plate")] - public class PlateEntity : Entity, ISoftDelete + public class PlateEntity : Entity, ISoftDelete,IAuditedObject { [SugarColumn(ColumnName = "Id", IsPrimaryKey = true)] public override Guid Id { get; protected set; } + + public string Code { get; set; } public string Name { get; set; } public string? Logo { get; set; } public string? Introduction { get; set; } public bool IsDeleted { get; set; } + + + + public DateTime CreationTime { get; set; } + + public Guid? CreatorId { get; set; } + + public Guid? LastModifierId { get; set; } + + public DateTime? LastModificationTime { get; set; } } } diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs index fa8a7da3..faa20eee 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs @@ -55,7 +55,7 @@ namespace Yi.Abp.Web //动态Api Configure(options => { - options.ConventionalControllers.Create(typeof(YiAbpApplicationModule).Assembly,options=>options.RemoteServiceName="default"); + options.ConventionalControllers.Create(typeof(YiAbpApplicationModule).Assembly, options => options.RemoteServiceName = "default"); options.ConventionalControllers.Create(typeof(YiFrameworkRbacApplicationModule).Assembly, options => options.RemoteServiceName = "rbac"); options.ConventionalControllers.Create(typeof(YiFrameworkBbsApplicationModule).Assembly, options => options.RemoteServiceName = "bbs"); }); @@ -63,7 +63,6 @@ namespace Yi.Abp.Web //设置api格式 service.AddControllers().AddNewtonsoftJson(options => { - // options.SerializerSettings.Converters.Add(new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss")); options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; }); Configure(options => @@ -76,7 +75,10 @@ namespace Yi.Abp.Web }); //Swagger - context.Services.AddYiSwaggerGen(); + context.Services.AddYiSwaggerGen(options => + { + options.SwaggerDoc("default", new OpenApiInfo { Title = "Yi.Framework.Abp", Version = "v1",Description="集大成者" }); + }); //跨域 context.Services.AddCors(options => @@ -145,10 +147,10 @@ namespace Yi.Abp.Web app.UseRouting(); - + //跨域 app.UseCors(DefaultCorsPolicyName); - + //鉴权 app.UseAuthentication(); diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db b/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db index a244c812..df0255e7 100644 Binary files a/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db and b/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db differ diff --git a/Yi.BBS.Vue3/.env.development b/Yi.BBS.Vue3/.env.development index 8229bdf9..9ea2ef99 100644 --- a/Yi.BBS.Vue3/.env.development +++ b/Yi.BBS.Vue3/.env.development @@ -1,2 +1,4 @@ +# 接口前缀 VITE_APP_BASEAPI="/api-dev" -VITE_APP_URL="http://localhost:19001/api/app" \ No newline at end of file +VITE_APP_URL="http://localhost:19001/api/app" +VITE_APP_ENV_NAME = "dev" \ No newline at end of file diff --git a/Yi.BBS.Vue3/.env.production b/Yi.BBS.Vue3/.env.production index a76814fb..d14259d7 100644 --- a/Yi.BBS.Vue3/.env.production +++ b/Yi.BBS.Vue3/.env.production @@ -1 +1,4 @@ -VITE_APP_BASEAPI="/prod-api" \ No newline at end of file +# 接口前缀 +VITE_APP_BASEAPI="/prod-api" + +VITE_APP_ENV_NAME = "production" \ No newline at end of file diff --git a/Yi.BBS.Vue3/src/apis/auth.js b/Yi.BBS.Vue3/src/apis/auth.js new file mode 100644 index 00000000..37e2524b --- /dev/null +++ b/Yi.BBS.Vue3/src/apis/auth.js @@ -0,0 +1,65 @@ +import request from "@/config/axios/service"; + +/** + * 用户登录 + * @param {*} data 账号密码 + */ +export function userLogin(data) { + return request({ + url: `/account/login`, + method: "post", + data, + }); +} + +/** + * 用户注册 + * @param {*} data 账号密码 + */ +export function userRegister(data) { + return request({ + url: `/account/register`, + method: "post", + data, + }); +} + +/** + * 获取用户详细信息 + */ +export function getUserDetailInfo() { + return request({ + url: `/account`, + method: "get", + }); +} + +/** + * 用户退出 + */ +export function userLogout() { + return request({ + url: `/account/logout`, + method: "post", + }); +} + +/** + * 获取验证码 + */ +export function getCodeImg() { + return request({ + url: `/account/captcha-image`, + method: "get", + }); +} +/** + * 获取短信验证码 + */ +export function getCodePhone(data) { + return request({ + url: `/account/captcha-phone`, + method: "post", + data, + }); +} diff --git a/Yi.BBS.Vue3/src/apis/configApi.js b/Yi.BBS.Vue3/src/apis/configApi.js index 79e7edf9..0b117e63 100644 --- a/Yi.BBS.Vue3/src/apis/configApi.js +++ b/Yi.BBS.Vue3/src/apis/configApi.js @@ -1,9 +1,9 @@ -import myaxios from '@/utils/request' +import request from "@/config/axios/service"; //获取配置 -export function getAll(){ - return myaxios({ - url: '/config', - method: 'get' - }) -}; \ No newline at end of file +export function getAll() { + return request({ + url: "/config", + method: "get", + }); +} diff --git a/Yi.BBS.Vue3/src/config/axios/ErrorCode.js b/Yi.BBS.Vue3/src/config/axios/ErrorCode.js new file mode 100644 index 00000000..f552fd6f --- /dev/null +++ b/Yi.BBS.Vue3/src/config/axios/ErrorCode.js @@ -0,0 +1,13 @@ +export default { + "000": "操作太频繁,请勿重复请求", + 401: "当前操作没有权限,请退出重试", + 403: "当前操作没有权限,请退出重试", + 404: "资源不存在", + 417: "未绑定登录账号,请使用密码登录后绑定", + 423: "演示环境不能操作,如需了解联系", + 426: "用户名不存在或密码错误", + 428: "验证码错误,请重新输入", + 429: "请求过频繁", + 479: "演示环境,没有权限操作", + default: "系统未知错误,请反馈给管理员", +}; diff --git a/Yi.BBS.Vue3/src/config/axios/config.js b/Yi.BBS.Vue3/src/config/axios/config.js new file mode 100644 index 00000000..bd677d55 --- /dev/null +++ b/Yi.BBS.Vue3/src/config/axios/config.js @@ -0,0 +1,34 @@ +const config = { + /** + * api请求基础路径 + */ + base_url: { + // 开发环境接口前缀 + dev: import.meta.env.VITE_APP_BASEAPI, + // 打包生产环境接口前缀 + pro: window.location.protocol + "//" + window.location.hostname + ":19001", + }, + + /** + * 接口请求前缀 + */ + pre_interface: import.meta.env.VITE_APP_BASEAPI, + + /** + * 接口成功返回状态码 + */ + result_code: "0000", + + /** + * 接口请求超时时间 + */ + request_timeout: 60000, + + /** + * 默认接口请求类型 + * 可选值:application/x-www-form-urlencoded multipart/form-data + */ + default_headers: "application/json", +}; + +export { config }; diff --git a/Yi.BBS.Vue3/src/config/axios/service.js b/Yi.BBS.Vue3/src/config/axios/service.js new file mode 100644 index 00000000..daed13fa --- /dev/null +++ b/Yi.BBS.Vue3/src/config/axios/service.js @@ -0,0 +1,89 @@ +import axios from "axios"; +import { ElMessage } from "element-plus"; +import { config } from "@/config/axios/config"; +import { Session } from "@/utils/storage"; +import useAuths from "@/hooks/useAuths"; + +const { getToken } = useAuths(); + +const { request_timeout } = config; +export const PATH_URL = import.meta.env.VITE_APP_BASEAPI; + +// 配置新建一个 axios 实例 +const service = axios.create({ + baseURL: PATH_URL, // api 的 base_url + timeout: request_timeout, // 请求超时时间 + headers: { "Content-Type": "application/json" }, + hideerror: false, //是否在底层显示错误信息 +}); + +// 添加请求拦截器 +service.interceptors.request.use( + (config) => { + // 在发送请求之前做些什么 token + const token = getToken(); + if (token) { + config.headers["Authorization"] = `Bearer ${token}`; + } + if (Session.get("tenantId")) { + config.headers["TenantId"] = Session.get("tenantId"); + } + return config; + }, + (error) => { + // 对请求错误做些什么 + return Promise.reject(error); + } +); + +// 添加响应拦截器 +service.interceptors.response.use( + (response) => { + return Promise.resolve(response); + }, + (error) => { + // 对响应错误做点什么 + if (error.message.indexOf("timeout") != -1) { + ElMessage({ + type: "danger", + message: "网络超时", + }); + } else if (error.message == "Network Error") { + ElMessage({ + type: "danger", + message: "网络连接错误", + }); + } else { + const res = error.response || {}; + const status = Number(res.status) || 200; + const message = res.data.error.message; + if (status === 401) { + ElMessage({ + type: "danger", + message, + }); + return; + } + if (status !== 200) { + if (status >= 500) { + ElMessage({ + type: "danger", + message: "网络开小差了,请稍后再试", + }); + return Promise.reject(new Error(message)); + } + // 避开找不到后端接口的提醒 + if (status !== 404) { + ElMessage({ + type: "danger", + message, + }); + } + } + } + return Promise.reject(new Error(error)); + } +); + +// 导出 axios 实例 +export default service; diff --git a/Yi.BBS.Vue3/src/hooks/useAuths.js b/Yi.BBS.Vue3/src/hooks/useAuths.js new file mode 100644 index 00000000..38552666 --- /dev/null +++ b/Yi.BBS.Vue3/src/hooks/useAuths.js @@ -0,0 +1,138 @@ +import { ElMessage, ElMessageBox } from "element-plus"; +import useUserStore from "@/stores/user"; +import router from "@/router"; +import { Session, Local } from "@/utils/storage"; +import { userLogin, getUserDetailInfo, userLogout } from "@/apis/auth"; + +const TokenKey = "AccessToken"; +export const AUTH_MENUS = "AUTH_MENUS"; +export const AUTH_USER = "AUTH_USER"; + +export default function useAuths(opt) { + const defaultOpt = { + loginUrl: "/login", // 登录页跳转url 默认: /login + loginReUrl: "", // 登录页登陆成功后带重定向redirect=的跳转url 默认为空 + homeUrl: "/index", // 主页跳转url 默认: /index + otherQuery: {}, // 成功登录后携带的(除redirect外)其他参数 + }; + + let option = { + ...defaultOpt, + ...opt, + }; + + // 获取token + const getToken = () => { + return Session.get(TokenKey); + }; + + // 存储token到cookies + const setToken = (token) => { + if (token == null) { + return false; + } + Session.set(TokenKey, token); + return true; + }; + + // 删除token + const removeToken = () => { + Session.remove(TokenKey); + return true; + }; + + // 退出登录 + const logoutFun = async () => { + let flag = true; + try { + await userLogout().then((res) => { + ElMessage({ + message: "退出成功", + type: "info", + duration: 2000, + }); + }); + } catch (error) { + flag = await ElMessageBox.confirm( + `退出登录失败,是否强制退出?`, + "提示", + { + confirmButtonText: "确 定", + cancelButtonText: "取 消", + type: "warning", + } + ) + .then(() => { + return true; + }) + .catch(() => { + //取消 + return false; + }); + } + if (flag) { + clearStorage(); + } + }; + + // 清空本地存储的信息 + const clearStorage = () => { + Session.clear(); + Local.clear(); + removeToken(); + window.location.reload(); + Session.set("vuex", null); + }; + + // 用户名密码登录 + const loginFun = async (params) => { + const res = await userLogin(params); + ElMessage({ + message: `您好${params.userName},登录成功!`, + type: "success", + }); + loginSuccess(res); + return res; + }; + + // 获取用户基本信息、角色、菜单权限 + const getUserInfo = async () => { + try { + let { data } = await getUserDetailInfo(); + // useUserStore + // store.dispatch("updateUserInfo", result); + return data; + } catch (error) { + return {}; + } + }; + + // 登录成功之后的操作 + const loginSuccess = async (res) => { + const { token } = res.data; + + setToken(token); + try { + // 存储用户信息 + await getUserInfo(); // 用户信息 + // 登录成功后 路由跳转 + router.replace({ + path: option.loginReUrl ? option.loginReUrl : option.homeUrl, + query: option.otherQuery, + }); + } catch (error) { + removeToken(); + return false; + } + }; + + return { + getToken, + setToken, + removeToken, + loginFun, + getUserInfo, + logoutFun, + clearStorage, + }; +} diff --git a/Yi.BBS.Vue3/src/permission.js b/Yi.BBS.Vue3/src/permission.js index aa0af493..01e51f38 100644 --- a/Yi.BBS.Vue3/src/permission.js +++ b/Yi.BBS.Vue3/src/permission.js @@ -1,65 +1,52 @@ -import router from './router' -import { ElMessage } from 'element-plus' -import NProgress from 'nprogress' -import 'nprogress/nprogress.css' -import { getToken } from '@/utils/auth' -import { isRelogin } from '@/utils/request' -import useUserStore from '@/stores/user' - +import router from "./router"; +import useAuths from "@/hooks/useAuths"; +import { ElMessage } from "element-plus"; +import NProgress from "nprogress"; +import "nprogress/nprogress.css"; +import useUserStore from "@/stores/user"; NProgress.configure({ showSpinner: false }); - -const whiteList = ['/login', '/auth-redirect', '/bind', '/register']; +const { getToken, logoutFun } = useAuths(); +const whiteList = ["/login", "/auth-redirect", "/bind", "/register"]; router.beforeEach((to, from, next) => { - NProgress.start() - if (getToken()) { - // to.meta.title && useSettingsStore().setTitle(to.meta.title) - /* has token*/ - if (to.path === '/login') { - next({ path: '/' }) - NProgress.done() + NProgress.start(); + const hasToken = getToken(); + if (hasToken) { + if (to.path === "/login") { + // 已经登陆跳转到首页 + next({ path: "/" }); + NProgress.done(); } else { - - if (useUserStore().roles.length === 0) - { - isRelogin.show = true + if (useUserStore().roles.length === 0) { // 判断当前用户是否已拉取完user_info信息 - useUserStore().getInfo().then(() => { - isRelogin.show = false - //这里不需要动态路由 - // usePermissionStore().generateRoutes().then(accessRoutes => { - // // 根据roles权限生成可访问的路由表 - // accessRoutes.forEach(route => { - // if (!isHttp(route.path)) { - // router.addRoute(route) // 动态添加可访问路由表 - // } - // }) - // next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 - // }) - next({ ...to, replace: true }) - }).catch(err => { - useUserStore().logOut().then(() => { - ElMessage.error(err) - next({ path: '/' }) + useUserStore() + .getInfo() + .then(() => { + next({ ...to, replace: true }); }) - }) + .catch((err) => { + logoutFun.then(() => { + ElMessage.error(err); + next({ path: "/" }); + }); + }); } else { - next() + next(); } } } else { // 没有token if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 - next() + next(); } else { - next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 - NProgress.done() + next(`/login?redirect=${to.path}&unTourist=true`); // 否则全部重定向到登录页 + NProgress.done(); } } -}) +}); router.afterEach(() => { - NProgress.done() -}) + NProgress.done(); +}); diff --git a/Yi.BBS.Vue3/src/router/index.js b/Yi.BBS.Vue3/src/router/index.js index e5a40141..efd0a79d 100644 --- a/Yi.BBS.Vue3/src/router/index.js +++ b/Yi.BBS.Vue3/src/router/index.js @@ -1,76 +1,74 @@ -import { createRouter, createWebHistory } from 'vue-router' -import Layout from '../layout/Index.vue' -import NotFound from '../views/error/404.vue' -import LoginLayout from '../layout/LoginLayout.vue' +import { createRouter, createWebHistory } from "vue-router"; +import Layout from "../layout/Index.vue"; +import NotFound from "../views/error/404.vue"; +import LoginLayout from "../layout/LoginLayout.vue"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), scrollBehavior(to, from, savedPosition) { // 始终滚动到顶部 - return { top: 0 } + return { top: 0 }; }, routes: [ { - name:'test', - path: '/test', - component: () => import('../views/Test.vue') + name: "test", + path: "/test", + component: () => import("../views/Test.vue"), }, { - - path: '/loginLayout', - name: 'loginLayout', + path: "/loginLayout", + name: "loginLayout", component: LoginLayout, - redirect: '/login' , - children :[ + redirect: "/login", + children: [ { - name:'login', - path: '/login', - component: () => import('../views/Login.vue') + name: "login", + path: "/login", + component: () => import("../views/Login.vue"), }, { - name:'register', - path: '/register', - component: () => import('../views/Register.vue') + name: "register", + path: "/register", + component: () => import("../views/Register.vue"), }, - ] + ], }, { - path: '/', - name: 'layout', + path: "/", + name: "layout", component: Layout, - redirect: '/index' , - children :[ + redirect: "/index", + children: [ { - name:'index', - path: '/index', - component: () => import('../views/Index.vue') + name: "index", + path: "/index", + component: () => import("../views/Index.vue"), }, { - name:'article', - path: '/article/:discussId/:articleId?', - component: () => import('../views/Article.vue') + name: "article", + path: "/article/:discussId/:articleId?", + component: () => import("../views/Article.vue"), }, { - name:'discuss', - path: '/discuss/:plateId?', - component: () => import('../views/Discuss.vue') + name: "discuss", + path: "/discuss/:plateId?", + component: () => import("../views/Discuss.vue"), }, { //artType:discuss主题、article文章 //operType:create创建、update更新 - name:'editArt', - path:'/editArt', - component:()=>import('../views/EditArticle.vue') + name: "editArt", + path: "/editArt", + component: () => import("../views/EditArticle.vue"), }, { - name:'profile', - path:'/profile', - component:()=>import('../views/profile/Index.vue') - - } - ] + name: "profile", + path: "/profile", + component: () => import("../views/profile/Index.vue"), + }, + ], }, - { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }, - ] -}) + { path: "/:pathMatch(.*)*", name: "NotFound", component: NotFound }, + ], +}); -export default router +export default router; diff --git a/Yi.BBS.Vue3/src/stores/user.js b/Yi.BBS.Vue3/src/stores/user.js index ea55f132..744a944b 100644 --- a/Yi.BBS.Vue3/src/stores/user.js +++ b/Yi.BBS.Vue3/src/stores/user.js @@ -1,98 +1,109 @@ -import { login, logout, getInfo,register } from '@/apis/accountApi' -import { getToken, setToken, removeToken } from '@/utils/auth' -import { defineStore } from 'pinia' -const useUserStore = defineStore('user', -{ - state: () => ({ - id:'', - token: getToken(), - name: '游客', - userName:'', - icon: null, - roles: [], - permissions: [] - }), - getters: { - }, - actions: { - // 登录 - login(userInfo) { - const userName = userInfo.userName.trim() - const password = userInfo.password - const code = userInfo.code - const uuid = userInfo.uuid - return new Promise((resolve, reject) => { - login(userName, password, code, uuid).then(response => { - const res=response.data; +import { login, logout, getInfo, register } from "@/apis/accountApi"; +import { getUserDetailInfo } from "@/apis/auth"; +import useAuths from "@/hooks/useAuths"; +import { defineStore } from "pinia"; + +const { getToken, setToken, removeToken } = useAuths(); + +const useUserStore = defineStore("user", { + state: () => ({ + id: "", + token: getToken(), + name: "游客", + userName: "", + icon: null, + roles: [], + permissions: [], + }), + getters: {}, + actions: { + // 登录 + login(userInfo) { + const userName = userInfo.userName.trim(); + const password = userInfo.password; + const code = userInfo.code; + const uuid = userInfo.uuid; + return new Promise((resolve, reject) => { + login(userName, password, code, uuid) + .then((response) => { + const res = response.data; setToken(res.token); this.token = res.token; resolve(response); - }).catch(error => { - reject(error) }) - }) - }, - // 获取用户信息 - getInfo() { - return new Promise((resolve, reject) => { - getInfo().then(response => { - const res=response.data; - const user = res.user - const avatar = (user.icon == "" || user.icon == null) ? "/src/assets/logo.ico" : import.meta.env.VITE_APP_BASEAPI + "/file/"+user.icon; - - if (res.roleCodes && res.roleCodes.length > 0) { // 验证返回的roles是否是一个非空数组 - this.roles = res.roleCodes - this.permissions = res.permissionCodes + .catch((error) => { + reject(error); + }); + }); + }, + // 获取用户信息 + getInfo() { + return new Promise((resolve, reject) => { + getUserDetailInfo() + .then((response) => { + const res = response.data; + const user = res.user; + const avatar = + user.icon == "" || user.icon == null + ? "/src/assets/logo.ico" + : import.meta.env.VITE_APP_BASEAPI + "/file/" + user.icon; + + if (res.roleCodes && res.roleCodes.length > 0) { + // 验证返回的roles是否是一个非空数组 + this.roles = res.roleCodes; + this.permissions = res.permissionCodes; // this.roles = ["admin"]; // this.permissions=["*:*:*"] - } else { - this.roles = ['ROLE_DEFAULT'] + this.roles = ["ROLE_DEFAULT"]; } // this.roles = ["admin"]; // this.permissions=["*:*:*"] - this.name = user.nick + this.name = user.nick; this.icon = avatar; - this.userName=user.userName; - this.id=user.id; - resolve(res) - }).catch(error => { - reject(error) + this.userName = user.userName; + this.id = user.id; + resolve(res); }) - }) - }, - // 退出系统 - logOut() { - return new Promise((resolve, reject) => { - logout().then(() => { - this.token = '' - this.roles = [] - this.permissions = [] - removeToken() - resolve() - }).catch(error => { - reject(error) + .catch((error) => { + reject(error); + }); + }); + }, + // 退出系统 + logOut() { + return new Promise((resolve, reject) => { + logout() + .then(() => { + this.token = ""; + this.roles = []; + this.permissions = []; + removeToken(); + resolve(); }) - }) - }, - // 注册 - register(userInfo) { - const userName = userInfo.userName.trim() - const password = userInfo.password.trim() - const phone = userInfo.phone; - const uuid = userInfo.uuid; - const code=userInfo.code; - return new Promise((resolve, reject) => { - register(userName,password,phone,code,uuid).then(response => { - resolve(response); - }).catch(error => { - reject(error) - }) - }) - }, - - }, -}) + .catch((error) => { + reject(error); + }); + }); + }, + // 注册 + register(userInfo) { + const userName = userInfo.userName.trim(); + const password = userInfo.password.trim(); + const phone = userInfo.phone; + const uuid = userInfo.uuid; + const code = userInfo.code; + return new Promise((resolve, reject) => { + register(userName, password, phone, code, uuid) + .then((response) => { + resolve(response); + }) + .catch((error) => { + reject(error); + }); + }); + }, + }, +}); export default useUserStore; - \ No newline at end of file diff --git a/Yi.BBS.Vue3/src/utils/file.js b/Yi.BBS.Vue3/src/utils/file.js new file mode 100644 index 00000000..5d65a68a --- /dev/null +++ b/Yi.BBS.Vue3/src/utils/file.js @@ -0,0 +1,140 @@ +/** + * 根据后缀判断文件类型 + * @param {string} fileName 文件后缀名 + * @returns + */ +export function matchType(fileName) { + // 后缀获取 + let suffix = ""; + // 获取类型结果 + let result = ""; + try { + let flieArr = fileName.split("."); + suffix = flieArr[flieArr.length - 1]; + } catch (err) { + suffix = ""; + } + // fileName无后缀返回 false + if (!suffix) { + result = false; + return result; + } + // 图片格式 + let imglist = ["png", "jpg", "jpeg", "bmp", "gif"]; + // 进行图片匹配 + result = imglist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "image"; + return result; + } + // 匹配txt + let txtlist = ["txt"]; + result = txtlist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "txt"; + return result; + } + // 匹配 excel + let excelist = ["xls", "xlsx"]; + result = excelist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "excel"; + return result; + } + // 匹配 word + let wordlist = ["doc", "docx"]; + result = wordlist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "word"; + return result; + } + // 匹配 pdf + let pdflist = ["pdf"]; + result = pdflist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "pdf"; + return result; + } + // 匹配 ppt + let pptlist = ["ppt"]; + result = pptlist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "ppt"; + return result; + } + // 匹配 视频 + let videolist = ["mp4", "m2v", "mkv"]; + result = videolist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "video"; + return result; + } + // 匹配 音频 + let radiolist = ["mp3", "wav", "wmv"]; + result = radiolist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "radio"; + return result; + } + // 其他 文件类型 + result = "other"; + return result; +} + +/** + * url处理 + * @param {string} path url路径 + * @returns + */ +export function convertToUrl(path) { + // 替换反斜杠为正斜杠 + const normalizedPathWithSlashes = path.replace(/\\/g, "/"); + // 去掉开始的点号和反斜杠 + const removedDotsAndSlashes = normalizedPathWithSlashes.replace(/^\.\//, ""); + // 添加斜杠作为根路径 + const url = `/${removedDotsAndSlashes}`; + return url; +} + +/** + * 下载文件 + * + * @param {*} path 下载地址/下载请求地址。 + * @param {string} name 下载文件的名字(考虑到兼容性问题,最好加上后缀名 + */ +export const downLoadFile = (path, name) => { + const link = document.createElement("a"); + link.href = path; + link.download = name; + + if (isMobileDevice()) { + link.target = "_blank"; + link.rel = "noopener noreferrer"; + } + + link.style.display = "none"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +}; + +// 判断是否移动设备 +export function isMobileDevice() { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); +} diff --git a/Yi.BBS.Vue3/src/utils/storage.js b/Yi.BBS.Vue3/src/utils/storage.js new file mode 100644 index 00000000..fe57badc --- /dev/null +++ b/Yi.BBS.Vue3/src/utils/storage.js @@ -0,0 +1,53 @@ +/** + * window.localStorage 浏览器永久缓存 + * @method set 设置永久缓存 + * @method get 获取永久缓存 + * @method remove 移除永久缓存 + * @method clear 移除全部永久缓存 + */ +export const Local = { + // 设置永久缓存 + set(key, val) { + window.localStorage.setItem(key, JSON.stringify(val)); + }, + // 获取永久缓存 + get(key) { + let json = window.localStorage.getItem(key); + return JSON.parse(json); + }, + // 移除永久缓存 + remove(key) { + window.localStorage.removeItem(key); + }, + // 移除全部永久缓存 + clear() { + window.localStorage.clear(); + }, +}; + +/** + * window.sessionStorage 浏览器会话临时缓存 + * @method set 设置临时缓存 + * @method get 获取临时缓存 + * @method remove 移除临时缓存 + * @method clear 移除全部临时缓存 + */ +export const Session = { + // 设置临时缓存 + set(key, val) { + window.sessionStorage.setItem(key, JSON.stringify(val)); + }, + // 获取临时缓存 + get(key) { + let json = window.sessionStorage.getItem(key); + return JSON.parse(json); + }, + // 移除临时缓存 + remove(key) { + window.sessionStorage.removeItem(key); + }, + // 移除全部临时缓存 + clear() { + window.sessionStorage.clear(); + }, +}; diff --git a/Yi.BBS.Vue3/src/views/Login.vue b/Yi.BBS.Vue3/src/views/Login.vue index a39f813b..70bfe533 100644 --- a/Yi.BBS.Vue3/src/views/Login.vue +++ b/Yi.BBS.Vue3/src/views/Login.vue @@ -1,47 +1,59 @@ diff --git a/Yi.RuoYi.Vue3/src/api/bbs/articleApi.js b/Yi.RuoYi.Vue3/src/api/bbs/articleApi.js index 403e94c0..dd128760 100644 --- a/Yi.RuoYi.Vue3/src/api/bbs/articleApi.js +++ b/Yi.RuoYi.Vue3/src/api/bbs/articleApi.js @@ -3,7 +3,7 @@ // 分页查询 export function listData(query) { return request({ - url: '/article/pageList', + url: '/article', method: 'get', params: query }) @@ -12,7 +12,7 @@ export function listData(query) { // id查询 export function getData(code) { return request({ - url: '/article/getById/' + code, + url: '/article/' + code, method: 'get' }) } @@ -20,7 +20,7 @@ export function getData(code) { // 新增 export function addData(data) { return request({ - url: '/article/add', + url: '/article', method: 'post', data: data }) @@ -29,17 +29,17 @@ export function addData(data) { // 修改 export function updateData(data) { return request({ - url: '/article/update', + url: `/article/${data.id}`, method: 'put', data: data }) } // 删除 -export function delData(code) { +export function delData(ids) { return request({ - url: '/article/delList', + url: '/article', method: 'delete', - data:"string"==typeof(code)?[code]:code + params:{id:ids} }) } diff --git a/Yi.RuoYi.Vue3/src/api/bbs/plateApi.js b/Yi.RuoYi.Vue3/src/api/bbs/plateApi.js new file mode 100644 index 00000000..71b14236 --- /dev/null +++ b/Yi.RuoYi.Vue3/src/api/bbs/plateApi.js @@ -0,0 +1,47 @@ +import request from '@/utils/request' +/* 以下为api的模板,通用的crud,将以下变量替换即可: +plate : 实体模型 +*/ +// 分页查询 +export function listData(query) { + return request({ + url: '/plate', + method: 'get', + params: query + }) +} + +// id查询 +export function getData(id) { + return request({ + url: `/plate/${id}`, + method: 'get' + }) +} + +// 新增 +export function addData(data) { + return request({ + url: '/plate', + method: 'post', + data: data + }) +} + +// 修改 +export function updateData(id,data) { + return request({ + url: `/plate/${id}`, + method: 'put', + data: data + }) +} + +// 删除 +export function delData(ids) { + return request({ + url: `/plate`, + method: 'delete', + params:{id:ids} + }) +} diff --git a/Yi.RuoYi.Vue3/src/api/template.txt b/Yi.RuoYi.Vue3/src/api/template.txt index 128ed557..06c97338 100644 --- a/Yi.RuoYi.Vue3/src/api/template.txt +++ b/Yi.RuoYi.Vue3/src/api/template.txt @@ -40,7 +40,8 @@ export function updateData(id,data) { // 删除 export function delData(ids) { return request({ - url: `/@model@/${ids}`, + url: `/@model@`, method: 'delete', + params:{id:ids} }) } diff --git a/Yi.RuoYi.Vue3/src/views/bbs/plate/index.vue b/Yi.RuoYi.Vue3/src/views/bbs/plate/index.vue new file mode 100644 index 00000000..1d81c57c --- /dev/null +++ b/Yi.RuoYi.Vue3/src/views/bbs/plate/index.vue @@ -0,0 +1,282 @@ + + + + \ No newline at end of file diff --git a/Yi.RuoYi.Vue3/src/views/template.txt b/Yi.RuoYi.Vue3/src/views/template.txt index cb6cabde..3b6f0e9f 100644 --- a/Yi.RuoYi.Vue3/src/views/template.txt +++ b/Yi.RuoYi.Vue3/src/views/template.txt @@ -92,11 +92,11 @@ -->