Compare commits

..

1 Commits

Author SHA1 Message Date
陈淳
d919e07096 fix: 修复大小写问题 2023-02-20 19:57:13 +08:00
1514 changed files with 51918 additions and 50868 deletions

14
.gitignore vendored
View File

@@ -2,11 +2,17 @@
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
dist/
WebFirst/SoEasyPlatform.exe
WebFirst/excel
WebFirst/wwwroot
WebFirst/SoEasyPlatform.exe
WebFirst/appsettings.Development.json
WebFirst/appsettings.json
WebFirst/SoEasyPlatform.pdb
WebFirst/SoEasyPlatform.Views.pdb
WebFirst/web.config
WebFirst/WebFirst.exe
appsettings.Production.json
appsettings.Development.json
wwwroot
# User-specific files
*.rsuser
*.suo

358
README.md
View File

@@ -1,6 +1,7 @@
<h1 align="center"><img align="left" height="150px" src="https://user-images.githubusercontent.com/68722157/138828506-f58b7c57-5e10-4178-8f7d-5d5e12050113.png"> Yi框架</h1>
<h4 align="center">一套以用户体验出发的.Net6 Web开源框架</h4>
<h5 align="center">支持原生版本、Furion版本、Abp版本前端后台接入Ruoyi Vue3.0</h5>
<h1 align="center"><img align="left" height="100px" src="https://user-images.githubusercontent.com/68722157/138828506-f58b7c57-5e10-4178-8f7d-5d5e12050113.png"> Yi框架</h1>
<h4 align="center">一套与SqlSugar一样爽的.Net6开源框架</h4>
<h2 align="center">集大成者,终究轮子</h2>
[English](README-en.md) | 简体中文
@@ -13,189 +14,87 @@
模块分化较多,可根据业务自行引用或抛弃,集大成者,大而全乎,也许你能从中学习到一些独特见解
正在持续更进业务模块已接入ruoyi
**英文YiFramework**
Yi框架-一套与SqlSugar一样爽的.Net6开源框架。
Yi框架-一套与SqlSugar一样爽的.Net6低代码开源框架。
与Sqlsugar理念一致以用户体验出发。
架构干净整洁、无业务代码、采用微软风格原生框架封装、CodeFrist+配套自研文件模板代码生成器 开发。
适合.Net6学习、Sqlsugar学习 、项目二次开发。
集大成者,终究轮子
Yi框架最新版本标签`v3.0.0`,具体版本可以查看标签迭代
Yi框架最新版本标签`v1.2.1`,具体版本可以查看标签迭代
项目与Sqlsugar同步更新但这作者老杰哥代码天天爆肝到凌晨两点我们也尽量会跟上他的脚步。更新频繁所以可watching持续关注。
————这不仅仅是一个程序,更是一个艺术品,面向艺术的开发!
> 核心特点简单好用框架不以打包形式引用而是直接以项目附带源码给出自由度拉满遵循Mit协议允许随意修改请注明来源即可
**分支**
**分支:**
本项目由EFCore版本历经3年不断迭代至Sqlsugar版本现EFcore版本已弃用目前sqlsugar已带业务功能
本项目由EFCore版本历经4年不断迭代至Sqlsugar版本现EFcore版本已弃用目前sqlsugar已带业务功能
**SqlSugar**:.Net6 DDD领域驱动设计 简单分层微服务架构
- (推荐) **Furion**: 基于Furion分支回归开发本质极度简单用起来贼爽
- Yi.Framework.Net6.NetCore 6 意框架 (后端)
- ~~**Framework**~~: 框架分支,所有东西都在这里
- Yi.Vue3.X.RuoYiVue3 RuoYi前端框架 (前端后台)
- ~~**SqlSugar**:.Net6 DDD领域驱动设计 简单分层微服务架构~~
你没有听错已经接入java流行指数最高最火爆的框架之一与其他框架不同Yi框架后端为完全重制版并非为ruoyi java模仿版
- Yi.Vue3.x.VantVue3 移动端前端框架 (前端前台)持续迭代
- ~~**SqlSugar-Dev**为sqlsugar分支的实时开发版本~~
**SqlSugar-Dev**为sqlsugar分支的实时开发版本
- ~~**abp**:基于abp.vnext项目~~
~~**ec**: EFcore完整电商项目~~
****
**目录:**
Yi后端框架分为3个部分
- Infrastructure基础设施框架底层+sqlsugar+furion
- Module应用模块可选项例如缓存模块、微信模块、文件模块、日志模块等
- Application业务模块用于开发
另外光说不练假把式我们不仅仅提供一个空白的框架还同时提供3个基于yi框架的业务模块没有听错目前为1个后端支持3个前端。各个模块关系解耦可单独使用其中的任意业务模块
- Yi.RuoYi.Vue3Ruoyi后台管理系统Rbac Vue3前端推荐
- Yi.Furion.Net6.NET6后端推荐
- Yi.App.Vue3移动端App Vue3前端
- Yi.BBS.Vue3Web网页端BBS论坛 Vue3+Ts前端
后续我们持续更新各大应用模块及业务模块shop商场、erp进销存、mes工厂系统等
业务支持并扩展至各个领域,用于具体项目的二次开发极大复用后端代码及前端代码,以通用的部分+不通的部分快速二开
### 演示地址:
废话少说直接上地址,**请不要**更改里面的数据
官网网址:[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
API服务[yi.ccnetcore.com](http://yi.ccnetcore.com) 管理员账号cc 、 123456
Bbs社区系统[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
网关地址:~~[gate.ccnetcore.com/swagger](http://gate.ccnetcore.com/swagger)~~
Rbac后台管理系统[yi.ccnetcore.com](http://yi.ccnetcore.com) (已上线)~~管理员账号cc 、 123456~~
~~WebFirst开发所有代码生成器已经配置完成无需任何操作数据库及任何代码只需要网页表格上点点点即可~~
App移动端系统[xxx](xxx)正在部署
代码自动生成:
网关地址:~~[gate.ccnetcore.com/swagger](http://gate.ccnetcore.com/swagger)~~(目前使用单体架构部署,无需网关)
![代码生成](Readme/%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90.gif)
### 支持:
- [x] 完全支持单体应用架构
- [x] 完全支持分布式应用架构
- [x] 完全支持微服务架构
- [ ] 即将支持网格服务架构我们将在后续版本加入dapr
****
### 详细到爆炸的Yi框架教程导航
### 软件架构:
1. [框架快速开始](https://ccnetcore.com/article/1641733850189139969)(已完成)
2. [框架模块教程](https://ccnetcore.com/article/1641733991574933505)(已完成)
3. [应用模块教程](https://ccnetcore.com/article/1641734073091231745)
4. [Yi.RBAC后台系统](https://ccnetcore.com/article/1641734171128893441)
5. [Yi.BBS社区系统](https://ccnetcore.com/article/1641734308475572225)
**架构**:后端.NET6(Asp.NetCore 6)、WebFirst代码生成器~~与.NET5(Asp.NetCore 5)、前端Vue2.0~~
**关系型数据库**mysql、sql server、sqlite、oracle(正在兼容中)
**操作系统**Windows、Linux
**身份验证**JWT、IdentityServer4
**组件**SqlSugar、Autofac、Castle、Swagger、Log4Net、Redis、RabbitMq、ES、Quartz.net、~~T4~~
**分布式**CAP、Lock
**微服务**Consul、Ocelot、IdentityService、Apollo、Docker、Jenkins、Nginx、K8s、ELK、Polly
**封装**Json处理模块滑动验证码模块base64图片处理模块异常捕捉模块、邮件处理模块、linq封装模块、随机数模块、统一接口模块、基于策略的jwt验证、过滤器、数据库连接、跨域、初始化种子数据、Base32、Console输出、日期处理、文件传输、html筛选、http请求、ip过滤、md5加密、Rsa加密、序列化、雪花算法、字符串处理、编码处理、地址处理、xml处理、心跳检查。。。
****
### 它的理念:
优雅的进行快速开发通常简单程度与优雅程度不可兼得Yi框架并不一昧的追求极致的解耦会站在用户使用角度上在使用难易度进行考虑衡量
例如我们大部分功能紧密贴合Sqlsugar虽然缺少其他orm的替换性但在使用程度上降低的使用难度
> 一个面向用户的快速开发后端框架
在真正的使用这,你会明白这一点,极致的简单,也是优雅的一种体现。
****
### 特点:
- 面向用户的后端框架,使用简单,适合小型、企业级项目
- 项目内置源码,不打包
- 开箱即用
- 支持模块化
- 支持动态Api
- 支持属性注入
- 内置包含大量通用场景模块
- 等等
### 基础设施简介
- Jwt鉴权
- 接口级别授权
- 对象映射
- O/RM
- 数据过滤
- 多租户
- 逻辑删除
- 审计日志
- 种子数据
- 工作单元
- 模块化
- 动态Api
- 属性注入
- 自动依赖注入
- 当前用户
- 仓储
- Crud
### 内置模块简介
- 后台任务
- 本地缓存
- 分布式缓存
- 事件总线
- 字典管理
- 文件管理
- 图片操作
- Excel操作
- 操作日志管理
- Sms短信
- 微信支付
- WebFirst代码生成
### 业务项目
- RABC后台管理系统
- BBS社区系统
- APP移动端系统
> 重复的东西,无需再写一遍,这也是优雅的体现之一
****
### 核心技术
#### 后端
C# Asp.NetCore 6.0
- [x] 动态ApiCike.AutoApi
- [x] 鉴权授权Jwt
- [x] 日志Nlog
- [x] 模块化StartupModules
- [x] 依赖注入Autofac
- [x] 对象映射Mapster
- [x] ORM:SqlsugarCore
- [x] 多租户Abp
- [x] 后台任务Quartz.Net
- [x] 本地缓存MemortCache
- [x] 分布式缓存CSRedisCore
- [x] 事件总线Cike.EventBus
- [x] 图像操作SixLabors.ImageSharp
- [x] Excle操作ExcelToObject.Npoi
#### 前端
js Vue3.2
- [x] 异步请求axios
- [x] 图表echarts
- [x] uielement-plus
- [x] 存储pinia
- [x] 路由vue-router
- [x] 打包vite
#### 运维
- [x] 部署nginx
- [x] CICDgitlab+Jenkins
- [x] Dockerharbor
****
### 业务支持模块:
<h3>业务支持模块</h3>
RABC权限管理系统正在更新
采用ruoyi前端
大部分ruoyi功能采用ruoyi前端
- 用户管理
- 角色管理
- 菜单管理
@@ -206,39 +105,15 @@ RABC权限管理系统正在更新
- 用户在线
- 操作日志
- 登录日志
- 定时任务
- 缓存列表
- 服务监控
- WebFirst代码生成工具
- 等等
**演示截图:**
![输入图片说明](readme/1.png)
![输入图片说明](readme/2.png)
![输入图片说明](readme/3.png)
![输入图片说明](readme/4.png)
![输入图片说明](readme/5.png)
![输入图片说明](readme/6.png)
![输入图片说明](readme/7.png)
![输入图片说明](readme/8.png)
![输入图片说明](readme/9.png)
![输入图片说明](readme/10.png)
![输入图片说明](readme/1696760969217.jpg)
![输入图片说明](readme/1696761014270.jpg)
ERP进销存系统正在更新
- 供货商管理
- 等等
BBS论坛系统持续迭代
- 文章管理
- 评论管理
- 主题管理
- 板块管理
- 点赞管理
- 等等
APP移动端系统持续迭代
- 动态查询
- 我的资料
ERP进销存系统持续迭代
- 供货商管理
- 等等
SHOP电商系统持续迭代
@@ -249,6 +124,109 @@ SHOP电商系统持续迭代
- 等等
![输入图片说明](Readme/%E7%94%A8%E6%88%B7%E7%AE%A1%E7%90%86.png)
![输入图片说明](Readme/%E8%8F%9C%E5%8D%95%E7%AE%A1%E7%90%86.png)
### 框架支持模块:
大致如图:
![image](https://user-images.githubusercontent.com/68722157/142923071-2fa524eb-e833-4143-a926-51566e56e889.png)
![image](https://user-images.githubusercontent.com/68722157/142923150-ebe1b538-c3fc-42dd-bea8-83e10e0f819a.png)
![image](https://user-images.githubusercontent.com/68722157/142923529-e4fbb2f6-def1-4702-b9da-5adbd22f0a2f.png)
(删除线代表已实现功能还未迁移过来)
- [x] 支持大致`DDD领域驱动设计`进行分层,支持微服务扩展
- [x] 支持采用`异步`开发awit/async
- [x] 支持数据库主从`读写分离`
- [x] 支持功能替换,无需改动代码,只需配置`json文件`进行装配即可
- [x] 支持`Aop封装`FilterAop、IocAop、LogAop、SqlAop
- [x] 支持`Log4Net日志`记录自动生成至bin目录下的logs文件夹
- [x] 支持`DbSeed数据库种子数据`接入
- [x] 支持主流`数据库随意切换`Mysql/Sqlite/Sqlserver/Oracle
- [x] 支持上海杰哥官方`SqlSugar ORM`封装
- [x] 支持新版`SwaggerWebAPI`jwt身份认证接入
- [x] 支持`Cors`跨域
- [x] 支持`AutoFac`自动映射依赖注入
- [x] 支持`consul`服务器注册与发现
- [x] 支持`健康检查`
- [x] 支持`RabbitMQ`消息队列
- [x] 支持`Redis`多级缓存
- [x] 支持`MemoryCache`多级缓存
- [x] 支持`Ocelot`网关路由、服务聚合、服务发现、认证、鉴权、限流、熔断、缓存、Header头传递
- [x] 支持`Apollo`全局配置中心;
- [x] 支持`docker`镜像制作
- [x] 支持`Quartz.net`任务调度,实现任意接口被调度
- [x] 支持`ThumbnailSharp`缩略图封装
- [x] 支持`ELK`log4net+kafka+es+logstach+kibana
- [x] 支持`IdentityService4`授权中心
- [x] 支持`Es`分词查询
- [x] 支持多级`缓存`
- [x] 支持`CAP`分布式事务mysql+rabbitMq
- [x] 支持`Docker+k8s`部署
- [x] 支持`Jenkins+CI/CD`
- [x] 支持`AutoMapper`模块映射
- [x] 支持`微信支付`模块
- [x] 支持`单表多租户`常用功能
- [x] 支持`逻辑删除`常用功能
- [x] 支持`操作日志`常用功能
- [x] 支持`自动分表`
- [x] 支持`数据权限`
- [x] 支持`CodeFrist`快速构建数据库
- [x] 支持自研文件版`代码生成器`快速构建通用代码
- [x] 支持完整`Dto`模式
- [x] 支持 太多了忘了
****
### 目录结构:
![image](https://s1.ax1x.com/2022/04/09/LCTleH.png)
![image](https://s1.ax1x.com/2022/04/24/L4qlSs.png)
![image](https://s1.ax1x.com/2022/04/24/L4q1ln.png)
我们大致依照DDD领域驱动设计分层
分层如此清晰什么还感觉太复杂了用户只需关注Api、Service其他都是轮子啊
- BackGround后台进程通常使用消息队列进行消费任务
- Test测试单元测试
- Domain领域层Dto、服务接口层、模型层、仓储层、服务层
- Infrastructure基础实例层(通用工具层、核心层、定时任务Job、国际化、Web扩展层)
- Module模块层其他模块可按需进行引入例如微信支付、代码生成
- MicroServiceInstance服务层微服务
****
### 安装教程:
我们将在之后更新教程手册!
后端
1. 下载全部源码默认使用sqlite数据库已经生成
2. 直接点击sln文件运行即可没有任何其他依赖
前端
1. 下载全部源码npm i 安装依赖
2. 使用npm run dev进行运行
****
### 使用说明:
~~1. 导入使用仓库中的WebFirst数据库~~
~~2. 使用WebFirst添加实体、同步实体、修改模板生成路径并生成方案~~
没了,恭喜你已经成功完成了项目,并且已经具备大部分通用场景业务
是不是一个字?爽!
到此为止,你无需写任何一个代码!
**爽点**
新人,看这里,项目下载之后直接可以启动,无任何依赖,之后你可以查看`Test控制器`,迫不及待的快来爽一爽!
我们将使用说明转移至我们的官方论坛中,正在制作中,尽情期待!
****
### 感谢:
@@ -260,30 +238,44 @@ SHOP电商系统持续迭代
[朝夕教育]https://www.zhaoxiedu.net
[Sqlsugar老杰哥]https://www.donet5.com/Home/Doc
[Sqlsugar]https://www.donet5.com/Home/Doc
[RuYiAdmin如意老兄]https://gitee.com/pang-mingjun/RuYiAdmin
[RuYiAdmin]https://gitee.com/pang-mingjun/RuYiAdmin
[ZrAdminNetCore字母老哥]https://gitee.com/izory/ZrAdminNetCore
[Admin.NET周哥]https://gitee.com/zuohuaijun/Admin.NET
[Furion百小僧]https://furion.baiqian.ltd/
[ZrAdminNetCore]https://gitee.com/izory/ZrAdminNetCore
****
### 联系我们:
作者QQ`454313500`2029年之前作者24小时在线时刻保持活跃更新。
QQ交流群官方一群已满、官方二群已满、官方三群`786308927`基本已满)、官方四群:`498310311`(新群
QQ交流群官方一群已满、官方二群已满、官方三群`786308927`加作者QQ后同意
联系作者,这里人人都是顾问
官方网址留言区:[ccnetcore.com](https://ccnetcore.com)
官方网址:正在建设
****
### FQA:
前往官网查看留言区
问1为什么不采用EFcore
[留言区](https://ccnetcore.com/discuss/1641030787056930818)
答1别问问就是Sqlsugar和本框架一样爽
问2以后会持续更新下去吗
答2一定会的我们的标题是 一个和Sqlsugar一样爽的.Net6开源框架 只要Sqlsugar在我们将一直更新下去。
问3这个框架的针对人群是哪些人适合所有人吗
答3并不是适合所有人应该算适合需要有一定基础的开发人员当然如果你是大神你完全可以将这个框架二次开发
问4花如此多的精力制作这个框架是为了什么是为了赚钱吗和目前主流的abp等框架比又有什么意义呢
答4我们与Sqlsugar作者理念一致我们是从用户角度出发框架是为用户服务与ABP复杂上手理念完全是相反的。
问5为何不出版一个详细的说明书呢
答5暂时不会了之后可能会代码都是基于Asp.NetCore框架适用于新手不用造轮子整个框架较为简单阅读源码后基本能自定义改造使用了过难也已经封装完毕别忘了其意义是为了开发更加简易建议添加作者好友这里人人都是顾问。
我大抵要厌倦了负重前行。

BIN
Readme/代码生成.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
Readme/用户管理.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
Readme/菜单管理.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View File

View File

@@ -1,35 +0,0 @@
<template>
<van-image round :width="width" :height="height" :src="getUrl()" />
</template>
<script setup lang="ts">
const props = defineProps({
src: { type: String, default: "null", required: false },
width: { type: String, default: "3rem", required: false },
height: { type: String, default: "3rem", required: false },
});
const url = `${import.meta.env.VITE_APP_BASE_API}/file/`;
const getUrl = () => {
const src = props.src;
if (src === null || typeof src === "undefined" || src.trim() === "") {
return "/icon.jpg";
// 字符串为 null、未定义或为空字符串
} else {
return url + "src";
}
};
</script>
<style scoped>
.col-body {
text-align: center;
}
.col-body .van-icon {
margin-bottom: 0.6rem;
}
p {
font-size: large;
font-weight: bold;
}
</style>

View File

View File

@@ -1,2 +0,0 @@
VITE_APP_BASEAPI="/api-dev"
VITE_APP_URL="http://localhost:19001/api"

View File

@@ -1 +0,0 @@
VITE_APP_BASEAPI="/prod-api"

View File

@@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

View File

@@ -1,29 +0,0 @@
# yi-bbs
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>意社区</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +0,0 @@
{
"name": "yi-bbs",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"axios": "^1.3.4",
"echarts": "^5.4.2",
"element-plus": "^2.2.32",
"highlight": "^0.2.4",
"i": "^0.3.7",
"marked": "^4.2.12",
"mavon-editor": "^3.0.0",
"nprogress": "^0.2.0",
"pinia": "^2.0.32",
"vue": "^3.2.47",
"vue-cropper": "1.0.3",
"vue-router": "^4.1.6",
"yarm": "^0.4.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"sass": "1.52.1",
"unplugin-auto-import": "^0.15.0",
"unplugin-vue-components": "^0.24.0",
"vite": "^4.1.3"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,28 +0,0 @@
<script setup>
</script>
<template>
<el-config-provider :locale="locale">
<RouterView />
</el-config-provider>
</template>
<script setup>
import useConfigStore from "@/stores/config";
import { ElConfigProvider } from 'element-plus'
import {onMounted } from "vue";
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
const locale= zhCn;
const configStore = useConfigStore();
//加载全局信息
onMounted(async()=>{
await configStore.getConfig();
})
</script>
<style scoped>
</style>

View File

@@ -1,17 +0,0 @@
import request from '@/utils/request'
// 触发访问
export function access() {
return request({
url: '/access-log',
method: 'post'
})
}
// 获取本周数据
export function getWeek() {
return request({
url: '/access-log/week',
method: 'get'
})
}

View File

@@ -1,79 +0,0 @@
import request from '@/utils/request'
// 登录方法
export function login(username, password, code, uuid) {
const data = {
username,
password,
code,
uuid
}
return request({
url: '/account/login',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 注册方法
export function register(userName,password,phone,code,uuid) {
const data = {
userName,
password,
phone,
code,
uuid
}
return request({
url: '/account/register',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 获取用户详细信息
export function getInfo() {
return request({
url: '/account',
method: 'get'
})
}
// 退出方法
export function logout() {
return request({
url: '/account/logout',
method: 'post'
})
}
// 获取验证码
export function getCodeImg() {
return request({
url: '/account/captcha-image',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
}
// 获取短信验证码
export function getCodePhone(phone) {
return request({
url: '/account/captcha-phone',
headers: {
isToken: false
},
method: 'post',
timeout: 20000,
data:{phone}
})
}

View File

@@ -1,11 +0,0 @@
import myaxios from '@/utils/request'
export function operate(discussId){
if(discussId==undefined)
{
return;
}
return myaxios({
url: `/agree/operate/${discussId}`,
method: 'post'
})
};

View File

@@ -1,41 +0,0 @@
import myaxios from '@/utils/request'
export function getList(data){
return myaxios({
url: '/article',
method: 'get',
params:data
})
};
export function get(id){
return myaxios({
url: `/article/${id}`,
method: 'get'
})
};
export function add(data){
return myaxios({
url: `/article`,
method: 'post',
data:data
})
};
export function update(id,data){
return myaxios({
url: `/article/${id}`,
method: 'put',
data:data
})
};
export function del(ids){
return myaxios({
url: `/article/${ids}`,
method: 'delete'
})
};
export function all(discussId)
{
return myaxios({
url: `/article/all/discuss-id/${discussId}`,
method: 'get'
})
}

View File

@@ -1,8 +0,0 @@
import myaxios from '@/utils/request'
export function getList(data){
return myaxios({
url: '/banner',
method: 'get',
params:data
})
};

View File

@@ -1,22 +0,0 @@
import myaxios from '@/utils/request'
export function getListByDiscussId(discussId,data){
return myaxios({
url: `/comment/discuss-id/${discussId}`,
method: 'get',
params:data
})
};
export function add(data){
return myaxios({
url: `/comment`,
method: 'post',
data:data
})
};
export function del(ids){
return myaxios({
url: `/comment/${ids}`,
method: 'delete'
})
};

View File

@@ -1,9 +0,0 @@
import myaxios from '@/utils/request'
//获取配置
export function getAll(){
return myaxios({
url: '/config',
method: 'get'
})
};

View File

@@ -1,51 +0,0 @@
import myaxios from '@/utils/request'
export function getList(data){
return myaxios({
url: '/discuss',
method: 'get',
params:data
})
};
export function getTopList(data){
if(data==undefined)
{
data={isTop:true}
}
else
{
data["isTop"]=true;
}
return myaxios({
url: '/discuss',
method: 'get',
params:data
})
};
export function get(id){
return myaxios({
url: `/discuss/${id}`,
method: 'get'
})
};
export function add(data){
return myaxios({
url: `/discuss`,
method: 'post',
data:data
})
};
export function update(id,data){
return myaxios({
url: `/discuss/${id}`,
method: 'put',
data:data
})
};
export function del(ids){
return myaxios({
url: `/discuss/${ids}`,
method: 'delete'
})
};

View File

@@ -1,9 +0,0 @@
import myaxios from '@/utils/request'
export function upload(data){
return myaxios({
url: '/file',
method: 'post',
data:data,
headers: { 'Content-Type': 'multipart/form-data' }
})
};

View File

@@ -1,8 +0,0 @@
import myaxios from '@/utils/request'
export function getList(data){
return myaxios({
url: '/plate',
method: 'get',
params:data
})
};

View File

@@ -1,139 +0,0 @@
import myaxios from '@/utils/request'
// 查询用户列表
export function listUser(query) {
return myaxios({
url: '/user',
method: 'get',
params: query
})
}
// 查询用户详细
export function getUser(userId) {
return myaxios({
url: '/user/' + parseStrEmpty(userId),
method: 'get'
})
}
// 新增用户
export function addUser(data) {
return myaxios({
url: '/user',
method: 'post',
data: data
})
}
// 修改用户
export function updateUser(id, data) {
return myaxios({
url: `/user/${id}`,
method: 'put',
data: data
})
}
// 删除用户
export function delUser(userId) {
return myaxios({
url: `/user/${userId}`,
method: 'delete',
})
}
// 用户密码重置
export function resetUserPwd(id, password) {
const data = {
password
}
return myaxios({
url: `/account/rest-password/${id}`,
method: 'put',
data: data
})
}
// 用户状态修改
export function changeUserStatus(userId, isDel) {
return myaxios({
url: `/user/${userId}/${isDel}`,
method: 'put'
})
}
// 查询用户个人信息
export function getUserProfile() {
return myaxios({
url: '/account',
method: 'get'
})
}
// 修改用户个人信息
export function updateUserProfile(data) {
return myaxios({
url: `/user/profile`,
method: 'put',
data: data
})
}
// 只修改用户头像
export function updateUserIcon(data) {
return myaxios({
url: `/account/icon`,
method: 'put',
data:{icon:data}
})
}
// 用户密码重置
export function updateUserPwd(oldPassword, newPassword) {
const data = {
oldPassword,
newPassword
}
return myaxios({
url: '/account/password',
method: 'put',
data: data
})
}
// 用户头像上传
export function uploadAvatar(data) {
return request({
url: '/system/user/profile/avatar',
method: 'post',
data: data
})
}
// 查询授权角色
export function getAuthRole(userId) {
return request({
url: '/system/user/authRole/' + userId,
method: 'get'
})
}
// 保存授权角色
export function updateAuthRole(data) {
return request({
url: '/system/user/authRole',
method: 'put',
params: data
})
}
// // 查询部门下拉树结构
// export function deptTreeSelect() {
// return request({
// url: '/system/user/deptTree',
// method: 'get'
// })
// }

View File

@@ -1,710 +0,0 @@
@font-face {
font-family: octicons-link;
src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff');
}
.markdown-body {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
line-height: 1.5;
color: #24292e;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 16px;
line-height: 1.5;
word-wrap: break-word;
}
.markdown-body .pl-c {
color: #6a737d;
}
.markdown-body .pl-c1,
.markdown-body .pl-s .pl-v {
color: #005cc5;
}
.markdown-body .pl-e,
.markdown-body .pl-en {
color: #6f42c1;
}
.markdown-body .pl-smi,
.markdown-body .pl-s .pl-s1 {
color: #24292e;
}
.markdown-body .pl-ent {
color: #22863a;
}
.markdown-body .pl-k {
color: #d73a49;
}
.markdown-body .pl-s,
.markdown-body .pl-pds,
.markdown-body .pl-s .pl-pse .pl-s1,
.markdown-body .pl-sr,
.markdown-body .pl-sr .pl-cce,
.markdown-body .pl-sr .pl-sre,
.markdown-body .pl-sr .pl-sra {
color: #032f62;
}
.markdown-body .pl-v,
.markdown-body .pl-smw {
color: #e36209;
}
.markdown-body .pl-bu {
color: #b31d28;
}
.markdown-body .pl-ii {
color: #fafbfc;
background-color: #b31d28;
}
.markdown-body .pl-c2 {
color: #fafbfc;
background-color: #d73a49;
}
.markdown-body .pl-c2::before {
content: "^M";
}
.markdown-body .pl-sr .pl-cce {
font-weight: bold;
color: #22863a;
}
.markdown-body .pl-ml {
color: #735c0f;
}
.markdown-body .pl-mh,
.markdown-body .pl-mh .pl-en,
.markdown-body .pl-ms {
font-weight: bold;
color: #005cc5;
}
.markdown-body .pl-mi {
font-style: italic;
color: #24292e;
}
.markdown-body .pl-mb {
font-weight: bold;
color: #24292e;
}
.markdown-body .pl-md {
color: #b31d28;
background-color: #ffeef0;
}
.markdown-body .pl-mi1 {
color: #22863a;
background-color: #f0fff4;
}
.markdown-body .pl-mc {
color: #e36209;
background-color: #ffebda;
}
.markdown-body .pl-mi2 {
color: #f6f8fa;
background-color: #005cc5;
}
.markdown-body .pl-mdr {
font-weight: bold;
color: #6f42c1;
}
.markdown-body .pl-ba {
color: #586069;
}
.markdown-body .pl-sg {
color: #959da5;
}
.markdown-body .pl-corl {
text-decoration: underline;
color: #032f62;
}
.markdown-body .octicon {
display: inline-block;
vertical-align: text-top;
fill: currentColor;
}
.markdown-body a {
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
.markdown-body a:active,
.markdown-body a:hover {
outline-width: 0;
}
.markdown-body strong {
font-weight: inherit;
}
.markdown-body strong {
font-weight: bolder;
}
.markdown-body h1 {
font-size: 2em;
margin: 0.67em 0;
}
.markdown-body img {
border-style: none;
}
.markdown-body svg:not(:root) {
overflow: hidden;
}
.markdown-body code,
.markdown-body kbd,
.markdown-body pre {
font-family: monospace, monospace;
font-size: 1em;
}
.markdown-body hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
.markdown-body input {
font: inherit;
margin: 0;
}
.markdown-body input {
overflow: visible;
}
.markdown-body [type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
.markdown-body * {
box-sizing: border-box;
}
.markdown-body input {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.markdown-body a {
color: #0366d6;
text-decoration: none;
}
.markdown-body a:hover {
text-decoration: underline;
}
.markdown-body strong {
font-weight: 600;
}
.markdown-body hr {
height: 0;
margin: 15px 0;
overflow: hidden;
background: transparent;
border: 0;
border-bottom: 1px solid #dfe2e5;
}
.markdown-body hr::before {
display: table;
content: "";
}
.markdown-body hr::after {
display: table;
clear: both;
content: "";
}
.markdown-body table {
border-spacing: 0;
border-collapse: collapse;
}
.markdown-body td,
.markdown-body th {
padding: 0;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body h1 {
font-size: 32px;
font-weight: 600;
}
.markdown-body h2 {
font-size: 24px;
font-weight: 600;
}
.markdown-body h3 {
font-size: 20px;
font-weight: 600;
}
.markdown-body h4 {
font-size: 16px;
font-weight: 600;
}
.markdown-body h5 {
font-size: 14px;
font-weight: 600;
}
.markdown-body h6 {
font-size: 12px;
font-weight: 600;
}
.markdown-body p {
margin-top: 0;
margin-bottom: 10px;
}
.markdown-body blockquote {
margin: 0;
}
.markdown-body ul,
.markdown-body ol {
padding-left: 0;
margin-top: 0;
margin-bottom: 0;
}
.markdown-body ol ol,
.markdown-body ul ol {
list-style-type: lower-roman;
}
.markdown-body ul ul ol,
.markdown-body ul ol ol,
.markdown-body ol ul ol,
.markdown-body ol ol ol {
list-style-type: lower-alpha;
}
.markdown-body dd {
margin-left: 0;
}
.markdown-body code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
}
.markdown-body pre {
margin-top: 0;
margin-bottom: 0;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
}
.markdown-body .octicon {
vertical-align: text-bottom;
}
.markdown-body .pl-0 {
padding-left: 0 !important;
}
.markdown-body .pl-1 {
padding-left: 4px !important;
}
.markdown-body .pl-2 {
padding-left: 8px !important;
}
.markdown-body .pl-3 {
padding-left: 16px !important;
}
.markdown-body .pl-4 {
padding-left: 24px !important;
}
.markdown-body .pl-5 {
padding-left: 32px !important;
}
.markdown-body .pl-6 {
padding-left: 40px !important;
}
.markdown-body::before {
display: table;
content: "";
}
.markdown-body::after {
display: table;
clear: both;
content: "";
}
.markdown-body>*:first-child {
margin-top: 0 !important;
}
.markdown-body>*:last-child {
margin-bottom: 0 !important;
}
.markdown-body a:not([href]) {
color: inherit;
text-decoration: none;
}
.markdown-body .anchor {
float: left;
padding-right: 4px;
margin-left: -20px;
line-height: 1;
}
.markdown-body .anchor:focus {
outline: none;
}
.markdown-body p,
.markdown-body blockquote,
.markdown-body ul,
.markdown-body ol,
.markdown-body dl,
.markdown-body table,
.markdown-body pre {
margin-top: 0;
margin-bottom: 16px;
}
.markdown-body hr {
height: 0.25em;
padding: 0;
margin: 24px 0;
background-color: #e1e4e8;
border: 0;
}
.markdown-body blockquote {
padding: 0 1em;
color: #6a737d;
border-left: 0.25em solid #dfe2e5;
}
.markdown-body blockquote>:first-child {
margin-top: 0;
}
.markdown-body blockquote>:last-child {
margin-bottom: 0;
}
.markdown-body kbd {
display: inline-block;
padding: 3px 5px;
font-size: 11px;
line-height: 10px;
color: #444d56;
vertical-align: middle;
background-color: #fafbfc;
border: solid 1px #c6cbd1;
border-bottom-color: #959da5;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #959da5;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
}
.markdown-body h1 .octicon-link,
.markdown-body h2 .octicon-link,
.markdown-body h3 .octicon-link,
.markdown-body h4 .octicon-link,
.markdown-body h5 .octicon-link,
.markdown-body h6 .octicon-link {
color: #1b1f23;
vertical-align: middle;
visibility: hidden;
}
.markdown-body h1:hover .anchor,
.markdown-body h2:hover .anchor,
.markdown-body h3:hover .anchor,
.markdown-body h4:hover .anchor,
.markdown-body h5:hover .anchor,
.markdown-body h6:hover .anchor {
text-decoration: none;
}
.markdown-body h1:hover .anchor .octicon-link,
.markdown-body h2:hover .anchor .octicon-link,
.markdown-body h3:hover .anchor .octicon-link,
.markdown-body h4:hover .anchor .octicon-link,
.markdown-body h5:hover .anchor .octicon-link,
.markdown-body h6:hover .anchor .octicon-link {
visibility: visible;
}
.markdown-body h1 {
padding-bottom: 0.3em;
font-size: 2em;
border-bottom: 1px solid #eaecef;
}
.markdown-body h2 {
padding-bottom: 0.3em;
font-size: 1.5em;
border-bottom: 1px solid #eaecef;
}
.markdown-body h3 {
font-size: 1.25em;
}
.markdown-body h4 {
font-size: 1em;
}
.markdown-body h5 {
font-size: 0.875em;
}
.markdown-body h6 {
font-size: 0.85em;
color: #6a737d;
}
.markdown-body ul,
.markdown-body ol {
padding-left: 2em;
}
.markdown-body ul ul,
.markdown-body ul ol,
.markdown-body ol ol,
.markdown-body ol ul {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body li>p {
margin-top: 16px;
}
.markdown-body li+li {
margin-top: 0.25em;
}
.markdown-body dl {
padding: 0;
}
.markdown-body dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: 600;
}
.markdown-body dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
.markdown-body table {
display: block;
width: 100%;
overflow: auto;
}
.markdown-body table th {
font-weight: 600;
}
.markdown-body table th,
.markdown-body table td {
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
.markdown-body table tr {
background-color: #fff;
border-top: 1px solid #c6cbd1;
}
.markdown-body table tr:nth-child(2n) {
background-color: #f6f8fa;
}
.markdown-body img {
max-width: 100%;
box-sizing: content-box;
background-color: #fff;
}
.markdown-body img[align=right] {
padding-left: 20px;
}
.markdown-body img[align=left] {
padding-right: 20px;
}
.markdown-body code {
padding: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
margin: 0;
font-size: 85%;
background-color: rgba(27,31,35,0.05);
border-radius: 3px;
}
.markdown-body code::before,
.markdown-body code::after {
letter-spacing: -0.2em;
content: "\00a0";
}
.markdown-body pre {
word-wrap: normal;
}
.markdown-body pre>code {
padding: 0;
margin: 0;
font-size: 100%;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}
.markdown-body .highlight {
margin-bottom: 16px;
}
.markdown-body .highlight pre {
margin-bottom: 0;
word-break: normal;
}
.markdown-body .highlight pre,
.markdown-body pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
/* background-color: #f6f8fa; */
border-radius: 3px;
}
.markdown-body pre code {
display: inline;
max-width: auto;
padding: 0;
margin: 0;
overflow: visible;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
}
.markdown-body pre code::before,
.markdown-body pre code::after {
content: normal;
}
.markdown-body .full-commit .btn-outline:not(:disabled):hover {
color: #005cc5;
border-color: #005cc5;
}
.markdown-body kbd {
display: inline-block;
padding: 3px 5px;
font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
line-height: 10px;
color: #444d56;
vertical-align: middle;
background-color: #fafbfc;
border: solid 1px #d1d5da;
border-bottom-color: #c6cbd1;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #c6cbd1;
}
.markdown-body :checked+.radio-label {
position: relative;
z-index: 1;
border-color: #0366d6;
}
.markdown-body .task-list-item {
list-style-type: none;
}
.markdown-body .task-list-item+.task-list-item {
margin-top: 3px;
}
.markdown-body .task-list-item input {
margin: 0 0.2em 0.25em -1.6em;
vertical-align: middle;
}
.markdown-body hr {
border-bottom-color: #eee;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,48 +0,0 @@
/* @import './base.css'; */
/* #app {
margin: 0 auto;
font-weight: normal;
} */
body
{
margin: 0 auto;
font-weight: normal;
}
a{
text-decoration: none;
}
/*
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
} */

View File

@@ -1,190 +0,0 @@
// @import './variables.module.scss';
// @import './mixin.scss';
// @import './transition.scss';
// @import './element-ui.scss';
// @import './sidebar.scss';
// @import './btn.scss';
// @import './ruoyi.scss';
// body {
// height: 100%;
// margin: 0;
// -moz-osx-font-smoothing: grayscale;
// -webkit-font-smoothing: antialiased;
// text-rendering: optimizeLegibility;
// font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
// }
// label {
// font-weight: 700;
// }
// html {
// height: 100%;
// box-sizing: border-box;
// }
// #app {
// height: 100%;
// }
// *,
// *:before,
// *:after {
// box-sizing: inherit;
// }
// .no-padding {
// padding: 0px !important;
// }
// .padding-content {
// padding: 4px 0;
// }
// a:focus,
// a:active {
// outline: none;
// }
// a,
// a:focus,
// a:hover {
// cursor: pointer;
// color: inherit;
// text-decoration: none;
// }
// div:focus {
// outline: none;
// }
// .fr {
// float: right;
// }
// .fl {
// float: left;
// }
// .pr-5 {
// padding-right: 5px;
// }
// .pl-5 {
// padding-left: 5px;
// }
// .block {
// display: block;
// }
// .pointer {
// cursor: pointer;
// }
// .inlineBlock {
// display: block;
// }
// .clearfix {
// &:after {
// visibility: hidden;
// display: block;
// font-size: 0;
// content: " ";
// clear: both;
// height: 0;
// }
// }
// aside {
// background: #eef1f6;
// padding: 8px 24px;
// margin-bottom: 20px;
// border-radius: 2px;
// display: block;
// line-height: 32px;
// font-size: 16px;
// font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
// color: #2c3e50;
// -webkit-font-smoothing: antialiased;
// -moz-osx-font-smoothing: grayscale;
// a {
// color: #337ab7;
// cursor: pointer;
// &:hover {
// color: rgb(32, 160, 255);
// }
// }
// }
// .components-container {
// margin: 30px 50px;
// position: relative;
// }
// .pagination-container {
// margin-top: 30px;
// }
.text-center {
text-align: center
}
// .sub-navbar {
// height: 50px;
// line-height: 50px;
// position: relative;
// width: 100%;
// text-align: right;
// padding-right: 20px;
// transition: 600ms ease position;
// background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
// .subtitle {
// font-size: 20px;
// color: #fff;
// }
// &.draft {
// background: #d0d0d0;
// }
// &.deleted {
// background: #d0d0d0;
// }
// }
// .link-type,
// .link-type:focus {
// color: #337ab7;
// cursor: pointer;
// &:hover {
// color: rgb(32, 160, 255);
// }
// }
// .filter-container {
// padding-bottom: 10px;
// .filter-item {
// display: inline-block;
// vertical-align: middle;
// margin-bottom: 10px;
// }
// }
// //refine vue-multiselect plugin
// .multiselect {
// line-height: 16px;
// }
// .multiselect--active {
// z-index: 1000 !important;
// }

View File

@@ -1,206 +0,0 @@
* {
margin: 0;
padding: 0;
}
/*公共CSS*/
.box {
width: 100vw;
height: 100vh;
background-color: rgb(29, 67, 89);
.content {
box-shadow: 0px 1px 6px #3B4859;
.login-wrapper {
h1 {
text-align: center;
}
.login-form {
.form-item {
margin: 20px 0;
span {
display: block;
margin: 5px 20px;
font-weight: 100;
}
.input-item {
width: 100%;
border-radius: 40px;
padding: 20px;
box-sizing: border-box;
font-size: 20px;
font-weight: 200;
&:focus {
outline: none;
}
}
}
.login-btn {
width: 100%;
border-radius: 40px;
color: #fff;
border: 0;
font-weight: 100;
margin-top: 10px;
cursor: pointer;
}
}
.divider {
width: 100%;
margin: 20px 0;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
span:nth-child(1) {
flex: 1;
}
span:nth-child(3) {
flex: 1;
}
.line {
display: inline-block;
max-width: 30%;
width: 30%;
}
.divider-text {
vertical-align: middle;
margin: 0px 20px;
line-height: 0px;
display: inline-block;
width: 100px;
}
}
.other-login-wrapper {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.other-login-item {
border: 1px solid rgb(214, 222, 228);
padding: 10px;
margin: 10px;
cursor: pointer;
}
}
}
}
/*一般大于手机的尺寸CSS*/
@media (min-width: 767px) {
.box {
background-color: #F0F2F5;
.content {
width: 85vw;
height: 90vh;
background: url("@/assets/login_images/login_two.jpg") no-repeat;
background-size: 90% 100%;
position: absolute;
right: 15%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 20px;
background-color: #fff;
.login-wrapper {
width: 25vw;
position: absolute;
right: 15%;
top: 50%;
transform: translateY(-50%);
h1 {
text-align: center;
font-size: 45px;
color: rgb(81, 100, 115);
margin-bottom: 40px;
}
.login-form {
margin: 10px 0;
.form-item {
span {
color: rgb(81, 100, 115);
}
.input-item {
height: 60px;
border: 1px solid rgb(214, 222, 228);
}
}
.login-btn {
height: 50px;
background-color: rgb(59, 72, 89);
font-size: 20px;
}
}
.divider {
.line {
border-bottom: 1px solid rgb(214, 222, 228);
}
}
.other-login-item {
border-radius: 20px;
img {
width: 40px;
height: 40px;
}
}
}
}
}
}
/*手机端CSS*/
@media (max-width: 768px) {
.box {
.content {
width: 100vw;
height: 100vh;
background: url("@/assets/login_images/login_bg_phone.png") no-repeat;
background-size: 100% 100%;
display: flex;
align-items: flex-start;
justify-content: center;
.login-wrapper {
width: 70%;
height: 60%;
padding-top: 15%;
h1 {
font-size: 30px;
color: #fff;
}
.login-form {
.form-item {
margin: 10px 0;
span {
color: rgb(113, 129, 141);
}
.input-item {
height: 30px;
border: 1px solid rgb(113, 129, 141);
background-color: transparent;
color: #fff;
}
}
.login-btn {
height: 40px;
background-color: rgb(235, 95, 93);
font-size: 16px;
}
}
.divider {
.line {
border-bottom: 1px solid #fff;
}
.divider-text {
color: #fff;
}
}
.other-login-item {
border-radius: 15px;
img {
width: 35px;
height: 35px;
}
}
}
}
}
}

View File

@@ -1,271 +0,0 @@
/**
* 通用css样式布局处理
* Copyright (c) 2019 ruoyi
*/
/** 基础通用 **/
.pt5 {
padding-top: 5px;
}
.pr5 {
padding-right: 5px;
}
.pb5 {
padding-bottom: 5px;
}
.mt5 {
margin-top: 5px;
}
.mr5 {
margin-right: 5px;
}
.mb5 {
margin-bottom: 5px;
}
.mb8 {
margin-bottom: 8px;
}
.ml5 {
margin-left: 5px;
}
.mt10 {
margin-top: 10px;
}
.mr10 {
margin-right: 10px;
}
.mb10 {
margin-bottom: 10px;
}
.ml10 {
margin-left: 10px;
}
.mt20 {
margin-top: 20px;
}
.mr20 {
margin-right: 20px;
}
.mb20 {
margin-bottom: 20px;
}
.ml20 {
margin-left: 20px;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
.el-dialog:not(.is-fullscreen) {
margin-top: 6vh !important;
}
.el-dialog.scrollbar .el-dialog__body {
overflow: auto;
overflow-x: hidden;
max-height: 70vh;
padding: 10px 20px 0;
}
.el-table {
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
th {
word-break: break-word;
background-color: #f8f8f9 !important;
color: #515a6e;
height: 40px !important;
font-size: 13px;
}
}
.el-table__body-wrapper {
.el-button [class*="el-icon-"] + span {
margin-left: 1px;
}
}
}
/** 表单布局 **/
.form-header {
font-size:15px;
color:#6379bb;
border-bottom:1px solid #ddd;
margin:8px 10px 25px 10px;
padding-bottom:5px
}
/** 表格布局 **/
.pagination-container {
// position: relative;
height: 25px;
margin-bottom: 10px;
margin-top: 15px;
padding: 10px 20px !important;
}
/* tree border */
.tree-border {
margin-top: 5px;
border: 1px solid #e5e6e7;
background: #FFFFFF none;
border-radius:4px;
width: 100%;
}
.pagination-container .el-pagination {
right: 0;
position: absolute;
}
@media ( max-width : 768px) {
.pagination-container .el-pagination > .el-pagination__jump {
display: none !important;
}
.pagination-container .el-pagination > .el-pagination__sizes {
display: none !important;
}
}
.el-table .fixed-width .el-button--small {
padding-left: 0;
padding-right: 0;
width: inherit;
}
/** 表格更多操作下拉样式 */
.el-table .el-dropdown-link {
cursor: pointer;
color: #409EFF;
margin-left: 10px;
}
.el-table .el-dropdown, .el-icon-arrow-down {
font-size: 12px;
}
.el-tree-node__content > .el-checkbox {
margin-right: 8px;
}
.list-group-striped > .list-group-item {
border-left: 0;
border-right: 0;
border-radius: 0;
padding-left: 0;
padding-right: 0;
}
.list-group {
padding-left: 0px;
list-style: none;
}
.list-group-item {
border-bottom: 1px solid #e7eaec;
border-top: 1px solid #e7eaec;
margin-bottom: -1px;
padding: 11px 0px;
font-size: 13px;
}
// .el-card__header {
// padding: 14px 15px 7px !important;
// min-height: 40px;
// }
// .el-card__body {
// padding: 15px 20px 20px 20px !important;
// }
.card-box {
padding-right: 15px;
padding-left: 15px;
margin-bottom: 10px;
}
/* button color */
.el-button--cyan.is-active,
.el-button--cyan:active {
background: #20B2AA;
border-color: #20B2AA;
color: #FFFFFF;
}
.el-button--cyan:focus,
.el-button--cyan:hover {
background: #48D1CC;
border-color: #48D1CC;
color: #FFFFFF;
}
.el-button--cyan {
background-color: #20B2AA;
border-color: #20B2AA;
color: #FFFFFF;
}
/* text color */
.text-navy {
color: #1ab394;
}
.text-primary {
color: inherit;
}
.text-success {
color: #1c84c6;
}
.text-info {
color: #23c6c8;
}
.text-warning {
color: #f8ac59;
}
.text-danger {
color: #ed5565;
}
.text-muted {
color: #888888;
}
/* image */
.img-circle {
border-radius: 50%;
}
.img-lg {
width: 120px;
height: 120px;
}
.avatar-upload-preview {
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 200px;
height: 200px;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
}
/* 拖拽列样式 */
.sortable-ghost{
opacity: .8;
color: #fff!important;
background: #42b983!important;
}
/* 表格右侧工具栏样式 */
.top-right-btn {
margin-left: auto;
}

View File

@@ -1,55 +0,0 @@
<template>
<el-button text @click="agree">
<el-icon v-if="data.isAgree" color="#409EFF">
<CircleCheckFilled />
</el-icon>
<el-icon v-else color="#1E1E1E">
<Pointer />
</el-icon> 点赞:{{ data.agreeNum ?? 0 }}</el-button>
</template>
<script setup>
import {onMounted,reactive,watch} from 'vue'
import { operate } from '@/apis/agreeApi'
//'isAgree','agreeNum','id'
const props = defineProps([ 'data'])
watch(()=>props,(n)=>{
data.id=n.data.id;
data.isAgree=n.data.isAgree;
data.agreeNum=n.data.agreeNum;
},{deep:true})
const data=reactive({
id:'',
isAgree:false,
agreeNum:0
})
// onMounted(()=>{
// })
//点赞操作
const agree = async () => {
const response = await operate(data.id)
const res = response.data;
//提示框,颜色区分
if (res.isAgree) {
data.isAgree = true;
data.agreeNum += 1;
ElMessage({
message: res.message,
type: 'success',
})
}
else {
data.isAgree = false;
data.agreeNum-= 1;
ElMessage({
message: res.message,
type: 'warning',
})
}
}
</script>

View File

@@ -1,43 +0,0 @@
<template>
<div>
<div class="markdown-body" v-html="outputHtml"></div>
</div>
</template>
<script setup>
import { marked } from 'marked';
import hljs from "highlight.js";
//可以设置加载样式切换主题
import 'highlight.js/styles/atom-one-dark.css'
import '@/assets/github-markdown.css'
import { ref,watch } from 'vue';
const outputHtml=ref("")
const props = defineProps(['code'])
watch(props,(n,o)=>{
marked.setOptions({
renderer: new marked.Renderer(),
highlight: function(code) {
return hljs.highlightAuto(code).value;
},
pedantic: false,
gfm: true,//允许 Git Hub标准的markdown
tables: true,//支持表格
breaks: true,
sanitize: false,
smartLists: true,
smartypants: false,
xhtml: false,
smartLists: true,
}
);
//需要注意代码块样式
outputHtml.value = marked(n.code).replace(/<pre>/g, "<pre class='hljs'>")
})
</script>

View File

@@ -1,131 +0,0 @@
<template >
<div class="avatar">
<div class="avatar-left">
<el-avatar :size="props.size" :src="iconUrl" />
<div v-if="props.isSelf">
<div class="nick"> {{ userInfo.nick }}</div>
</div>
<div v-if="!props.isSelf">
<div class="nick" :class="{ mt_1: props.time != 'undefined' }"> {{ userInfo.nick }}</div>
<div class="remarks" v-if="props.time"> {{ props.time }}</div>
<div class="remarks">
<slot name="bottom" />
</div>
</div>
<div class="info" v-if="!props.isSelf">
<el-tag class="ml-2" type="warning">V8</el-tag>
<el-tag class="ml-2" type="danger">会员</el-tag>
</div>
</div>
<el-button v-if="props.showWatching" type="primary" size="default" icon="Plus">关注</el-button>
</div>
</template>
<script setup>
import useUserStore from '@/stores/user'
import { reactive, watch, onMounted, computed, ref } from 'vue';
//userInfo
//{icon,name,role,id},根据判断userInfo是否等于未定义来觉得是当前登录用户信息还是其他人信息
const props = defineProps(['size', 'showWatching', 'time', 'userInfo', 'isSelf'])
const userStore = useUserStore();
const userInfo = reactive({
icon: "",
nick: "",
role: [],
id: ""
});
const iconUrl=ref('/src/assets/logo.ico');
const iconUrlHandler = () => {
if (userInfo.icon == null || userInfo.icon == undefined || userInfo.icon == '') {
return '/src/assets/logo.ico';
}
return `${import.meta.env.VITE_APP_BASEAPI}/file/${userInfo.icon}`;
}
watch(userStore, (n) => {
if (props.userInfo == undefined) {
userInfo.nick = n.name;
}
})
watch(() => props, (n) => {
Init();
}, { deep: true })
onMounted(() => {
Init();
})
const Init = () => {
//使用传入值
if (props.userInfo != undefined) {
userInfo.icon = props.userInfo.icon;
userInfo.nick = props.userInfo.nick;
userInfo.role = props.userInfo.role;
userInfo.id = props.userInfo.id;
iconUrl.value=iconUrlHandler(userInfo.icon)
}
//使用当前登录用户
else {
userInfo.icon = userStore.icon;
userInfo.nick = userStore.name;
userInfo.role = userStore.role;
userInfo.id = userStore.id;
iconUrl.value=userInfo.icon;
}
}
</script>
<style scoped>
.mt_1 {
margin-top: 0.5rem;
}
.nick {
font-weight: bold;
}
.info {
margin-top: 0.6rem;
margin-left: 1rem;
}
.info .el-tag {
margin-right: 1rem;
}
.el-icon {
color: white;
}
.avatar {
display: flex;
justify-content: space-between;
}
.avatar-left {
display: flex;
justify-content: flex-start;
align-items: center;
}
.el-avatar {
margin-right: 1.2rem;
}
.remarks {
padding-top: 0.5rem;
color: #8C8C8C;
}
</style>

View File

@@ -1,38 +0,0 @@
<template>
<div class="botton-div">
<a><el-icon><UserFilled /></el-icon>站长{{configStore.author}}</a>
<a><el-icon><Search /></el-icon>{{configStore.bottom}}</a>
<a><el-icon><View /></el-icon>关于本站</a>
<a><el-icon><Message /></el-icon>建议反馈</a>
<p></p>
<a ><el-icon><Position /></el-icon>{{configStore.icp}}</a>
</div>
</template>
<script setup>
import useConfigStore from "@/stores/config";
const configStore= useConfigStore();
</script>
<style scoped>
.el-icon
{margin: 0 0.2rem;}
a{
margin-right: 2rem;
line-height: 1.8rem;
}
a:hover {
color: #40a9ff;
cursor:pointer;
}
.botton-div
{
background: transparent;
color: rgba(0,0,0,.45);
font-size: 14px;
display: flex;
flex-wrap: wrap;
height: auto;
width: auto;
justify-content: center;
margin: 0.5rem auto;
}
</style>

View File

@@ -1,204 +0,0 @@
<template>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="评论" name="comment"></el-tab-pane>
<el-tab-pane label="相关内容" name="interrelated"></el-tab-pane>
</el-tabs>
<div class="total">
<div style="align-self: center;"> {{total}}个评论</div>
<div> <el-radio-group v-model="selectRadio">
<el-radio-button label="new" name="new">最新</el-radio-button>
<el-radio-button label="host" name="host">最热</el-radio-button>
</el-radio-group></div>
</div>
<el-divider />
<div v-hasPer="['bbs:comment:add']">
<el-input v-model="topContent" placeholder="发表一个友善的评论吧~" :rows="5" type="textarea"></el-input>
<el-button @click="addTopComment" type="primary" class="btn-top-comment" >发表评论</el-button>
<el-button class="btn-top-comment">其他</el-button>
<el-divider />
</div>
<!-- 开始评论主体 -->
<div v-for="item in commentList" :key="item.id" class="comment1">
<AvatarInfo :userInfo="item.createUser" />
<div class="content">
{{ item.content }}
</div>
<span class="time"> {{ item.creationTime }} </span>
<span class="pointer"><el-icon>
<Pointer />
</el-icon> 0</span>
<el-button type="primary" @click="replay(item.createUser.nick, item.id, item.id)" size="large" text v-hasPer="['bbs:comment:add']">回复</el-button>
<el-button type="danger" @click="delComment(item.id)" size="large" text v-hasPer="['bbs:comment:remove']">删除</el-button>
<div v-show="replayId == item.id" class="input-reply">
<el-input v-model="form.content" :placeholder="placeholder" :rows="3" type="textarea"></el-input>
<div class="btn-reply">
<el-button @click="addComment" type="primary" v-hasPer="['bbs:comment:add']">回复</el-button>
</div>
</div>
<!-- 开始子评论主体 -->
<div v-for="children in item.children" :key="children.id" class="comment2">
<div style="display: flex ;">
<AvatarInfo :userInfo="children.createUser" />
<span style="align-self: center;color:#606266;"> 回复@{{ children.commentedUser.nick }}</span>
</div>
<div class="content">
{{ children.content }}
</div>
<span class="time">{{ children.creationTime }} </span>
<span class="pointer"> <el-icon>
<Pointer />
</el-icon>0</span>
<el-button type="primary" @click="replay(children.createUser.nick, children.id, item.id)" size="large" text v-hasPer="['bbs:comment:add']">回复</el-button>
<el-button type="danger" @click="delComment(children.id)" size="large" text v-hasPer="['bbs:comment:remove']">删除</el-button>
<div v-show="replayId == children.id" class="input-reply">
<el-input v-model="form.content" :placeholder="placeholder" :rows="3" type="textarea"></el-input>
<div class="btn-reply">
<el-button @click="addComment" type="primary" v-hasPer="['bbs:comment:add']">回复</el-button>
</div>
</div>
</div>
<el-divider />
</div>
<el-empty v-show="commentList.length<=0" description="评论空空如也,快来抢占沙发~" />
</template>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { getListByDiscussId, add ,del} from "@/apis/commentApi.js";
import AvatarInfo from './AvatarInfo.vue';
//数据定义
const route = useRoute();
const router = useRouter();
const commentList = ref([]);
const query = reactive({});
const topContent=ref('');
//当前回复id
const replayId = ref('');
//回复文本框
const placeholder = ref('')
//选择类型:评论
const activeName = ref('comment');
//选择 最新
const selectRadio = ref('new');
//评论总数
const total=ref(0);
const form = reactive({
content: "",
discussId: route.params.discussId,
query,
parentId: 0,
rootId: 0,
});
onMounted(async () => {
await loadComment();
});
const loadComment = async () => {
topContent.value='';
form.content = '';
const response = await getListByDiscussId(route.params.discussId, query);
commentList.value = response.data.items;
total.value=response.data.total
};
const addTopComment = async () => {
form.parentId = 0;
form.rootId = 0;
form.content=topContent.value;
await addComment();
}
const addComment = async () => {
if(form.content.length<=0)
{
ElMessage.error('输入评论不能为空!')
return
}
await add(form);
await loadComment();
ElMessage({
message: '评论发表成功!',
type: 'success',
})
};
const delComment=async(ids)=>{
ElMessageBox.confirm(`确定是否删除编号[${ids}]的评论吗?`, "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
await del(ids);
await loadComment();
ElMessage({
message: '评论已删除!',
type: 'success',
})
});
}
const replay = async (parentUserName, parentId, rootId) => {
replayId.value = parentId;
form.parentId = parentId;
form.rootId = rootId;
placeholder.value = `回复@${parentUserName}`;
}
//切换 评论、相关内容
const handleClick = () => { }
</script>
<style scoped>
.input-reply {
margin-top: 1rem;
}
.btn-reply {
margin: 1rem 0;
display: flex;
justify-content: end;
}
.comment1 .pointer {
margin: 0 0 0 1rem;
}
.time {
color: #8C8C8C;
}
.content {
margin: 1rem 0;
}
.total {
display: flex;
justify-content: space-between;
}
.comment2 {
margin-left: 3rem;
}
.el-divider {
margin: 1rem 0;
}
.btn-top-comment {
margin-top: 0.5rem;
margin-right: 1rem;
}
</style>

View File

@@ -1,197 +0,0 @@
<template>
<el-badge :value="props.badge ?? ''" class="box-card">
<el-card shadow="never" :style="{ 'border-color': discuss.color }">
<el-row>
<!-- 头部 -->
<el-col :span=24 class="card-header">
<AvatarInfo :userInfo="discuss.user" :time="discuss.creationTime" />
</el-col>
<!-- 身体 -->
<el-col :span=18 >
<el-row>
<el-col v-if="discuss.isBan" :span=24 class=" item item-title "> <el-link size="100" :underline="false"
style="color:#F56C6C;" >{{ discuss.title }}</el-link></el-col>
<el-col v-else :span=24 class=" item item-title "> <el-link size="100" :underline="false"
@click="enterDiscuss(discuss.id)">{{ discuss.title }}</el-link></el-col>
<el-col :span=24 class=" item item-description">{{ discuss.introduction }}</el-col>
<el-col :span=24 class=" item item-tag"><el-tag v-for="i in 4" :key="i">教程</el-tag></el-col>
</el-row>
</el-col>
<el-col :span=6 style=" display: flex;justify-content: center;">
<el-image :preview-src-list="[getUrl(discuss.cover)]" v-if="discuss.cover" :src="getUrl(discuss.cover)" style="width: 100px;height: 100px;" />
</el-col>
<!-- 底部 -->
<el-col :span=24 class=" item item-bottom " style=" margin-bottom: 0;">
<el-space :size="10" :spacer="spacer">
<div class="item-description">
{{ discuss.creationTime }}
</div>
<AgreeInfo :data="discuss"/>
<!--
<el-button text @click="agree">
<el-icon v-if="discuss.isAgree" color="#409EFF">
<CircleCheckFilled />
</el-icon>
<el-icon v-else color="#1E1E1E">
<Pointer />
</el-icon> 点赞:{{ discuss.agreeNum ?? 0 }}</el-button>
<el-button icon="Star" text>
收藏</el-button> -->
<el-button icon="View" text>
浏览数:{{ discuss.seeNum ?? 0 }}</el-button>
</el-space>
</el-col>
</el-row>
</el-card>
</el-badge>
</template>
<script setup>
import { h, ref, toRef, onMounted ,reactive} from 'vue'
import { useRouter } from 'vue-router'
import AvatarInfo from './AvatarInfo.vue';
import AgreeInfo from './AgreeInfo.vue'
import { operate } from '@/apis/agreeApi'
const props = defineProps(['discuss','badge'])
const discuss=reactive({
id:'',
title:"",
introduction:"",
creationTime:"",
user:{},
color:"",
seeNum:0,
agreeNum:0,
isAgree:false,
cover:"",
isBan:false,
isAgree:false,
agreeNum:0
})
const router = useRouter()
const spacer = h(ElDivider, { direction: 'vertical' })
const enterDiscuss = (id) => {
router.push(`/article/${id}`)
}
const getUrl= (str)=>{
return `${import.meta.env.VITE_APP_BASEAPI}/file/${str}`
}
//点赞操作
const agree = async () => {
const response = await operate(discuss.id)
const res = response.data;
//提示框,颜色区分
if (res.isAgree) {
discuss.isAgree = true;
discuss.agreeNum += 1;
ElMessage({
message: res.message,
type: 'success',
})
}
else {
discuss.isAgree = false;
discuss.agreeNum-= 1;
ElMessage({
message: res.message,
type: 'warning',
})
}
}
onMounted(() => {
// id:'',
// title:"",
// introduction:"",
// creationTime:"",
// user:{},
// color:"",
// seeNum:0,
// agreeNum:0,
// isAgree:""
discuss.id=props.discuss.id;
discuss.title=props.discuss.title;
discuss.introduction=props.discuss.introduction;
discuss.creationTime=props.discuss.creationTime;
discuss.user=props.discuss.user;
discuss.color=props.discuss.color;
discuss.seeNum=props.discuss.seeNum;
discuss.isAgree=props.discuss.isAgree;
discuss.agreeNum=props.discuss.agreeNum;
discuss.isBan=props.discuss.isBan;
discuss.cover=props.discuss.cover;
discuss.value = props.isAgree;
discuss.value = props.agreeNum;
})
</script>
<style scoped>
.el-card {
border: 2px solid white
}
.item-bottom .el-icon {
margin-right: 0.4rem;
}
.card-header {
display: flex;
margin-bottom: 1.5rem;
align-items: center;
}
.item {
font-size: 14px;
margin-bottom: 18px;
}
.box-card {
width: 100%;
min-height: 15rem;
/* right: calc(1px + var(--el-badge-size)/ 2) !important; */
/* top: 0 !important; */
}
.item-title {
/* font-size: var(--el-font-size-large); */
}
.item-description {
font-size: var(--el-font-size-small);
color: #8C8C8C;
}
.item .el-tag {
margin-right: 1rem;
}
.ml-2 {
margin-left: 1.2rem;
}
.el-link {
font-size: initial;
font-weight: 700;
}
</style>

View File

@@ -1,55 +0,0 @@
<script setup>
import { ref } from 'vue'
const props = defineProps(['items','header','text','hideDivider'])
</script>
<template>
<el-card class="box-card" shadow="never">
<template #header>
<div class="card-header">
<span>{{props.header}}</span>
<el-link :underline="false" type="primary">{{props.text}}</el-link>
</div>
</template>
<slot name="content" />
<div v-for="(item,i) in props.items " >
<div class="text item">
<slot name="item" v-bind="item"/>
</div>
<el-divider v-if="i!=props.items.length-1&&hideDivider==undefined" />
</div>
</el-card>
</template>
<style scoped>
.el-divider
{
margin: 0.2rem 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.text {
font-size: 14px;
}
.item {
margin: 0.5rem 0;
}
.box-card {
width: 100%;
}
</style>

View File

@@ -1,151 +0,0 @@
<template>
<mavon-editor ref='md' v-model="text" :subfield="true" :codeStyle="props.codeStyle" :ishljs="true"
:style="{ minHeight: props.height, maxHeight: '100%' }" class="edit" @imgAdd="imgAdd">
<!-- 引用视频链接的自定义按钮 -->
<template v-slot:left-toolbar-after>
<!--点击按钮触发的事件是打开表单对话框-->
<el-button @click="fileDialogVisible=true" aria-hidden="true" class="op-icon fa" title="上传文件">
<el-icon ><FolderChecked /></el-icon>
</el-button>
<el-dropdown :hide-on-click='false'>
<el-button aria-hidden="true" class="op-icon fa" title="表情包">
😊
</el-button>
<template #dropdown>
<el-dropdown-menu >
<el-dropdown-item>
<table border="1">
<tr>
<td @click="text+='😊'">😊</td>
<td>😊</td>
<td>😊</td>
</tr>
<tr>
<td>😊</td>
<td>😊</td>
<td>😊</td>
</tr>
<tr>
<td>😊</td>
<td>😊</td>
<td>😊</td>
</tr>
</table>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dialog
:modal=false
:draggable=true
v-model="fileDialogVisible"
title="文件上传"
width="30%"
:before-close="fileHandleClose"
>
<span>选择你的文件</span>
<el-upload
class="upload-demo"
drag
:action="fileUploadUrl"
multiple
:on-success="onSuccess"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
可将文件拖拽到这里 <em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
文件需小于100MB以内
</div>
</template>
</el-upload>
<p v-for="(item,i) in fileUrlList" :key="i">{{` ${i+1}: ${getUrl(item)}` }} <el-button></el-button></p>
<template #footer>
<span class="dialog-footer">
<el-button @click="fileDialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogVisible = false">
确认
</el-button>
</span>
</template>
</el-dialog>
</template>
</mavon-editor>
</template>
<script setup>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import { ref, computed, watch, onMounted } from 'vue';
import { upload } from '@/apis/fileApi'
const md = ref(null);
const props = defineProps(['height', 'modelValue', "codeStyle"])
const emit = defineEmits(['update:modelValue'])
const fileDialogVisible=ref(false)
//已经上传好的文件列表url
const fileUrlList=ref([])
const fileUploadUrl=`${import.meta.env.VITE_APP_BASEAPI}/file`
// //v-model传值出去
const text = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const getUrl= (str)=>{
return `${import.meta.env.VITE_APP_BASEAPI}/file/${str}`
}
//关闭文件上传弹窗
const fileHandleClose=()=>{
fileDialogVisible.value=false;
}
//文件上传成功后
const onSuccess=(response)=>{
fileUrlList.value.push(response.data[0].id)
}
//图片上传
const imgAdd = async (pos, $file) => {
// 第一步.将图片上传到服务器.
var formdata = new FormData();
formdata.append('file', $file);
const response = await upload(formdata)
const url = `${import.meta.env.VITE_APP_BASEAPI}/file/${response.data[0].id}/true`;
console.log(url)
md.value.$img2Url(pos, url);
}
</script>
<style scoped>
.edit {
width: 100%;
}
</style>

View File

@@ -1,49 +0,0 @@
<template>
<el-row>
<el-col
>
<el-card :body-style="{ padding: '0px' }" shadow="never">
<img
src=""
class="image"
/>
<div style="padding: 14px">
<span>{{props.name}}</span>
<div class="bottom">
<time class="remarks">{{ props.introduction }}</time>
<RouterLink :to="`/discuss/${props.id}`"> <el-button text class="button" type="primary">进入<el-icon><CaretRight /></el-icon></el-button> </RouterLink>
</div>
</div>
</el-card>
</el-col>
</el-row>
</template>
<script setup>
import { onMounted } from 'vue';
const props = defineProps(['name','introduction','id'])
</script>
<style>
.remarks {
font-size: 12px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.image {
width: 100%;
display: block;
}
</style>

View File

@@ -1,28 +0,0 @@
<template>
<el-scrollbar>
<div class="scrollbar-flex-content">
<p v-for="item in 50" :key="item" class="scrollbar-item">
推荐
</p>
</div>
</el-scrollbar>
</template>
<style scoped>
.scrollbar-flex-content {
display: flex;
}
.scrollbar-item {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 4rem;
height: 2.6rem;
margin: 0 0.2rem ;
text-align: center;
border-radius: 4px;
background-color:#FAFAFA;
font-size: 14px;
}
</style>

View File

@@ -1,67 +0,0 @@
<template>
<el-tree
:data="props.data==''?[]:props.data"
:props="defaultProps"
@node-click="handleNodeClick"
:expand-on-click-node="false"
node-key="id"
:default-expand-all='true'
:highlight-current="true"
:current-node-key="currentNodeKey"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{data.name}}</span>
<span>
<a style="color: #409EFF; margin-left: 8px" @click="$emit('create',node, data)" v-hasPer="['bbs:article:add']"
> + </a>
<a style="color: #409EFF; margin-left: 8px" @click="$emit('update',node, data)" v-hasPer="['bbs:article:edit']"
> 编辑 </a>
<a style="color: #f56c6c; margin-left: 8px" @click="$emit('remove',node, data)" v-hasPer="['bbs:article:remove']"
> 删除 </a>
</span>
</span>
</template>
</el-tree>
</template>
<script setup>
import { ref } from 'vue';
const props = defineProps(['data',"currentNodeKey"])
const emits= defineEmits(["handleNodeClick"])
const currentNodeKey=props.currentNodeKey;
//数据定义
//子文章数据
// const articleData =ref([]);
//树形子文章选项
const defaultProps = {
children: "children",
label: "name",
};
// //子文章初始化
// const loadArticleData=async()=>
// {
// articleData.value= await articleall(route.params.discussId);
// }
//点击事件
const handleNodeClick = (data) => {
emits('handleNodeClick',data)
};
</script>
<style scoped>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>

View File

@@ -1,129 +0,0 @@
<template>
<el-select
style="width: 600px;"
v-model="value"
multiple
filterable
remote
reserve-keyword
placeholder="请输入用户账号(可多选)"
remote-show-suffix
:remote-method="remoteMethod"
:loading="loading"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<script setup>
import { onMounted, ref,computed } from 'vue'
import {listUser} from '@/apis/userApi'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
//这个为可选择的列表,{value,label},value为用户idlabel为账号名称不可重复
const options = ref([])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const loading = ref(false)
onMounted( async()=>{
const response= await listUser({ids:value.value.join()});
const res=response.data.items;
//下拉列表
options.value = res
.map((item) => {
return { value: `${item.id}`, label: `用户:${item.userName}` }
})
})
const loadUser=async(query)=>{
const response= await listUser({userName:query});
const res=response.data.items;
//下拉列表
options.value = res
.map((item) => {
return { value: `${item.id}`, label: `用户:${item.userName}` }
})
}
const remoteMethod =async (query) => {
if (query) {
loading.value = true
await loadUser(query);
loading.value = false
} else {
options.value = []
}
}
const states = [
'Alabama',
'Alaska',
'Arizona',
'Arkansas',
'California',
'Colorado',
'Connecticut',
'Delaware',
'Florida',
'Georgia',
'Hawaii',
'Idaho',
'Illinois',
'Indiana',
'Iowa',
'Kansas',
'Kentucky',
'Louisiana',
'Maine',
'Maryland',
'Massachusetts',
'Michigan',
'Minnesota',
'Mississippi',
'Missouri',
'Montana',
'Nebraska',
'Nevada',
'New Hampshire',
'New Jersey',
'New Mexico',
'New York',
'North Carolina',
'North Dakota',
'Ohio',
'Oklahoma',
'Oregon',
'Pennsylvania',
'Rhode Island',
'South Carolina',
'South Dakota',
'Tennessee',
'Texas',
'Utah',
'Vermont',
'Virginia',
'Washington',
'West Virginia',
'Wisconsin',
'Wyoming',
]
</script>

View File

@@ -1,55 +0,0 @@
<template>
<div ref="VisitsLineChart"></div>
</template>
<script setup>
import * as echarts from 'echarts/core';
import { GridComponent } from 'echarts/components';
import { LineChart } from 'echarts/charts';
import { UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { ref ,onMounted} from 'vue';
import { getWeek } from '@/apis/accessApi.js'
echarts.use([GridComponent, LineChart, CanvasRenderer, UniversalTransition]);
const VisitsLineChart=ref(null);
onMounted(async()=>{
var myChart = echarts.init(VisitsLineChart.value, null, {
width: 320,
height: 230
});
var option;
const response=await getWeek();
var numberData=response.data.map(x=>x.number);
option = {
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
data: numberData,
type: 'line',
areaStyle: {}
}
]
};
option && myChart.setOption(option);
window.addEventListener('resize', function() {
myChart.resize();
});
})
</script>

View File

@@ -1,9 +0,0 @@
import hasRole from './permission/hasRole'
import hasPermi from './permission/hasPermi'
// import copyText from './common/copyText'
export default function directive(app){
app.directive('hasRole', hasRole)
app.directive('hasPer', hasPermi)
// app.directive('copyText', copyText)
}

View File

@@ -1,29 +0,0 @@
/**
* v-hasDiscussPermi 操作权限处理
*/
import useUserStore from '@/stores/user'
//传一个值一个主题id的创建者id判断当前主题是否为自己创建拥有*:*:*,直接跳过
export default {
mounted(el, binding, vnode) {
const { value } = binding
const all_permission = "*:*:*";
const permissions = useUserStore().permissions
const userId = useUserStore().id
if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value
const hasPermissions = permissions.some(permission => {
return all_permission === permission || permissionFlag==userId
})
if (!hasPermissions) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`请设置操作主题用户签值`)
}
}
}

View File

@@ -1,27 +0,0 @@
/**
* v-hasPermi 操作权限处理
* Copyright (c) 2019 ruoyi
*/
import useUserStore from '@/stores/user'
export default {
mounted(el, binding, vnode) {
const { value } = binding
const all_permission = "*:*:*";
const permissions = useUserStore().permissions
if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value
const hasPermissions = permissions.some(permission => {
return all_permission === permission || permissionFlag.includes(permission)
})
if (!hasPermissions) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`请设置操作权限标签值`)
}
}
}

View File

@@ -1,28 +0,0 @@
/**
* v-hasRole 角色权限处理
* Copyright (c) 2019 ruoyi
*/
import useUserStore from '@/stores/user'
export default {
mounted(el, binding, vnode) {
const { value } = binding
const super_admin = "admin";
const roles = useUserStore().roles
if (value && value instanceof Array && value.length > 0) {
const roleFlag = value
const hasRole = roles.some(role => {
return super_admin === role || roleFlag.includes(role)
})
if (!hasRole) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`请设置角色权限标签值"`)
}
}
}

View File

@@ -1,24 +0,0 @@
<template class="back-color">
<div class="content-main">
<RouterView />
</div>
</template>
<style scoped>
.body-main{
min-height: 10rem;
min-width: 10rem;
background-color:#F0F2F5;
}
.content-main{
margin: 0 auto;
display: flex;
justify-content: center;
min-height: 1150px;
}
</style>

View File

@@ -1,112 +0,0 @@
<template>
<el-menu
:default-active="activeIndex"
class="el-menu-demo"
mode="horizontal"
:ellipsis="false"
@select="handleSelect"
>
<el-menu-item class="logo" index="" @click="enterIndex" >
<img class="img-icon" style="width: 35px; height: 35px" src="@/assets/logo.ico" />{{configStore.name}}</el-menu-item>
<el-menu-item index="1" @click="enterIndex">主页</el-menu-item>
<el-sub-menu index="2">
<template #title>学习</template>
<el-menu-item index="2-1">学习 one</el-menu-item>
<el-menu-item index="2-2">学习 two</el-menu-item>
<el-menu-item index="2-3">学习 three</el-menu-item>
</el-sub-menu>
<el-sub-menu index="3">
<template #title>资源</template>
<el-menu-item index="3-1">资源 one</el-menu-item>
<el-menu-item index="3-2">资源 two</el-menu-item>
<el-menu-item index="3-3">资源 three</el-menu-item>
</el-sub-menu>
<el-sub-menu index="4">
<template #title>问答</template>
<el-menu-item index="4-1">问答 one</el-menu-item>
<el-menu-item index="4-2">问答 two</el-menu-item>
<el-menu-item index="4-3">问答 three</el-menu-item>
</el-sub-menu>
<div class="flex-grow" />
<el-menu-item index="5">
<div style="width: 350px;">
<el-input style="width: 300px;" v-model="searchText" placeholder="全站搜索" clearable prefix-icon="Search" />
<el-button type="primary" plain @click="search">搜索</el-button>
</div>
</el-menu-item>
<el-menu-item index="6" @click="enterProfile" >
<AvatarInfo :size='30' :isSelf="true" />
</el-menu-item>
<el-sub-menu index="6">
<template #title>个人中心</template>
<el-menu-item index="6-1" @click="enterProfile">进入个人中心</el-menu-item>
<el-menu-item index="6-2" @click="enterProfile">其他</el-menu-item>
<el-menu-item index="6-3" @click="logout">登出</el-menu-item>
</el-sub-menu>
</el-menu>
</template>
<script setup>
import AvatarInfo from '@/components/AvatarInfo.vue'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import useUserStore from '@/stores/user.js'
import useConfigStore from "@/stores/config";
const configStore= useConfigStore();
const router = useRouter()
const userStore =useUserStore();
const activeIndex = ref('1')
const searchText=ref('')
const handleSelect = (key, keyPath) => {
console.log(key, keyPath)
}
const logout= async()=>{
ElMessageBox.confirm(`确定登出系统吗?`, "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
}).then( async() => {
//异步
await userStore.logOut();
//删除成功后,跳转到主页
router.push("/login");
ElMessage({
type: "success",
message: "登出成功",
});
});
}
const enterIndex=()=>{
router.push("/index");
};
const enterProfile=()=>{
router.push("/profile");}
const search=()=>{
var routerPer = { path: `/discuss`,query:{q:searchText.value} };
searchText.value='';
router.push(routerPer)
}
</script>
<style scoped>
.logo{
min-width: 14rem;
font-size: large;
font-weight: 600;
}
.flex-grow {
flex-grow: 1;
}
.img-icon
{
margin-right: 0.5rem;
}
</style>

View File

@@ -1,27 +0,0 @@
<template>
<div class="common-layout">
<el-container>
<el-header>
<AppHeader />
</el-header>
<el-main>
<AppBody />
</el-main>
</el-container>
</div>
</template>
<script setup>
import AppHeader from "./AppHeader.vue"
import AppBody from "./AppBody.vue"
</script>
<style scoped>
.el-main{
margin: 0;
padding: 0;
min-height: 10rem;
background-color: #F0F2F5;
}
</style>

View File

@@ -1,8 +0,0 @@
<template>
<div class="box">
<div class="content">
<RouterView/>
</div>
</div>
</template>
<style src="@/assets/styles/login.scss" scoped></style>

View File

@@ -1,23 +0,0 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/main.css'
import '@/assets/styles/index.scss' // global css
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import directive from './directive' // directive
import "./permission"
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(createPinia())
directive(app);
app.use(router)
app.mount('#app')

View File

@@ -1,65 +0,0 @@
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'
NProgress.configure({ showSpinner: false });
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()
} else {
if (useUserStore().roles.length === 0)
{
isRelogin.show = true
// 判断当前用户是否已拉取完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: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})

View File

@@ -1,76 +0,0 @@
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 }
},
routes: [
{
name:'test',
path: '/test',
component: () => import('../views/Test.vue')
},
{
path: '/loginLayout',
name: 'loginLayout',
component: LoginLayout,
redirect: '/login' ,
children :[
{
name:'login',
path: '/login',
component: () => import('../views/Login.vue')
},
{
name:'register',
path: '/register',
component: () => import('../views/Register.vue')
},
]
},
{
path: '/',
name: 'layout',
component: Layout,
redirect: '/index' ,
children :[
{
name:'index',
path: '/index',
component: () => import('../views/Index.vue')
},
{
name:'article',
path: '/article/:discussId/:articleId?',
component: () => import('../views/Article.vue')
},
{
name:'discuss',
path: '/discuss/:plateId?',
component: () => import('../views/Discuss.vue')
},
{
//artTypediscuss主题、article文章
//operTypecreate创建、update更新
name:'editArt',
path:'/editArt',
component:()=>import('../views/EditArticle.vue')
},
{
name:'profile',
path:'/profile',
component:()=>import('../views/profile/Index.vue')
}
]
},
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
]
})
export default router

View File

@@ -1,22 +0,0 @@
import {getAll} from '@/apis/configApi'
import { defineStore } from 'pinia'
const useConfigStore = defineStore('config',
{
state: () => ({
data: []
}),
getters: {
name:(state)=>state.data.filter(s=> s.configKey=='bbs.site.name').map(x=>x.configValue)[0],
author:(state)=>state.data.filter(s=> s.configKey=='bbs.site.author').map(x=>x.configValue)[0],
icp:(state)=>state.data.filter(s=> s.configKey=='bbs.site.icp').map(x=>x.configValue)[0],
bottom:(state)=>state.data.filter(s=>s.configKey=='bbs.site.bottom').map(x=>x.configValue)[0]
},
actions: {
// 登录
async getConfig() {
const response = await getAll();
this.data = response.data.items;
},
},
})
export default useConfigStore;

View File

@@ -1,98 +0,0 @@
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;
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
// this.roles = ["admin"];
// this.permissions=["*:*:*"]
} else {
this.roles = ['ROLE_DEFAULT']
}
// this.roles = ["admin"];
// this.permissions=["*:*:*"]
this.name = user.nick
this.icon = avatar;
this.userName=user.userName;
this.id=user.id;
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 退出系统
logOut() {
return new Promise((resolve, reject) => {
logout().then(() => {
this.token = ''
this.roles = []
this.permissions = []
removeToken()
resolve()
}).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;

View File

@@ -1,13 +0,0 @@
const TokenKey = 'Admin-Token'
export function getToken() {
return localStorage.getItem(TokenKey)
}
export function setToken(token) {
return localStorage.setItem(TokenKey, token)
}
export function removeToken() {
return localStorage.removeItem (TokenKey)
}

View File

@@ -1,81 +0,0 @@
export default {
'agate': 1,
'androidstudio': 1,
'arduino-light': 1,
'arta': 1,
'ascetic': 1,
'atelier-cave-dark': 1,
'atelier-cave-light': 1,
'atelier-dune-dark': 1,
'atelier-dune-light': 1,
'atelier-estuary-dark': 1,
'atelier-estuary-light': 1,
'atelier-forest-dark': 1,
'atelier-forest-light': 1,
'atelier-heath-dark': 1,
'atelier-heath-light': 1,
'atelier-lakeside-dark': 1,
'atelier-lakeside-light': 1,
'atelier-plateau-dark': 1,
'atelier-plateau-light': 1,
'atelier-savanna-dark': 1,
'atelier-savanna-light': 1,
'atelier-seaside-dark': 1,
'atelier-seaside-light': 1,
'atelier-sulphurpool-dark': 1,
'atelier-sulphurpool-light': 1,
'atom-one-dark': 1,
'atom-one-light': 1,
'brown-paper': 1,
'codepen-embed': 1,
'color-brewer': 1,
'darcula': 1,
'dark': 1,
'darkula': 1,
'default': 1,
'docco': 1,
'dracula': 1,
'far': 1,
'foundation': 1,
'github-gist': 1,
'github': 1,
'googlecode': 1,
'grayscale': 1,
'gruvbox-dark': 1,
'gruvbox-light': 1,
'hopscotch': 1,
'hybrid': 1,
'idea': 1,
'ir-black': 1,
'kimbie.dark': 1,
'kimbie.light': 1,
'magula': 1,
'mono-blue': 1,
'monokai-sublime': 1,
'monokai': 1,
'obsidian': 1,
'ocean': 1,
'paraiso-dark': 1,
'paraiso-light': 1,
'pojoaque': 1,
'purebasic': 1,
'qtcreator_dark': 1,
'qtcreator_light': 1,
'railscasts': 1,
'rainbow': 1,
'routeros': 1,
'school-book': 1,
'solarized-dark': 1,
'solarized-light': 1,
'sunburst': 1,
'tomorrow-night-blue': 1,
'tomorrow-night-bright': 1,
'tomorrow-night-eighties': 1,
'tomorrow-night': 1,
'tomorrow': 1,
'vs': 1,
'vs2015': 1,
'xcode': 1,
'xt256': 1,
'zenburn': 1
};

View File

@@ -1,64 +0,0 @@
import axios from 'axios';
import { getToken } from '@/utils/auth'
export let isRelogin = { show: false };
// import JsonBig from 'json-bigint'
const myaxios = axios.create({
baseURL:import.meta.env.VITE_APP_BASEAPI,
timeout: 50000,
// transformResponse: [data => {
// try {
// const json = JsonBig({
// storeAsString: true
// })
// return json.parse(data)
// } catch (err) {
// // 如果转换失败,则包装为统一数据格式并返回
// return {
// data
// }
// }
// }],
})
// 请求拦截器
myaxios.interceptors.request.use(function (config) {
if (getToken()) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config;
}, function (error) {
return Promise.reject(error);
});
// 响应拦截器
myaxios.interceptors.response.use(function (response) {
//业务错误
if(response.data.statusCode==403)
{
ElMessage.error(response.data.errors)
}
return response.data;
}, function (error) {
const code = error.response.status;
const msg = error.message;
//业务异常+应用异常,统一处理
switch(code)
{
case 401:
ElMessage.error('登录已过期')
break;
case 403:
ElMessage.error(msg)
break;
case 500:
ElMessage.error(msg)
break;
}
return Promise.reject(error);
});
export default myaxios

View File

@@ -1,386 +0,0 @@
<template>
<div style="width: 90%; min-width: 1200px">
<!-- <div style="width: 1200px;"> -->
<el-row :gutter="20" class="top-div">
<el-col :span="5">
<el-row class="art-info-left">
<el-col :span="24">
<InfoCard header="文章信息" text="展开" hideDivider="true">
<template #content>
<el-button style="width: 100%; margin-bottom: 0.8rem" @click="loadDiscuss(true)">首页</el-button>
<el-button v-hasPer="['bbs:article:add']" @click="addArticle(0)" type="primary"
style="width: 100%; margin-bottom: 0.8rem; margin-left: 0">添加子文章</el-button>
<!--目录在这里 -->
<TreeArticleInfo :data="articleData" @remove="delArticle" @update="updateArticle" @create="addNextArticle"
@handleNodeClick="handleNodeClick" :currentNodeKey="currentNodeKey"/>
</template>
</InfoCard>
</el-col>
<el-col :span="24">
<InfoCard :items="items" header="作者分享" text="更多">
<template #item="temp">
<AvatarInfo />
</template>
</InfoCard>
</el-col>
<el-col :span="24">
<InfoCard :items="items" header="内容推荐" text="更多">
<template #item="temp">
<AvatarInfo />
</template>
</InfoCard>
</el-col>
</el-row>
</el-col>
<el-col :span="14">
<el-row class="left-div">
<el-col :span="24">
<!-- {{ discuss.user }} -->
<AvatarInfo :size="50" :showWatching="true" :time="discuss.creationTime" :userInfo="discuss.user"></AvatarInfo>
<!-- :userInfo="{nick:'qwe'} -->
<el-divider />
<h2>{{ discuss.title }}</h2>
<el-image :preview-src-list="[getUrl(discuss.cover)]" v-if="discuss.cover" :src="getUrl(discuss.cover)" style="width: 150px;height: 150px;" />
<ArticleContentInfo :code="discuss.content??''"></ArticleContentInfo>
<el-divider class="tab-divider" />
<el-space :size="10" :spacer="spacer">
<AgreeInfo :data="discuss"/>
<el-button icon="Star" text> 0</el-button>
<el-button icon="Share" text> 分享</el-button>
<el-button icon="Operation" text> 操作</el-button>
</el-space>
</el-col>
<el-col :span="24" class="comment">
<CommentInfo/>
</el-col>
</el-row>
<BottomInfo/>
</el-col>
<el-col :span="5">
<el-row class="right-div">
<el-col :span="24">
<InfoCard class="art-info-right" header="主题信息" text="更多" hideDivider="true">
<template #content>
<div>
<ul class="art-info-ul">
<li>
<el-button type="primary" size="default"
v-hasPer="['bbs:discuss:edit']"
@click="updateHander(route.params.discussId)">编辑</el-button>
<el-button style="margin-left: 1rem" type="danger"
v-hasPer="['bbs:discuss:remove']"
@click="delHander(route.params.discussId)">删除</el-button>
</li>
<li>分类: <span>主题</span></li>
标签:
<el-tag type="success">主题</el-tag>
<el-tag type="info">资源</el-tag>
</ul>
</div>
</template>
</InfoCard>
</el-col>
<el-col :span="24">
<InfoCard class="art-info-right" header="目录" hideDivider="true">
<template #content>
<div>
<el-empty :image-size="100" style="padding: 20px 0;" v-if="catalogueData.length==0" description="无目录" />
<ul v-else class="art-info-ul">
<li v-for="(item, i) in catalogueData" :key="i">
<el-button style="width: 100%; justify-content: left" type="primary" text>{{ `${i + 1} ${item}`
}}</el-button>
</li>
</ul>
</div>
</template>
</InfoCard>
</el-col>
<el-col :span="24">
<InfoCard :items="items" header="其他" text="更多">
<template #item="temp">
<AvatarInfo />
</template>
</InfoCard>
</el-col>
<el-col :span="24">
<InfoCard :items="items" header="其他" text="更多">
<template #item="temp">
<AvatarInfo />
</template>
</InfoCard>
</el-col>
</el-row>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { h, ref, onMounted } from "vue";
import AvatarInfo from "@/components/AvatarInfo.vue";
import InfoCard from "@/components/InfoCard.vue";
import ArticleContentInfo from "@/components/ArticleContentInfo.vue";
import CommentInfo from "@/components/CommentInfo.vue";
import BottomInfo from '@/components/BottomInfo.vue'
import TreeArticleInfo from "@/components/TreeArticleInfo.vue";
import { useRoute, useRouter } from "vue-router";
import AgreeInfo from '@/components/AgreeInfo.vue'
import { get as discussGet, del as discussDel } from "@/apis/discussApi.js";
import { all as articleall, del as articleDel, get as articleGet } from "@/apis/articleApi.js";
//数据定义
const route = useRoute();
const router = useRouter();
const spacer = h(ElDivider, { direction: "vertical" });
const items = [{ user: "用户1" }, { user: "用户2" }, { user: "用户3" }];
//子文章数据
const articleData = ref([]);
//主题内容
const discuss = ref({});
//封面url
const getUrl= (str)=>{
return `${import.meta.env.VITE_APP_BASEAPI}/file/${str}`
}
//当前默认选择的子文章
const currentNodeKey=route.params.articleId;
//目录数据
const catalogueData = ref([]);
//子文章初始化
const loadArticleData = async () => {
const response= await articleall(route.params.discussId)
articleData.value = response.data;
}
//主题初始化
const loadDiscuss = async (isRewrite) => {
if (isRewrite) {
//跳转路由
router.push(`/article/${route.params.discussId}`);
}
discuss.value = (await discussGet(route.params.discussId)).data;
if (route.params.articleId != "") {
const response = await articleGet(route.params.articleId);
discuss.value.content = response.data.content;
}
ContentHander();
};
//加载文章及目录
const ContentHander = () => {
//加载目录
var reg = /(#{1,6})\s(.*)/g;
if(discuss.value.content)
{
var myArray = discuss.value.content.match(reg);
if (myArray != null) {
catalogueData.value = myArray.map((x) => {
return x.replace(/#/g, "").replace(/\s/g, "");
});
}
}
}
//添加树型子文章
const addArticle = (parentArticleId) => {
//跳转路由
var routerPer = {
path: "/editArt",
query: {
operType: "create",
artType: "article",
discussId: route.params.discussId,
parentArticleId: parentArticleId,
},
};
router.push(routerPer);
};
//删除主题
const delHander = async (ids) => {
ElMessageBox.confirm(`确定是否删除编号[${ids}]的主题吗?`, "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
const response = await discussDel(ids);
//删除成功后,跳转到主页
router.push("/index");
ElMessage({
type: "success",
message: "删除成功",
});
});
};
//更新操作
const updateHander = (discussId) => {
//跳转路由
var routerPer = {
path: "/editArt",
query: {
operType: "update",
artType: "discuss",
discussId: discussId,
},
};
router.push(routerPer);
};
//跳转添加子菜单
const addNextArticle = (node, data) => {
//跳转路由
var routerPer = {
path: "/editArt",
query: {
operType: "create",
artType: "article",
discussId: data.discussId,
parentArticleId: data.id,
},
};
router.push(routerPer);
}
//跳转更新子菜单
const updateArticle = (node, data) => {
//跳转路由
var routerPer = {
path: "/editArt",
query: {
operType: "update",
artType: "article",
discussId: data.discussId,
parentArticleId: data.parentId,
articleId: data.id
},
};
router.push(routerPer);
}
//单机节点
const handleNodeClick = async(data) => {
//跳转路由
router.push(`/article/${route.params.discussId}/${data.id}`);
const response=await articleGet(data.id);
discuss.value.content = response.data.content;
ContentHander();
}
//删除子文章
const delArticle = (node, data) => {
ElMessageBox.confirm(`确定是否删除编号[${data.id}]的子文章吗?`, "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
await articleDel(data.id);
await loadArticleData();
ElMessage({
type: "success",
message: "删除成功",
});
});
}
onMounted(async () => {
await loadDiscuss();
await loadArticleData();
});
</script>
<style scoped >
.comment {
min-height: 40rem;
}
.art-info-left .el-col {
margin-bottom: 1rem;
}
.art-info-ul span {
margin-left: 1rem;
}
.art-info-ul .el-tag {
margin-left: 1rem;
}
.art-info-ul {
padding: 0;
margin: 0;
}
li {
list-style: none;
margin-bottom: 0.5rem;
}
.art-info-right {
height: 100%;
}
.left-div .el-col {
background-color: #ffffff;
min-height: 12rem;
margin-bottom: 1rem;
}
.right-div .el-col {
background-color: #ffffff;
min-height: 10rem;
margin-bottom: 1rem;
}
.left-top-div .el-col {
min-height: 2rem;
background-color: #fafafa;
margin-bottom: 1rem;
margin-left: 10px;
}
.top-div {
padding-top: 1rem;
}
.left-top-div {
font-size: small;
text-align: center;
line-height: 2rem;
}
h2 {
margin-bottom: 0.5em;
color: rgba(0, 0, 0, 0.85);
font-weight: 600;
font-size: 30px;
line-height: 1.35;
}
.left-div .el-col {
padding: 1.4rem 1.4rem 0.5rem 1.4rem;
}
.el-space {
display: flex;
vertical-align: top;
justify-content: space-evenly;
}
.tab-divider {
margin-bottom: 0.5rem;
}
</style>

View File

@@ -1,221 +0,0 @@
<template>
<div style="width: 1200px;" class="body-div">
<div class="header">
<el-form :inline="true" >
<el-form-item label="标题:" >
<el-input v-model="query.title" placeholder="请输入标题"
></el-input>
</el-form-item>
<el-form-item label="标签:">
<el-input
placeholder="搜索当下分类下的标签"
/>
</el-form-item>
<div class="form-right">
<el-button>重置</el-button>
<el-button type="primary" @click="async()=>{ await loadDiscussList();}">查询</el-button>
<el-button @click="enterEditArticle" type="primary" v-hasPer="['bbs:discuss:add']">分享</el-button>
<el-dropdown>
<span class="el-dropdown-link">
展开
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>Action 1</el-dropdown-item>
<el-dropdown-item>Action 2</el-dropdown-item>
<el-dropdown-item>Action 3</el-dropdown-item>
<el-dropdown-item disabled>Action 4</el-dropdown-item>
<el-dropdown-item divided>Action 5</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-form>
</div>
<el-tabs v-model="activeName" @tab-change="handleClick">
<el-tab-pane label="最新" name="new"> </el-tab-pane>
<el-tab-pane label="推荐" name="suggest"> </el-tab-pane>
<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" />
<div class="div-item" v-for="i in discussList" >
<DisscussCard :discuss="i"/>
</div>
<div>
<el-pagination
v-model:current-page="query.pageNum"
v-model:page-size="query.pageSize"
:page-sizes="[10, 20, 30, 50]"
:background="true"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="async(val)=>{ await loadDiscussList();}"
@current-change="async(val)=>{ await loadDiscussList();}"
/>
</div>
<el-empty v-if="discussList.length==0" description="空空如也" />
<BottomInfo/>
</div>
</template>
<script setup>
import DisscussCard from '@/components/DisscussCard.vue'
import {getList,getTopList} from '@/apis/discussApi.js'
import { onMounted, ref,reactive } from 'vue'
import { useRoute,useRouter } from 'vue-router'
import BottomInfo from '@/components/BottomInfo.vue'
//数据定义
const route=useRoute()
const router=useRouter()
const activeName = ref('new')
//主题内容
const discussList=ref([]);
//置顶主题内容
const topDiscussList = ref([]);
const total=ref(100)
const query=reactive({
pageNum:1,
pageSize:10,
title:'',
plateId:route.params.plateId,
type:activeName.value
})
const handleClick =async (tab, event) => {
query.type=activeName.value ;
await loadDiscussList();
}
onMounted(async()=>{
if(route.query.q !=undefined)
{
query.title=route.query.q??''
router.push("/discuss")
}
await loadDiscussList();
})
//加载discuss
const loadDiscussList=async()=>{
const response= await getList(query);
discussList.value=response.data.items;
total.value=Number( response.data.total);
//全查,无需参数
const topResponse=await getTopList();
topDiscussList.value=topResponse.data.items;
}
//进入添加主题页面
const enterEditArticle=()=>{
//跳转路由
var routerPer= { path: '/editArt', query: {
operType: 'create',
artType:'discuss',
plateId:route.params.plateId,
}}
router.push(routerPer);
}
</script>
<style scoped>
.el-pagination
{margin: 2rem 0rem 2rem 0rem;justify-content: right;}
.body-div{
min-height: 1000px;
}
.el-dropdown-link {
cursor: pointer;
color: var(--el-color-primary);
display: flex;
align-items: center;
}
.header{
background-color: #FFFFFF;
padding: 1rem;
margin: 1rem 0rem ;
}
.collapse-top
{
padding-left: 2rem;
}
.header .el-input
{
}
.el-tabs
{
background-color: #FFFFFF;
padding-left: 2rem;
}
.el-tabs >>> .el-tabs__header
{
margin-bottom: 0;
}
.div-item
{
margin-bottom: 1rem;
}
.el-form {
--el-form-label-font-size: var(--el-font-size-base);
display: flex;
align-items: center;
}
.el-form-item
{padding-top: 0.8rem;}
.form-right
{
align-items: center;
display: flex;
margin-left: auto;
}
.form-right .el-button
{
margin-right: 0.6rem;
}
.header .el-input
{
width:20rem;
}
.collapse-list >>> .el-collapse-item__header {
border-bottom-color: #F0F2F5 !important;
}
.el-divider
{
margin: 0.5rem 0;
}
</style>

View File

@@ -1,294 +0,0 @@
<template>
<div style="width: 100%">
<div class="body-div">
<el-form label-width="120px" :model="editForm" label-position="left" :rules="rules" ref="ruleFormRef">
<el-form-item label="类型:">
<el-radio-group v-model="radio">
<el-radio-button label="discuss">主题</el-radio-button>
<el-radio-button label="article">文章</el-radio-button>
<el-radio-button label="plate">板块</el-radio-button>
<el-radio-button label="orther">其他</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="权限:" v-if="route.query.artType == 'discuss'">
<el-radio-group v-model="perRadio">
<el-radio-button label="Public">公开</el-radio-button>
<el-radio-button label="Oneself">仅自己可见</el-radio-button>
<el-radio-button label="User">部分用户可见</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="可见用户:" v-if="route.query.artType == 'discuss' && perRadio=='User'">
<UserSelectInfo v-model="editForm.permissionUserIds"/>
</el-form-item>
<el-form-item v-if="route.query.artType == 'article'" label="子文章名称:" prop="name">
<el-input placeholder="请输入" v-model="editForm.name" />
</el-form-item>
<el-form-item v-else label="标题:" prop="title">
<el-input placeholder="请输入" v-model="editForm.title" />
</el-form-item>
<el-form-item label="描述:" prop="introduction">
<el-input placeholder="请输入" v-model="editForm.introduction" />
</el-form-item>
<el-form-item label="内容:" prop="content">
<MavonEdit height="30rem" v-model="editForm.content" :codeStyle="codeStyle" />
</el-form-item>
<el-form-item label="封面:" v-if="route.query.artType == 'discuss'">
<!-- 主题封面选择 -->
<el-upload
class="avatar-uploader"
:action="fileUploadUrl"
:show-file-list="false"
:on-success="onSuccess"
>
<el-image v-if="dialogImageUrl" :src="getUrl(dialogImageUrl)" style="width: 178px;height: 178px;" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="标签:" prop="types">
<el-input placeholder="请输入" v-model="editForm.types" />
</el-form-item>
<el-form-item>
<el-button @click="submit(ruleFormRef)" class="submit-btn" type="primary">提交</el-button></el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import MavonEdit from "@/components/MavonEdit.vue";
import UserSelectInfo from '@/components/UserSelectInfo.vue'
import { ref, reactive, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import {
add as discussAdd,
update as discussUpdate,
get as discussGet,
} from "@/apis/discussApi.js";
import {
add as articleAdd,
update as articleUpdate,
get as articleGet,
} from "@/apis/articleApi.js";
//数据定义
const route = useRoute();
const router = useRouter();
const perRadio=ref("Public");
const radio = ref(route.query.artType);
const codeStyle = "atom-one-dark";
//封面完整显示的url
const fileUploadUrl=`${import.meta.env.VITE_APP_BASEAPI}/file`
//封面的url
const dialogImageUrl = ref('')
//文件上传成功后
const onSuccess=(response)=>{
dialogImageUrl.value=response.data[0].id
}
//封面url
const getUrl= (str)=>{
return `${import.meta.env.VITE_APP_BASEAPI}/file/${str}`
}
//整个页面上的表单
const editForm = reactive({
title: "",
types: "",
introduction: "",
content: "",
name: "",
permissionUserIds:[]
});
//组装主题内容: 需要更新主题信息
const discuss = {
};
//组装文章内容:需要添加的文章信息
const article = {
};
//定义效验规则
const ruleFormRef = ref(null);
const rules = reactive({
title: [
{ required: true, message: "请输入标题", trigger: "blur" },
{ min: 3, max: 40, message: "长度 3 到 20", trigger: "blur" },
],
name: [
{ required: true, message: "请输入子文章名称", trigger: "blur" },
],
content: [
{ required: true, message: "请输入内容", trigger: "blur" },
{ min: 10, message: "长度 大于 10", trigger: "blur" },
],
});
//提交按钮,需要区分操作类型
const submit = async (formEl) => {
if (!formEl) return;
await formEl.validate(async (valid, fields) => {
if (valid) {
//dicuss主题处理
if (route.query.artType == "discuss") {
discuss.title = editForm.title;
discuss.types = editForm.types;
discuss.introduction = editForm.introduction;
discuss.content = editForm.content;
discuss.plateId = discuss.plateId ?? route.query.plateId
discuss.cover=dialogImageUrl.value;
discuss.permissionType=perRadio.value;
discuss.permissionUserIds=editForm.permissionUserIds;
//主题创建
if (route.query.operType == "create") {
const response = await discussAdd(discuss);
ElMessage({
message: `[${discuss.title}]主题创建成功!`,
type: 'success',
})
var routerPer = { path: `/article/${response.data.id}` };
router.push(routerPer);
}
//主题更新
else if (route.query.operType == "update") {
await discussUpdate(route.query.discussId, discuss);
ElMessage({
message: `[${discuss.title}]主题更新成功!`,
type: 'success',
})
var routerPer = { path: `/article/${route.query.discussId}` };
router.push(routerPer);
}
}
//artcle文章处理
else if (route.query.artType == "article") {
//组装文章内容:需要添加的文章信息
article.content = editForm.content;
article.name = editForm.name;
article.discussId = route.query.discussId;
article.parentId = route.query.parentArticleId
//文章创建
if (route.query.operType == "create") {
const response = await articleAdd(article);
ElMessage({
message: `[${article.name}]文章创建成功!`,
type: 'success',
})
var routerPer = { path: `/article/${route.query.discussId}/${response.data.id}` };
router.push(routerPer);
}
//文章更新
else if (route.query.operType == "update") {
await articleUpdate(route.query.articleId, article);
ElMessage({
message: `[${article.name}]文章更新成功!`,
type: 'success',
})
var routerPer = { path: `/article/${route.query.discussId}/${route.query.articleId}` };
router.push(routerPer);
}
}
//添加成功后跳转到该页面
// var routerPer = { path: `/discuss/${discuss.plateId}` };
// router.push(routerPer);
// ruleFormRef.value.resetFields();
// discuss.plateId = route.query.plateId;
}
});
};
onMounted(async () => {
//如果是更新操作,需要先查询
if (route.query.operType == "update") {
//更新主题
if (route.query.artType == "discuss") {
await loadDiscuss();
//更新文章
} else if (route.query.artType == "article") {
await loadArticle();
}
}
});
//加载主题
const loadDiscuss = async () => {
const response = await discussGet(route.query.discussId);
const res = response.data
editForm.content = res.content;
editForm.title = res.title;
editForm.types = res.types;
editForm.introduction = res.introduction;
discuss.plateId = res.plateId;
dialogImageUrl.value= res.cover;
perRadio.value=res.permissionType;
editForm.permissionUserIds=res.permissionUserIds;
};
//加载文章
const loadArticle = async () => {
const response = await articleGet(route.query.articleId);
const res = response.data
editForm.content = res.content;
editForm.name = res.name;
editForm.discussId = res.discussId;
};
</script>
<style scoped>
.submit-btn {
width: 40%;
}
.body-div {
min-height: 1000px;
background-color: #fff;
margin: 1.5rem;
padding: 1.5rem;
}
.avatar-uploader >>>.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader >>>.el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.el-upload
{
}
</style>

View File

@@ -1,177 +0,0 @@
<template >
<div style="width: 1200px;">
<el-row :gutter="20" class="top-div">
<el-col :span="17">
<div class="scrollbar">
<ScrollbarInfo />
</div>
<el-row class="left-div">
<el-col :span="8" v-for="i in plateList" class="plate"
:style="{ 'padding-left': i % 3 == 1 ? 0 : 0.2 + 'rem', 'padding-right': i % 3 == 0 ? 0 : 0.2 + 'rem' }">
<PlateCard :name="i.name" :introduction="i.introduction" :id="i.id" />
</el-col>
<el-col :span="24" v-for="i in discussList">
<DisscussCard :discuss="i" />
</el-col>
<el-col :span="24">
<el-empty v-show="discussList.length <= 0" description="推荐位置,空空如也" />
</el-col>
</el-row>
</el-col>
<el-col :span="7">
<el-row class="right-div">
<el-col :span="24">
<el-carousel trigger="click" height="150px">
<el-carousel-item v-for="item in bannerList">
<div class="carousel-font" :style="{ color: item.color }">{{ item.name }}</div>
<el-image style="width: 100%; height: 100%" :src="item.logo" fit="cover" />
</el-carousel-item>
</el-carousel>
</el-col>
<el-col :span="24" >
<InfoCard header="访问统计" class="VisitsLineChart" text="详情">
<template #content>
<VisitsLineChart />
</template>
</InfoCard>
</el-col>
<el-col :span="24">
<InfoCard header="简介" text="详情">
<template #content>
<div class="introduce">
没有什么能够阻挡人类对代码<span style="color: #1890ff;">优雅</span>的追求
</div>
</template>
</InfoCard>
</el-col>
<el-col :span="24">
<InfoCard :items=items header="本月排行" text="更多">
<template #item="temp">
<AvatarInfo>
<template #bottom>
本月积分290
</template>
</AvatarInfo>
</template>
</InfoCard>
</el-col>
<el-col :span="24">
<InfoCard :items=items header="推荐好友" text="更多">
<template #item="temp">
<AvatarInfo />
</template>
</InfoCard>
</el-col>
<el-col :span="24" style=" background: transparent;">
<BottomInfo />
</el-col>
</el-row>
</el-col>
</el-row>
</div>
</template>
<script setup>
import DisscussCard from '@/components/DisscussCard.vue'
import InfoCard from '@/components/InfoCard.vue'
import PlateCard from '@/components/PlateCard.vue'
import ScrollbarInfo from '@/components/ScrollbarInfo.vue'
import AvatarInfo from '@/components/AvatarInfo.vue'
import BottomInfo from '@/components/BottomInfo.vue'
import VisitsLineChart from '@/components/echars/VisitsLineChart.vue'
import { access } from '@/apis/accessApi.js'
import { getList } from '@/apis/plateApi.js'
import { getList as bannerGetList } from '@/apis/bannerApi.js'
import { getList as discussGetList } from '@/apis/discussApi.js'
import { onMounted, ref, reactive } from 'vue'
var plateList = ref([]);
var discussList = ref([]);
var bannerList = ref([]);
const items = [{ user: "用户1" }, { user: "用户2" }, { user: "用户3" }]
//主题查询参数
const query = reactive({
pageNum: 1,
pageSize: 10,
isTop: true
});
//初始化
onMounted(async () => {
await access();
plateList.value = (await getList()).data.items;
discussList.value = (await discussGetList(query)).data.items;
bannerList.value = (await bannerGetList()).data.items
});
</script>
<style scoped >
.introduce {
color: rgba(0, 0, 0, .45);
font-size: small;
}
.plate {
background: transparent !important;
}
.left-div .el-col {
background-color: #FFFFFF;
margin-bottom: 1rem;
}
.right-div .el-col {
background-color: #FFFFFF;
margin-bottom: 1rem;
}
.carousel-font {
position: absolute;
z-index: 1;
top: 10%;
left: 10%;
}
.top-div {
padding-top: 0.5rem;
}
.scrollbar {
display: block;
margin-bottom: 0.5rem;
}
.VisitsLineChart >>> .el-card__body
{
padding: 0.5rem;
}
</style>

View File

@@ -1,101 +0,0 @@
<template>
<div class="login-wrapper">
<h1>{{configStore.name}}-登录</h1>
<div class="login-form">
<el-form ref="loginFormRef" v-model="loginForm" :rules="rules" >
<div class="username form-item">
<el-form-item prop="userName">
<span>使用账号</span>
<input type="text" class="input-item" v-model="loginForm.userName">
</el-form-item>
</div>
<div class="password form-item">
<el-form-item prop="password">
<span>密码</span>
<input type="password" class="input-item" v-model="loginForm.password">
</el-form-item>
</div>
</el-form>
<RouterLink to="/register" > 没有账号前往注册</RouterLink>
<button class="login-btn" @click="login"> </button>
<button class="login-btn" @click="guestlogin">游客临时登录</button>
</div>
<div class="divider">
<span class="line"></span>
<span class="divider-text">其他方式登录</span>
<span class="line"></span>
</div>
<div class="other-login-wrapper">
<div class="other-login-item">
<img src="@/assets/login_images/QQ.png" alt="">
</div>
<div class="other-login-item">
<img src="@/assets/login_images/WeChat.png" alt="">
</div>
</div>
</div>
<!-- <h2> 登录-欢迎</h2>
<el-input v-model="loginForm.userName" placeholder="用户名" />
<el-input v-model="loginForm.password" placeholder="密码" show-password />
<el-button class="login-btn" type="primary" @click="login">登录</el-button>
<br>
<el-button class="login-btn" type="primary" @click="guestlogin">游客临时登录</el-button> -->
</template>
<script setup>
import { reactive } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import useUserStore from '@/stores/user.js'
import useConfigStore from "@/stores/config";
const configStore= useConfigStore();
const userStore = useUserStore();
const router = useRouter();
const route = useRoute();
const rules = reactive({
userName: [
{ required: true, message: '请输入账号名', trigger: 'blur' },
{ min: 3, message: '至少大于等于3位', trigger: 'blur' },
],
password:[
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 3, message: '至少大于等于6位', trigger: 'blur' },
]})
const loginForm = reactive({
userName: "cc",
password: "123456",
uuid: "",
code: ""
})
const guestlogin = async () => {
loginForm.userName = "guest";
loginForm.password = "123456"
await userStore.login(loginForm);
const redirect = route.query?.redirect ?? '/index'
router.push(redirect)
}
const login = async () => {
const response = await userStore.login(loginForm).catch((e) => {
loginForm.password = "";
});
if (response!=undefined) {
ElMessage({
message: `您好${loginForm.userName},登录成功!`,
type: 'success',
})
const redirect = route.query?.redirect ?? '/index'
router.push(redirect)
}
}
</script>
<style src="@/assets/styles/login.scss" scoped></style>

View File

@@ -1,4 +0,0 @@
<template>
404
</template>

View File

@@ -1,108 +0,0 @@
<template>
<div class="login-wrapper">
<h1>{{configStore.name}}-注册</h1>
<div class="login-form">
<div class="username form-item">
<span>登录账号</span>
<input type="text" class="input-item" v-model="registerForm.userName">
</div>
<div class="username form-item">
<span>手机号</span>
<input style="width: 70%;" type="text" class="input-item" v-model="registerForm.phone">
<button v-if="!isSendCaptcha" style="width: 30%;background-color: #C14949;" class="login-btn" @click="captcha" >验证码</button>
<button v-else style="width: 30%;background-color:#F0F2F5;" class="login-btn" >已发送</button>
</div>
<div class="username form-item" v-show="isSendCaptcha">
<span>手机短信验证码</span>
<input type="text" class="input-item" v-model="registerForm.code">
</div>
<div class="password form-item">
<span>密码</span>
<input type="password" class="input-item" v-model="registerForm.password">
</div>
<div class="password form-item">
<span>确认密码</span>
<input type="password" class="input-item" v-model="passwordConfirm">
</div>
<RouterLink to="/login" > 已有账号前往登录</RouterLink>
<button class="login-btn" @click="register">注册</button>
</div>
<div class="divider">
<span class="line"></span>
<span class="divider-text">其他方式注册</span>
<span class="line"></span>
</div>
<div class="other-login-wrapper">
<div class="other-login-item">
<img src="@/assets/login_images/QQ.png" alt="">
</div>
<div class="other-login-item">
<img src="@/assets/login_images/WeChat.png" alt="">
</div>
</div>
</div>
</template>
<script setup>
import { reactive ,ref} from 'vue';
import { useRouter, useRoute } from 'vue-router';
import {getCodePhone} from '@/apis/accountApi'
import useUserStore from '@/stores/user.js'
import useConfigStore from "@/stores/config";
const configStore= useConfigStore();
const userStore = useUserStore();
const router = useRouter();
const route = useRoute();
const passwordConfirm=ref('');
const registerForm = reactive({
userName: "",
password: "",
uuid: "",
code: "",
phone:""
})
const isSendCaptcha=ref(false)
//验证码
const captcha=async()=>{
isSendCaptcha.value=true;
const response= await getCodePhone(registerForm.phone);
ElMessage({
message: `已向${registerForm.phone}发送验证码,请注意查收`,
type: 'success',
})
}
const register = async () => {
if(registerForm.password!=passwordConfirm.value)
{
ElMessage.error('两次密码输入不一致')
return;
}
const response = await userStore.register(registerForm).catch((e) => {
registerForm.password="";
passwordConfirm.value="";
});
//成功
if (response!=undefined) {
ElMessage({
message: `恭喜!${registerForm.userName},注册成功!请登录!`,
type: 'success',
})
const redirect = route.query?.redirect ?? '/login'
router.push(redirect)
}
}
</script>
<style src="@/assets/styles/login.scss" scoped></style>

View File

@@ -1 +0,0 @@
<template></template>

View File

@@ -1,85 +0,0 @@
<template>
<div class="errPage-container">
<el-button icon="arrow-left" class="pan-back-btn" @click="back">
返回
</el-button>
<el-row>
<el-col :span="12">
<h1 class="text-jumbo text-ginormous">
401错误!
</h1>
<h2>您没有访问权限</h2>
<h6>对不起您没有访问权限请不要进行非法操作您可以返回主页面</h6>
<ul class="list-unstyled">
<li class="link-type">
<router-link to="/">
回首页
</router-link>
</li>
</ul>
</el-col>
<el-col :span="12">
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
</el-col>
</el-row>
</div>
</template>
<script setup>
import errImage from "@/assets/401_images/401.gif";
import { useRouter } from "vue-router";
let { proxy } = getCurrentInstance();
const router=useRouter();
const errGif = ref(errImage + "?" + +new Date());
function back() {
if (proxy.$route.query.noGoBack) {
router.push({ path: "/" });
} else {
router.go(-1);
}
}
</script>
<style lang="scss" scoped>
.errPage-container {
width: 800px;
max-width: 100%;
margin: 100px auto;
.pan-back-btn {
background: #008489;
color: #fff;
border: none !important;
}
.pan-gif {
margin: 0 auto;
display: block;
}
.pan-img {
display: block;
margin: 0 auto;
width: 100%;
}
.text-jumbo {
font-size: 60px;
font-weight: 700;
color: #484848;
}
.list-unstyled {
font-size: 14px;
li {
padding-bottom: 5px;
}
a {
color: #008489;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
</style>

View File

@@ -1,228 +0,0 @@
<template>
<div class="wscn-http404-container">
<div class="wscn-http404">
<div class="pic-404">
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
</div>
<div class="bullshit">
<div class="bullshit__oops">
404错误!
</div>
<div class="bullshit__headline">
{{ message }}
</div>
<div class="bullshit__info">
对不起您正在寻找的页面不存在尝试检查URL的错误然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容
</div>
<router-link to="/index" class="bullshit__return-home">
返回首页
</router-link>
</div>
</div>
</div>
</template>
<script setup>
import {computed} from 'vue'
let message = computed(() => {
return '找不到网页!'
})
</script>
<style lang="scss" scoped>
.wscn-http404-container{
transform: translate(-50%,-50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #222;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>

View File

@@ -1,124 +0,0 @@
<template>
<div class="app-container" style="width: 1400px;">
<el-row :gutter="20">
<el-col :span="6" :xs="24">
<el-card class="box-card">
<template v-slot:header>
<div class="clearfix">
<span>个人信息</span>
</div>
</template>
<div>
<div class="text-center">
<userAvatar :user="state.user" />
</div>
<ul class="list-group list-group-striped">
<li class="list-group-item">
<el-icon><User /></el-icon> 用户名称
<div class="pull-right">{{ state.user.userName }}</div>
</li>
<li class="list-group-item">
<el-icon><Phone /></el-icon> 手机号码
<div class="pull-right">{{ state.user.phone }}</div>
</li>
<li class="list-group-item">
<el-icon><Message /></el-icon> 用户邮箱
<div class="pull-right">{{ state.user.email }}</div>
</li>
<li class="list-group-item">
<el-icon><HelpFilled /></el-icon> 所属部门
<div class="pull-right" v-if="state.dept">{{ state.dept.deptName }}</div>
</li>
<li class="list-group-item">
<el-icon><Avatar /></el-icon> 所属角色
<div class="pull-right"><span v-for="role in state.roles" :key="role.id">{{ role.roleName }} /</span></div>
</li>
<li class="list-group-item">
<el-icon><Stopwatch /></el-icon> 创建日期
<div class="pull-right">{{ state.user.creationTime }}</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :span="18" :xs="24">
<el-card>
<template v-slot:header>
<div class="clearfix">
<span>基本资料</span>
</div>
</template>
<el-tabs v-model="activeTab">
<el-tab-pane label="基本资料" name="userinfo">
<userInfo :user="state.user" />
</el-tab-pane>
<el-tab-pane label="修改密码" name="resetPwd">
<resetPwd />
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="Profile">
import userAvatar from "./UserAvatar.vue";
import userInfo from "./UserInfo.vue";
import resetPwd from "./ResetPwd.vue";
import { getUserProfile } from "@/apis/userApi.js";
import { onMounted ,ref,reactive} from "vue";
const activeTab = ref("userinfo");
const state = reactive({
user: {},
dept: {},
roles: [],
roleGroup: {},
postGroup: {}
});
function getUser() {
getUserProfile().then(response => {
const res=response.data;
state.user = res.user;
state.dept=res.dept;
state.roles=res.roles;
state.roleGroup = res.roleGroup;
state.postGroup = res.postGroup;
});
};
onMounted(()=>{
getUser();
})
</script>
<style scoped>
.pull-right {
float: right !important;
}
.list-group-striped > .list-group-item {
border-left: 0;
border-right: 0;
border-radius: 0;
padding-left: 0;
padding-right: 0;
}
.list-group {
padding-left: 0px;
list-style: none;
}
.list-group-item {
border-bottom: 1px solid #e7eaec;
border-top: 1px solid #e7eaec;
margin-bottom: -1px;
padding: 11px 0px;
font-size: 13px;
}
.app-container {
padding: 20px;
}
</style>

View File

@@ -1,66 +0,0 @@
<template>
<el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { updateUserPwd } from "@/apis/userApi";
import { ref,reactive } from "vue";
const pwdRef=ref(null);
const user = reactive({
oldPassword: undefined,
newPassword: undefined,
confirmPassword: undefined
});
const equalToPassword = (rule, value, callback) => {
if (user.newPassword !== value) {
callback(new Error("两次输入的密码不一致"));
} else {
callback();
}
};
const rules = ref({
oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }],
newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }],
confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }]
});
/** 提交按钮 */
function submit() {
pwdRef.value.validate(async valid => {
if (valid) {
const response=await updateUserPwd(user.oldPassword, user.newPassword)
if(response.status==200)
{
ElMessage({
type: "success",
message: "更新密码成功",
});
}
}
});
};
/** 关闭按钮 */
function close() {
// proxy.$tab.closePage();
};
</script>

View File

@@ -1,187 +0,0 @@
<template>
<div class="user-info-head" @click="editCropper()"><img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
</div>
<el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-row>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<vue-cropper ref="cropper" :img="options.img" :info="true" :autoCrop="options.autoCrop"
:autoCropWidth="options.autoCropWidth" :autoCropHeight="options.autoCropHeight" :fixedBox="options.fixedBox"
@realTime="realTime" v-if="visible" />
</el-col>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<div class="avatar-upload-preview">
<img :src="options.previews.url" :style="options.previews.img" />
</div>
</el-col>
</el-row>
<br />
<el-row>
<el-col :lg="2" :md="2">
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
<el-button>
选择
<el-icon class="el-icon--right">
<Upload />
</el-icon>
</el-button>
</el-upload>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button icon="Plus" @click="changeScale(1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="Minus" @click="changeScale(-1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshRight" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
<el-button type="primary" @click="uploadImg()">上传</el-button>
</el-col>
</el-row>
</el-dialog>
</template>
<script setup>
import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
import { upload } from "@/apis/fileApi";
import { updateUserIcon } from "@/apis/userApi";
import useUserStore from '@/stores/user'
import { ref, reactive } from "vue";
const userStore = useUserStore()
const cropper = ref(null);
const open = ref(false);
const visible = ref(false);
const title = ref("修改头像");
//图片裁剪数据
const options = reactive({
img: userStore.icon, // 裁剪图片的地址
autoCrop: true, // 是否默认生成截图框
autoCropWidth: 200, // 默认生成截图框宽度
autoCropHeight: 200, // 默认生成截图框高度
fixedBox: true, // 固定截图框大小 不允许改变
previews: {} //预览数据
});
/** 编辑头像 */
function editCropper() {
open.value = true;
};
/** 打开弹出层结束时的回调 */
function modalOpened() {
visible.value = true;
};
/** 覆盖默认上传行为 */
function requestUpload() {
};
/** 向左旋转 */
function rotateLeft() {
cropper.value.rotateLeft();
};
/** 向右旋转 */
function rotateRight() {
cropper.value.rotateRight();
};
/** 图片缩放 */
function changeScale(num) {
num = num || 1;
cropper.value.changeScale(num);
};
/** 上传预处理 */
function beforeUpload(file) {
if (file.type.indexOf("image/") == -1) {
ElMessage.error("文件格式错误,请上传图片类型,如JPGPNG后缀的文件。")
} else {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
options.img = reader.result;
};
}
};
/** 上传图片 */
async function uploadImg() {
await cropper.value.getCropBlob(async data => {
let formData = new FormData();
formData.append("file", data);
const response = await upload(formData)
open.value = false;
options.img = import.meta.env.VITE_APP_BASEAPI + "/file/" + response.data[0].id;
userStore.icon = options.img;
const iconResponse= await updateUserIcon(response.data[0].id);
if(iconResponse.status==200)
{
ElMessage({
type: "success",
message: "头像更新成功",
});
}
});
};
/** 实时预览 */
function realTime(data) {
options.previews = data;
};
/** 关闭窗口 */
function closeDialog() {
options.img = userStore.icon;
options.visible = false;
};
</script>
<style lang='scss' scoped>
.user-info-head {
position: relative;
display: inline-block;
height: 120px;
}
.user-info-head:hover:after {
content: "+";
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
color: #eee;
background: rgba(0, 0, 0, 0.5);
font-size: 24px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
line-height: 110px;
border-radius: 50%;
}
.img-circle {
border-radius: 50%;
}
.img-lg {
width: 120px;
height: 120px;
}
.avatar-upload-preview {
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 200px;
height: 200px;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
}
</style>

View File

@@ -1,66 +0,0 @@
<template>
<el-form ref="userRef" :model="user" :rules="rules" label-width="80px">
<el-form-item label="账号" prop="userName">
<el-input v-model="user.userName" disabled />
</el-form-item>
<el-form-item label="用户昵称" prop="nick">
<el-input v-model="user.nick" maxlength="30" />
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="user.phone" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="user.email" maxlength="50" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="user.sex">
<el-radio :label="'Male'"></el-radio>
<el-radio :label="'Woman'"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { updateUserProfile } from "@/apis/userApi";
import useUserStore from "@/stores/user"
import { ref } from "vue";
const userStore = useUserStore();
const props = defineProps({
user: {
type: Object
}
});
const userRef = ref(null);
const rules = ref({
nick: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
email: [{ required: true, message: "邮箱地址不能为空", trigger: "blur" }, { type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phone: [{ required: true, message: "手机号码不能为空", trigger: "blur" }, { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }],
});
/** 提交按钮 */
function submit() {
userRef.value.validate(async valid => {
if (valid) {
const response = await updateUserProfile(props.user);
if (response.status == 200) {
ElMessage({
type: "success",
message: "用户信息修改成功",
});
}
}
});
};
/** 关闭按钮 */
function close() {
// proxy.$tab.closePage();
};
</script>

View File

@@ -1,45 +0,0 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
var CopyWebpackPlugin = require('copy-webpack-plugin');
/** @type {import('vite').UserConfig} */
export default defineConfig(({ command, mode })=>{
const env = loadEnv(mode, process.cwd(), '')
return {
// envDir: 'env',
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server:{
port:18001,
open:true,
proxy:{
[env.VITE_APP_BASEAPI]: {
target: env.VITE_APP_URL,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api-dev/, ''),
},
}
}
}
}
)

View File

View File

@@ -1,2 +0,0 @@
VITE_APP_BASEAPI="/api-dev"
VITE_APP_URL="http://localhost:19001/api"

View File

@@ -1 +0,0 @@
VITE_APP_BASEAPI="/prod-api"

View File

@@ -1,28 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

View File

@@ -1,29 +0,0 @@
# yi-bbs
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>意社区</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +0,0 @@
{
"name": "yi-bbs",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"axios": "^1.3.4",
"echarts": "^5.4.2",
"element-plus": "^2.2.32",
"highlight": "^0.2.4",
"i": "^0.3.7",
"marked": "^4.2.12",
"mavon-editor": "^3.0.0",
"nprogress": "^0.2.0",
"pinia": "^2.0.32",
"vue": "^3.2.47",
"vue-cropper": "1.0.3",
"vue-router": "^4.1.6",
"yarm": "^0.4.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"sass": "1.52.1",
"unplugin-auto-import": "^0.15.0",
"unplugin-vue-components": "^0.24.0",
"vite": "^4.1.3"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,28 +0,0 @@
<script setup>
</script>
<template>
<el-config-provider :locale="locale">
<RouterView />
</el-config-provider>
</template>
<script setup>
import useConfigStore from "@/stores/config";
import { ElConfigProvider } from 'element-plus'
import {onMounted } from "vue";
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
const locale= zhCn;
const configStore = useConfigStore();
//加载全局信息
onMounted(async()=>{
await configStore.getConfig();
})
</script>
<style scoped>
</style>

View File

@@ -1,17 +0,0 @@
import request from '@/utils/request'
// 触发访问
export function access() {
return request({
url: '/access-log',
method: 'post'
})
}
// 获取本周数据
export function getWeek() {
return request({
url: '/access-log/week',
method: 'get'
})
}

View File

@@ -1,79 +0,0 @@
import request from '@/utils/request'
// 登录方法
export function login(username, password, code, uuid) {
const data = {
username,
password,
code,
uuid
}
return request({
url: '/account/login',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 注册方法
export function register(userName,password,phone,code,uuid) {
const data = {
userName,
password,
phone,
code,
uuid
}
return request({
url: '/account/register',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 获取用户详细信息
export function getInfo() {
return request({
url: '/account',
method: 'get'
})
}
// 退出方法
export function logout() {
return request({
url: '/account/logout',
method: 'post'
})
}
// 获取验证码
export function getCodeImg() {
return request({
url: '/account/captcha-image',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
}
// 获取短信验证码
export function getCodePhone(phone) {
return request({
url: '/account/captcha-phone',
headers: {
isToken: false
},
method: 'post',
timeout: 20000,
data:{phone}
})
}

View File

@@ -1,11 +0,0 @@
import myaxios from '@/utils/request'
export function operate(discussId){
if(discussId==undefined)
{
return;
}
return myaxios({
url: `/agree/operate/${discussId}`,
method: 'post'
})
};

View File

@@ -1,41 +0,0 @@
import myaxios from '@/utils/request'
export function getList(data){
return myaxios({
url: '/article',
method: 'get',
params:data
})
};
export function get(id){
return myaxios({
url: `/article/${id}`,
method: 'get'
})
};
export function add(data){
return myaxios({
url: `/article`,
method: 'post',
data:data
})
};
export function update(id,data){
return myaxios({
url: `/article/${id}`,
method: 'put',
data:data
})
};
export function del(ids){
return myaxios({
url: `/article/${ids}`,
method: 'delete'
})
};
export function all(discussId)
{
return myaxios({
url: `/article/all/discuss-id/${discussId}`,
method: 'get'
})
}

View File

@@ -1,8 +0,0 @@
import myaxios from '@/utils/request'
export function getList(data){
return myaxios({
url: '/banner',
method: 'get',
params:data
})
};

View File

@@ -1,22 +0,0 @@
import myaxios from '@/utils/request'
export function getListByDiscussId(discussId,data){
return myaxios({
url: `/comment/discuss-id/${discussId}`,
method: 'get',
params:data
})
};
export function add(data){
return myaxios({
url: `/comment`,
method: 'post',
data:data
})
};
export function del(ids){
return myaxios({
url: `/comment/${ids}`,
method: 'delete'
})
};

Some files were not shown because too many files have changed in this diff Show More