Compare commits
77 Commits
dev_202510
...
07e8151736
| Author | SHA1 | Date | |
|---|---|---|---|
| 07e8151736 | |||
| ad5ca8c545 | |||
| 95e7b752e3 | |||
| 3ea2777c94 | |||
| 1db7b3f6a1 | |||
| 03c5226a6a | |||
| 7bd3d54d97 | |||
| 61371a4a76 | |||
| 6606438862 | |||
| c9332b85db | |||
| 74d19bb9d6 | |||
| 57f97a490a | |||
| 746e5051cf | |||
| f27b57ec8b | |||
| 5e0773a59f | |||
| 70ea5f4dd4 | |||
| 31ea86d90c | |||
| 73dda8b648 | |||
| 80a60e37af | |||
| 71885c8796 | |||
| bfbb94481f | |||
| 3dcf9ca3ef | |||
| 2b92f29a17 | |||
| 3dbfff5f41 | |||
| f04385a306 | |||
| 44f817eced | |||
| 0f2cc6a33c | |||
| 0d1127f25e | |||
| bb7b4035fc | |||
| c3b3ed3681 | |||
| 3fe027661b | |||
| 74034ed26a | |||
| ad98033faa | |||
| bd8f47d9ae | |||
| c975c205df | |||
| 3ead596483 | |||
| 882df50f75 | |||
| 13da68b90d | |||
| 5f8bc1af55 | |||
| 1391239193 | |||
| fe93d9fac0 | |||
| 6ed65f89b5 | |||
| 9ff5b98b05 | |||
| 461c390cf7 | |||
| 1b9c9e9b86 | |||
| be30f31990 | |||
| ac4d7d8176 | |||
| 54997d4e3d | |||
| 7bbb1be2a2 | |||
| 19312e7a6f | |||
| 727dd254f0 | |||
| 2942fdabfe | |||
| 5ea30d6f85 | |||
| e6ae7d60e3 | |||
| cca1847fbf | |||
| 10a317bed6 | |||
| a3bf673440 | |||
| 30c4ec612a | |||
| 486455af81 | |||
| e8dc76ef82 | |||
| 8aa6fc0abf | |||
| 7eda1a85eb | |||
| 6a124a5754 | |||
| 564e617802 | |||
| 3f09aa1238 | |||
| b6cdbba20a | |||
| bc77cd244b | |||
| 5306431417 | |||
| 53287f0a1d | |||
| efbe2a3def | |||
| a1cbb9fdf0 | |||
| a84e8b9fe6 | |||
| 98052c0ce7 | |||
| 3780b9d2ab | |||
| bd43d35074 | |||
| 085cd485ad | |||
| 9007c7de57 |
@@ -1,211 +0,0 @@
|
||||
# Gitea Actions 工作流配置说明
|
||||
|
||||
本项目配置了两个 Gitea Actions 工作流,用于自动化构建、测试和部署。
|
||||
|
||||
## 工作流列表
|
||||
|
||||
### 1. ci.yml - 持续集成
|
||||
**触发时机:**
|
||||
- 推送代码到 `main` 或 `master` 分支
|
||||
- 创建 Pull Request
|
||||
|
||||
**执行内容:**
|
||||
- 自动检出代码
|
||||
- 配置 Java 8 环境
|
||||
- 使用 Gradle 构建项目
|
||||
- 运行单元测试
|
||||
- 上传构建产物(JAR 包)
|
||||
- 上传测试报告
|
||||
|
||||
**查看结果:**
|
||||
构建完成后,可以在 Gitea 仓库的 "Actions" 标签页查看运行结果和下载构建产物。
|
||||
|
||||
---
|
||||
|
||||
### 2. deploy.yml - 自动部署
|
||||
**触发时机:**
|
||||
- 推送代码到 `main` 或 `master` 分支
|
||||
- 手动触发(在 Gitea Actions 页面点击 "Run workflow")
|
||||
|
||||
**执行内容:**
|
||||
- 构建项目
|
||||
- 通过 SCP 将 JAR 包上传到服务器
|
||||
- 通过 SSH 重启应用
|
||||
|
||||
**配置步骤:**
|
||||
|
||||
#### 第一步:在 Gitea 中配置 Secrets
|
||||
进入仓库 → Settings → Secrets,添加以下密钥:
|
||||
|
||||
| 密钥名称 | 说明 | 示例 |
|
||||
|---------|------|------|
|
||||
| `SERVER_HOST` | 服务器 IP 地址 | `120.24.204.180` |
|
||||
| `SERVER_PORT` | SSH 端口 | `22` |
|
||||
| `SERVER_USER` | SSH 用户名 | `root` 或 `ubuntu` |
|
||||
| `SERVER_SSH_KEY` | SSH 私钥内容 | 完整的私钥文件内容 |
|
||||
| `DEPLOY_PATH` | 部署目录路径 | `/opt/corewing` |
|
||||
|
||||
#### 第二步:生成 SSH 密钥对(如果还没有)
|
||||
```bash
|
||||
# 在本地生成密钥对
|
||||
ssh-keygen -t rsa -b 4096 -C "gitea-deploy" -f ~/.ssh/gitea_deploy
|
||||
|
||||
# 查看公钥
|
||||
cat ~/.ssh/gitea_deploy.pub
|
||||
|
||||
# 查看私钥(复制到 Gitea Secrets 中)
|
||||
cat ~/.ssh/gitea_deploy
|
||||
```
|
||||
|
||||
#### 第三步:配置服务器
|
||||
```bash
|
||||
# 1. SSH 登录到服务器
|
||||
ssh user@your-server
|
||||
|
||||
# 2. 创建部署目录
|
||||
sudo mkdir -p /opt/corewing
|
||||
sudo chown $USER:$USER /opt/corewing
|
||||
|
||||
# 3. 添加公钥到 authorized_keys
|
||||
echo "your-public-key-content" >> ~/.ssh/authorized_keys
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
|
||||
# 4. 安装 Java(如果未安装)
|
||||
sudo apt update
|
||||
sudo apt install openjdk-8-jdk -y
|
||||
java -version
|
||||
```
|
||||
|
||||
#### 第四步:测试部署
|
||||
推送代码到 main 分支,或在 Gitea Actions 页面手动触发 deploy 工作流。
|
||||
|
||||
---
|
||||
|
||||
## 启用 Gitea Actions
|
||||
|
||||
### Gitea 服务器端配置
|
||||
|
||||
如果你的 Gitea 实例还未启用 Actions,需要管理员配置:
|
||||
|
||||
1. **修改 Gitea 配置文件** (`app.ini`):
|
||||
```ini
|
||||
[actions]
|
||||
ENABLED = true
|
||||
DEFAULT_ACTIONS_URL = https://gitea.com
|
||||
```
|
||||
|
||||
2. **安装 Gitea Act Runner**:
|
||||
```bash
|
||||
# 下载 Act Runner
|
||||
wget https://dl.gitea.com/act_runner/0.2.6/act_runner-0.2.6-linux-amd64
|
||||
chmod +x act_runner-0.2.6-linux-amd64
|
||||
mv act_runner-0.2.6-linux-amd64 /usr/local/bin/act_runner
|
||||
|
||||
# 注册 Runner
|
||||
act_runner register --no-interactive --instance http://120.24.204.180:3000 --token YOUR_RUNNER_TOKEN
|
||||
|
||||
# 启动 Runner
|
||||
act_runner daemon
|
||||
```
|
||||
|
||||
3. **获取 Runner Token**:
|
||||
- 登录 Gitea 管理后台
|
||||
- 进入 Site Administration → Actions → Runners
|
||||
- 点击 "Create new Runner" 获取 Token
|
||||
|
||||
---
|
||||
|
||||
## 自定义工作流
|
||||
|
||||
### 修改触发条件
|
||||
可以根据需要修改工作流的触发条件:
|
||||
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ] # 监听多个分支
|
||||
tags:
|
||||
- 'v*' # 监听标签
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # 定时执行(每天午夜)
|
||||
```
|
||||
|
||||
### 添加环境变量
|
||||
在工作流中使用环境变量:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
JAVA_VERSION: '8'
|
||||
SPRING_PROFILES_ACTIVE: 'prod'
|
||||
|
||||
steps:
|
||||
- name: 运行应用
|
||||
run: java -jar -Dspring.profiles.active=${{ env.SPRING_PROFILES_ACTIVE }} app.jar
|
||||
```
|
||||
|
||||
### 添加通知
|
||||
可以添加构建成功/失败通知:
|
||||
|
||||
```yaml
|
||||
- name: 发送通知
|
||||
if: failure()
|
||||
run: |
|
||||
curl -X POST "https://your-notification-webhook" \
|
||||
-d "构建失败: ${{ github.repository }}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 工作流未触发?
|
||||
- 检查 Gitea Actions 是否启用
|
||||
- 检查 Act Runner 是否正常运行
|
||||
- 确认触发条件是否匹配
|
||||
|
||||
### 2. 构建失败?
|
||||
- 查看 Actions 日志获取详细错误信息
|
||||
- 检查 Java 版本是否正确
|
||||
- 确认依赖是否能正常下载
|
||||
|
||||
### 3. 部署失败?
|
||||
- 检查 Secrets 配置是否正确
|
||||
- 测试 SSH 连接是否正常
|
||||
- 检查服务器目录权限
|
||||
|
||||
### 4. 如何查看日志?
|
||||
- Gitea 仓库 → Actions 标签页
|
||||
- 点击具体的工作流运行记录
|
||||
- 展开各个步骤查看详细日志
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **分支策略**:
|
||||
- `main` 分支自动部署到生产环境
|
||||
- `develop` 分支自动部署到测试环境
|
||||
- Pull Request 只执行构建和测试
|
||||
|
||||
2. **安全性**:
|
||||
- 不要在代码中硬编码密码和密钥
|
||||
- 使用 Secrets 管理敏感信息
|
||||
- 定期轮换 SSH 密钥
|
||||
|
||||
3. **性能优化**:
|
||||
- 启用 Gradle 缓存加快构建速度
|
||||
- 使用 `build -x test` 跳过测试快速构建
|
||||
- 合理设置产物保留时间
|
||||
|
||||
4. **监控**:
|
||||
- 定期检查工作流运行状态
|
||||
- 设置构建失败通知
|
||||
- 保存构建日志便于问题排查
|
||||
|
||||
---
|
||||
|
||||
## 更多资源
|
||||
|
||||
- [Gitea Actions 官方文档](https://docs.gitea.io/en-us/actions/)
|
||||
- [GitHub Actions 语法参考](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions)(Gitea Actions 兼容 GitHub Actions 语法)
|
||||
- [Act Runner 项目](https://gitea.com/gitea/act_runner)
|
||||
@@ -1,55 +0,0 @@
|
||||
name: CI Build and Test
|
||||
|
||||
# 触发条件:推送到 main 分支或创建 Pull Request
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# 1. 检出代码
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# 2. 设置 Java 环境
|
||||
- name: 设置 Java 8
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
|
||||
# 3. 赋予 Gradle wrapper 执行权限
|
||||
- name: 赋予 Gradle wrapper 执行权限
|
||||
run: chmod +x gradlew
|
||||
|
||||
# 4. 构建项目(跳过测试)
|
||||
- name: 使用 Gradle 构建项目
|
||||
run: ./gradlew build -x test
|
||||
|
||||
# 5. 运行测试
|
||||
- name: 运行测试
|
||||
run: ./gradlew test
|
||||
|
||||
# 6. 上传构建产物(JAR 文件)
|
||||
- name: 上传 JAR 包
|
||||
uses: actions/upload-artifact@v3
|
||||
if: success()
|
||||
with:
|
||||
name: corewing-app
|
||||
path: build/libs/*.jar
|
||||
retention-days: 7
|
||||
|
||||
# 7. 上传测试报告
|
||||
- name: 上传测试报告
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-reports
|
||||
path: build/reports/tests/
|
||||
retention-days: 7
|
||||
@@ -1,80 +0,0 @@
|
||||
name: Deploy to Server
|
||||
|
||||
# 触发条件:手动触发或推送到 main 分支
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
workflow_dispatch: # 允许手动触发
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# 1. 检出代码
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# 2. 设置 Java 环境
|
||||
- name: 设置 Java 8
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
|
||||
# 3. 赋予 Gradle wrapper 执行权限
|
||||
- name: 赋予 Gradle wrapper 执行权限
|
||||
run: chmod +x gradlew
|
||||
|
||||
# 4. 构建项目
|
||||
- name: 构建项目
|
||||
run: ./gradlew build -x test
|
||||
|
||||
# 5. 打包 JAR 文件
|
||||
- name: 获取 JAR 文件名
|
||||
id: jar
|
||||
run: echo "jar_file=$(ls build/libs/*.jar | head -n 1)" >> $GITHUB_OUTPUT
|
||||
|
||||
# 6. 部署到服务器(使用 SCP)
|
||||
# 需要在 Gitea 仓库设置中配置以下 Secrets:
|
||||
# - SERVER_HOST: 服务器地址
|
||||
# - SERVER_PORT: SSH 端口(默认 22)
|
||||
# - SERVER_USER: SSH 用户名
|
||||
# - SERVER_SSH_KEY: SSH 私钥
|
||||
# - DEPLOY_PATH: 部署路径(如 /opt/corewing)
|
||||
- name: 部署到服务器
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
username: ${{ secrets.SERVER_USER }}
|
||||
key: ${{ secrets.SERVER_SSH_KEY }}
|
||||
port: ${{ secrets.SERVER_PORT }}
|
||||
source: "build/libs/*.jar"
|
||||
target: ${{ secrets.DEPLOY_PATH }}
|
||||
strip_components: 2
|
||||
|
||||
# 7. 重启应用(通过 SSH 执行命令)
|
||||
- name: 重启应用
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
username: ${{ secrets.SERVER_USER }}
|
||||
key: ${{ secrets.SERVER_SSH_KEY }}
|
||||
port: ${{ secrets.SERVER_PORT }}
|
||||
script: |
|
||||
cd ${{ secrets.DEPLOY_PATH }}
|
||||
# 停止旧进程
|
||||
pkill -f corewing || true
|
||||
# 等待进程完全停止
|
||||
sleep 3
|
||||
# 启动新进程
|
||||
nohup java -jar *.jar > app.log 2>&1 &
|
||||
# 检查启动状态
|
||||
sleep 5
|
||||
if pgrep -f corewing > /dev/null; then
|
||||
echo "应用启动成功"
|
||||
else
|
||||
echo "应用启动失败"
|
||||
exit 1
|
||||
fi
|
||||
@@ -36,11 +36,14 @@ dependencies {
|
||||
implementation 'cn.dev33:sa-token-spring-boot-starter:1.44.0' // 权限认证
|
||||
implementation 'com.alibaba:druid-spring-boot-starter:1.2.27' // 数据库连接池
|
||||
implementation 'org.lionsoul:ip2region:2.7.0' // IP 归属地
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' // mongodb
|
||||
compileOnly 'org.projectlombok:lombok' // Lombok
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools' // 热重载
|
||||
runtimeOnly 'com.mysql:mysql-connector-j' // MySQL 驱动
|
||||
annotationProcessor 'org.projectlombok:lombok' // Lombok 注解处理
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test' // 测试框架
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // thymeleaf模版引擎
|
||||
implementation 'com.aliyun.oss:aliyun-sdk-oss:3.17.4' // OSS SDK
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
|
||||
@@ -54,6 +54,13 @@ public class Result<T> implements Serializable {
|
||||
return new Result<>(200, I18nUtil.getMessage("common.success"), data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> success(String message) {
|
||||
return new Result<>(200, message, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回(自定义消息和数据)
|
||||
*/
|
||||
@@ -81,4 +88,27 @@ public class Result<T> implements Serializable {
|
||||
public static <T> Result<T> error(Integer code, String message) {
|
||||
return new Result<>(code, message, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态返回信息结果
|
||||
*/
|
||||
public static <T> Result<T> isBool(boolean flag) {
|
||||
return flag ? Result.success() : Result.error();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态返回信息结果(自定义成功消息)
|
||||
*/
|
||||
public static <T> Result<T> isBoolAsMsg(boolean flag, String successMsg) {
|
||||
return flag ? Result.success(successMsg) : Result.error();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态返回信息结果(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> isBoolAsMsg(boolean flag, String successMsg, String errorMsg) {
|
||||
return flag ? Result.success(successMsg) : Result.error(errorMsg);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
37
src/main/java/com/corewing/app/common/base/BaseEntity.java
Normal file
37
src/main/java/com/corewing/app/common/base/BaseEntity.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.corewing.app.common.base;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class BaseEntity {
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createBy;
|
||||
/**
|
||||
* 修改时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
private String updateBy;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
23
src/main/java/com/corewing/app/common/page/PageContext.java
Normal file
23
src/main/java/com/corewing/app/common/page/PageContext.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.corewing.app.common.page;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
||||
public class PageContext {
|
||||
private static final ThreadLocal<Page<?>> PAGE_HOLDER = new ThreadLocal<>();
|
||||
|
||||
// 设置分页对象
|
||||
public static void setPage(Page<?> page) {
|
||||
PAGE_HOLDER.set(page);
|
||||
}
|
||||
|
||||
// 泛型方法:获取指定类型的分页对象(消除警告)
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Page<T> getPage(Class<T> clazz) {
|
||||
return (Page<T>) PAGE_HOLDER.get();
|
||||
}
|
||||
|
||||
// 清除线程变量
|
||||
public static void clear() {
|
||||
PAGE_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.corewing.app.common.page;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class PageInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 默认分页参数
|
||||
int pageNum = 1;
|
||||
int pageSize = 10;
|
||||
|
||||
// 从请求参数中获取分页信息
|
||||
String current = request.getParameter("current");
|
||||
String size = request.getParameter("size");
|
||||
|
||||
// 解析页码
|
||||
if (current != null && !current.isEmpty()) {
|
||||
try {
|
||||
pageNum = Integer.parseInt(current);
|
||||
if (pageNum < 1) pageNum = 1; // 页码不能小于1
|
||||
} catch (NumberFormatException e) {
|
||||
// 非法参数使用默认值
|
||||
}
|
||||
}
|
||||
|
||||
// 解析每页条数
|
||||
if (size != null && !size.isEmpty()) {
|
||||
try {
|
||||
pageSize = Integer.parseInt(size);
|
||||
if (pageSize < 1) pageSize = 10; // 每页条数不能小于1
|
||||
if (pageSize > 100) pageSize = 100; // 限制最大条数
|
||||
} catch (NumberFormatException e) {
|
||||
// 非法参数使用默认值
|
||||
}
|
||||
}
|
||||
|
||||
// 创建 MyBatis-Plus 的 Page 对象并存储到 ThreadLocal
|
||||
Page<?> page = new Page<>(pageNum, pageSize);
|
||||
PageContext.setPage(page);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
// 清除ThreadLocal中的数据,防止内存泄漏
|
||||
PageContext.clear();
|
||||
}
|
||||
}
|
||||
39
src/main/java/com/corewing/app/config/CorsConfig.java
Normal file
39
src/main/java/com/corewing/app/config/CorsConfig.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package com.corewing.app.config;
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration // 标记为配置类
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 配置跨域过滤器,优先级高于所有拦截器
|
||||
*/
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
// 1. 配置跨域参数
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
// 允许的前端域名(根据实际环境修改)
|
||||
config.addAllowedOriginPattern("*");
|
||||
// 允许携带 Cookie(前后端都需要开启)
|
||||
config.setAllowCredentials(true);
|
||||
// 允许的请求方法(包含预检请求 OPTIONS)
|
||||
config.addAllowedMethod("*");
|
||||
// 允许的请求头(* 表示所有)
|
||||
config.addAllowedHeader("*");
|
||||
// 预检请求缓存时间(1小时,减少重复验证)
|
||||
config.setMaxAge(3600L);
|
||||
|
||||
// 2. 配置路径匹配规则(对所有接口生效)
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
|
||||
// 3. 返回过滤器(优先级最高)
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.corewing.app.config;
|
||||
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.corewing.app.common.page.PageInterceptor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
@@ -17,6 +18,8 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 分页参数拦截
|
||||
registry.addInterceptor(new PageInterceptor());
|
||||
// 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
|
||||
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
|
||||
// 拦截所有路由
|
||||
@@ -29,15 +32,21 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
||||
.excludePathPatterns("/feedback", "/feedback/**")
|
||||
// 排除教程接口(支持匿名查询)
|
||||
.excludePathPatterns("/tutorial", "/tutorial/**")
|
||||
// 排除隐私政策接口(支持匿名查询)
|
||||
.excludePathPatterns("/privacy_policy", "/privacy_policy/**")
|
||||
// 排除固件查询接口(不需要登录)
|
||||
.excludePathPatterns("/firmware/**")
|
||||
// 排除系统登录页(不需要登录)
|
||||
.excludePathPatterns("/loading.html", "/admin/login.html")
|
||||
// 排除静态资源
|
||||
.excludePathPatterns("/", "/index.html", "/admin/login.html", "/*.css", "/*.js", "/*.ico", "/static/**")
|
||||
.excludePathPatterns("/", "/*.css", "/*.js", "/*.ico", "/static/**", "/assets/**")
|
||||
// 排除后台管理静态资源
|
||||
.excludePathPatterns("/admin/**")
|
||||
// 排除 Druid 监控
|
||||
.excludePathPatterns("/druid/**")
|
||||
// 排除错误页面
|
||||
.excludePathPatterns("/error", "/error/**");
|
||||
.excludePathPatterns("/error", "/error/**")
|
||||
// 排除咨询接口
|
||||
.excludePathPatterns("/contactMsg", "/contactMsg/**");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.corewing.app.dto;
|
||||
package com.corewing.app.dto.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.corewing.app.dto;
|
||||
package com.corewing.app.dto.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.corewing.app.dto;
|
||||
package com.corewing.app.dto.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.corewing.app.dto;
|
||||
package com.corewing.app.dto.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.corewing.app.dto;
|
||||
package com.corewing.app.dto.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.corewing.app.dto;
|
||||
package com.corewing.app.dto.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.corewing.app.dto.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TutorialListRequest {
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.corewing.app.dto;
|
||||
package com.corewing.app.dto.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.corewing.app.dto;
|
||||
package com.corewing.app.dto.api;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.corewing.app.dto.biz.feedback;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class FeedbackBatchDeleteRequest {
|
||||
|
||||
private List<Long> ids;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.corewing.app.dto.biz.feedback;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class FeedbackBatchStatusRequest {
|
||||
|
||||
private List<Long> ids;
|
||||
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.corewing.app.dto.biz.firmware;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class FirmwareBatchDeleteRequest {
|
||||
private List<Long> ids;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.corewing.app.dto.biz.privacyPolicy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class PrivacyPolicyBatchDeleteRequest {
|
||||
|
||||
private List<Long> ids;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.corewing.app.dto.biz.tutorial;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BatchTutorialDeleteRequest {
|
||||
private List<Long> ids;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.corewing.app.dto.biz.tutorial;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class CategoryBatchDeleteRequest {
|
||||
private List<Long> ids;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.corewing.app.dto.biz.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BizUserBatchDeleteRequest {
|
||||
|
||||
private List<Long> ids;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.corewing.app.dto.biz.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BizUserIdRequest {
|
||||
private long id;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.corewing.app.dto.biz.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BizUserStatusRequest {
|
||||
private List<Long> ids;
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.corewing.app.dto.biz.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ResetPasswordRequest {
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Integer userId;
|
||||
|
||||
/**
|
||||
* 新密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.corewing.app.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SysResetPasswordRequest {
|
||||
private Long userId;
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.corewing.app.dto.sys;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SysUserIdRequest {
|
||||
private Long id;
|
||||
}
|
||||
52
src/main/java/com/corewing/app/entity/ContactMsg.java
Normal file
52
src/main/java/com/corewing/app/entity/ContactMsg.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package com.corewing.app.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.corewing.app.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
/**
|
||||
* 联系消息
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@TableName("app_contact_msg")
|
||||
public class ContactMsg extends BaseEntity {
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 咨询人
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 咨询类型
|
||||
*/
|
||||
private int category;
|
||||
|
||||
/**
|
||||
* 联系方式
|
||||
*/
|
||||
private String contact;
|
||||
|
||||
/**
|
||||
* 咨询内容
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 咨询状态
|
||||
*/
|
||||
private int status;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -66,4 +66,34 @@ public class Feedback implements Serializable {
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String username;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String statusName;
|
||||
|
||||
public String getStatusName() {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return "待处理";
|
||||
case 1:
|
||||
return "处理中";
|
||||
case 2:
|
||||
return "已完成";
|
||||
case 3:
|
||||
return "已关闭";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,4 +61,20 @@ public class Firmware implements Serializable {
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String firmwareTypeName;
|
||||
|
||||
public String getFirmwareTypeName() {
|
||||
switch (firmwareType) {
|
||||
case 1:
|
||||
return "调参固件";
|
||||
case 2:
|
||||
return "AP固件";
|
||||
case 3:
|
||||
return "INAV固件";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/main/java/com/corewing/app/entity/PrivacyPolicy.java
Normal file
43
src/main/java/com/corewing/app/entity/PrivacyPolicy.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.corewing.app.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.corewing.app.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 隐私政策实体
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@TableName("app_privacy_policy")
|
||||
public class PrivacyPolicy extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 隐私政策id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 隐私政策名称
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 隐私政策内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 是否显示
|
||||
*/
|
||||
private Integer visible;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
}
|
||||
57
src/main/java/com/corewing/app/entity/SysMenu.java
Normal file
57
src/main/java/com/corewing/app/entity/SysMenu.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package com.corewing.app.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.corewing.app.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@TableName("sys_menu")
|
||||
public class SysMenu extends BaseEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 菜单id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 菜单名称
|
||||
*/
|
||||
private String menuName;
|
||||
|
||||
/**
|
||||
* 菜单地址
|
||||
*/
|
||||
private String menuUrl;
|
||||
|
||||
/**
|
||||
* 菜单图标
|
||||
*/
|
||||
private String menuIcon;
|
||||
|
||||
/**
|
||||
* 菜单分类
|
||||
*/
|
||||
private String menuCategory;
|
||||
|
||||
/**
|
||||
* 是否隐藏
|
||||
*/
|
||||
private boolean visible;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private int sort;
|
||||
|
||||
|
||||
|
||||
}
|
||||
31
src/main/java/com/corewing/app/entity/SysOption.java
Normal file
31
src/main/java/com/corewing/app/entity/SysOption.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.corewing.app.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.corewing.app.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@TableName("sys_option")
|
||||
public class SysOption extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 键名
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 键值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package com.corewing.app.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
@@ -62,12 +60,23 @@ public class Tutorial implements Serializable {
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 教程分类id
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Long categoryId;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String categoryName;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.corewing.app.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教程分类
|
||||
@@ -65,11 +65,19 @@ public class TutorialCategory implements Serializable {
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public static final String typeCategory = "category";
|
||||
public static final String typeTag = "tag";
|
||||
|
||||
@TableField(exist = false)
|
||||
private List<Tutorial> tutorials;
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ public class User implements Serializable {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
|
||||
10
src/main/java/com/corewing/app/mapper/ContactMsgMapper.java
Normal file
10
src/main/java/com/corewing/app/mapper/ContactMsgMapper.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.corewing.app.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.corewing.app.entity.ContactMsg;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ContactMsgMapper extends BaseMapper<ContactMsg> {
|
||||
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.corewing.app.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.entity.Feedback;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 问题反馈 Mapper 接口
|
||||
@@ -10,4 +12,5 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface FeedbackMapper extends BaseMapper<Feedback> {
|
||||
|
||||
Page<Feedback> page(Page<Feedback> page, @Param("feedback") Feedback feedback);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.corewing.app.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.corewing.app.entity.PrivacyPolicy;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PrivacyPolicyMapper extends BaseMapper<PrivacyPolicy> {
|
||||
|
||||
}
|
||||
9
src/main/java/com/corewing/app/mapper/SysMenuMapper.java
Normal file
9
src/main/java/com/corewing/app/mapper/SysMenuMapper.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package com.corewing.app.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.corewing.app.entity.SysMenu;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SysMenuMapper extends BaseMapper<SysMenu> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.corewing.app.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.corewing.app.entity.SysOption;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SysOptionMapper extends BaseMapper<SysOption> {
|
||||
}
|
||||
@@ -13,5 +13,7 @@ import org.apache.ibatis.annotations.Param;
|
||||
@Mapper
|
||||
public interface TutorialMapper extends BaseMapper<Tutorial> {
|
||||
|
||||
Page<Tutorial> pageList(Page<Tutorial> page, @Param("categoryId") int categoryId, @Param("tutorialTitle") String tutorialTitle, @Param("lang") String lang);
|
||||
Page<Tutorial> pageList(Page<Tutorial> page, @Param("categoryId") Long categoryId, @Param("tutorialTitle") String tutorialTitle, @Param("lang") String lang);
|
||||
|
||||
Page<Tutorial> page(Page<Tutorial> page, @Param("tutorial") Tutorial tutorial);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.corewing.app.modules.admin.biz;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.biz.feedback.FeedbackBatchDeleteRequest;
|
||||
import com.corewing.app.dto.biz.feedback.FeedbackBatchStatusRequest;
|
||||
import com.corewing.app.entity.Feedback;
|
||||
import com.corewing.app.service.FeedbackService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户反馈
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/biz/feedback")
|
||||
public class BizFeedBackController {
|
||||
|
||||
@Resource
|
||||
private FeedbackService feedbackService;
|
||||
|
||||
/**
|
||||
* 反馈管理首页
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/index")
|
||||
public String index() {
|
||||
return "admin/biz/feedback/index";
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询反馈数据分页
|
||||
* @param feedback
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@ResponseBody
|
||||
public Result<Page<Feedback>> page(Feedback feedback) {
|
||||
return Result.success(feedbackService.page(feedback));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增反馈
|
||||
* @param feedback
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/save")
|
||||
@ResponseBody
|
||||
public Result<String> save(@RequestBody Feedback feedback) {
|
||||
return Result.isBool(feedbackService.save(feedback));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑反馈
|
||||
* @param feedback
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
@ResponseBody
|
||||
public Result<String> update(@RequestBody Feedback feedback) {
|
||||
return Result.isBool(feedbackService.updateById(feedback));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除反馈
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping("/delete")
|
||||
@ResponseBody
|
||||
public Result<String> delete(Long id) {
|
||||
return Result.isBool(feedbackService.removeById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除反馈
|
||||
* @param feedbackBatchDeleteRequest
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/batchDelete")
|
||||
@ResponseBody
|
||||
public Result<String> batchDelete(@RequestBody FeedbackBatchDeleteRequest feedbackBatchDeleteRequest) {
|
||||
return Result.isBool(feedbackService.removeBatchByIds(feedbackBatchDeleteRequest.getIds()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更改状态
|
||||
* @param feedbackBatchStatusRequest
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/batchStatus")
|
||||
@ResponseBody
|
||||
public Result<String> batchStatus(@RequestBody FeedbackBatchStatusRequest feedbackBatchStatusRequest) {
|
||||
return Result.isBool(feedbackService.batchStatus(feedbackBatchStatusRequest));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.corewing.app.modules.admin.biz;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.biz.firmware.FirmwareBatchDeleteRequest;
|
||||
import com.corewing.app.entity.Firmware;
|
||||
import com.corewing.app.service.FirmwareService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 固件管理
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/biz/firmware")
|
||||
public class BizFirmwareController {
|
||||
|
||||
@Resource
|
||||
private FirmwareService firmwareService;
|
||||
|
||||
|
||||
/**
|
||||
* 固件管理首页
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/index")
|
||||
public String index() {
|
||||
return "admin/biz/firmware/index";
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param firmware
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@ResponseBody
|
||||
public Result<Page<Firmware>> page(Firmware firmware) {
|
||||
return Result.success(firmwareService.page(firmware));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存
|
||||
* @param firmware
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/save")
|
||||
@ResponseBody
|
||||
public Result<String> save(@RequestBody Firmware firmware) {
|
||||
return Result.isBool(firmwareService.save(firmware));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param firmware
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
@ResponseBody
|
||||
public Result<String> update(@RequestBody Firmware firmware) {
|
||||
return Result.isBool(firmwareService.updateById(firmware));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping("/delete")
|
||||
@ResponseBody
|
||||
public Result<Firmware> delete(String id) {
|
||||
return Result.isBool(firmwareService.removeById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param firmwareBatchDeleteRequest
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/batchDelete")
|
||||
@ResponseBody
|
||||
public Result<Firmware> batchDelete(@RequestBody FirmwareBatchDeleteRequest firmwareBatchDeleteRequest) {
|
||||
return Result.isBool(firmwareService.removeBatchByIds(firmwareBatchDeleteRequest.getIds()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传固件
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/uploadFile")
|
||||
@ResponseBody
|
||||
public Result<String> uploadFile(MultipartFile file, Long id) {
|
||||
return Result.isBool(firmwareService.uploadFile(file, id));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.corewing.app.modules.admin.biz;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.biz.privacyPolicy.PrivacyPolicyBatchDeleteRequest;
|
||||
import com.corewing.app.entity.PrivacyPolicy;
|
||||
import com.corewing.app.service.PrivacyPolicyService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.env.PropertyResolver;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 隐私政策
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/biz/privacy_policy")
|
||||
public class BizPrivacyPolicyController {
|
||||
|
||||
@Resource
|
||||
private PrivacyPolicyService privacyPolicyService;
|
||||
|
||||
@GetMapping("/index")
|
||||
public String index() {
|
||||
return "admin/biz/privacyPolicy/index";
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@ResponseBody
|
||||
public Result<Page<PrivacyPolicy>> page(PrivacyPolicy privacyPolicy) {
|
||||
return Result.success(privacyPolicyService.page(privacyPolicy));
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
@ResponseBody
|
||||
public Result<String> save(@RequestBody PrivacyPolicy privacyPolicy) {
|
||||
return Result.isBool(privacyPolicyService.save(privacyPolicy));
|
||||
}
|
||||
|
||||
@PostMapping("/update")
|
||||
@ResponseBody
|
||||
public Result<String> update(@RequestBody PrivacyPolicy privacyPolicy) {
|
||||
return Result.isBool(privacyPolicyService.updateById(privacyPolicy));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@ResponseBody
|
||||
public Result<String> delete(Long id) {
|
||||
return Result.isBool(privacyPolicyService.removeById(id));
|
||||
}
|
||||
|
||||
@PostMapping("/batchDelete")
|
||||
@ResponseBody
|
||||
public Result<String> batchDelete(@RequestBody PrivacyPolicyBatchDeleteRequest privacyPolicyBatchDeleteRequest) {
|
||||
return Result.isBool(privacyPolicyService.removeBatchByIds(privacyPolicyBatchDeleteRequest.getIds()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.corewing.app.modules.admin.biz;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.biz.tutorial.CategoryBatchDeleteRequest;
|
||||
import com.corewing.app.entity.Tutorial;
|
||||
import com.corewing.app.entity.TutorialCategory;
|
||||
import com.corewing.app.service.TutorialCategoryService;
|
||||
import com.corewing.app.service.TutorialService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 教程指南
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/biz/tutorial")
|
||||
public class BizTutorialController {
|
||||
|
||||
@Resource
|
||||
private TutorialCategoryService tutorialCategoryService;
|
||||
|
||||
@Resource
|
||||
private TutorialService tutorialService;
|
||||
|
||||
|
||||
/**
|
||||
* 教程分页
|
||||
* @param tutorial
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@ResponseBody
|
||||
public Result<Page<Tutorial>> page(Tutorial tutorial) {
|
||||
return Result.success(tutorialService.page(tutorial));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增教程
|
||||
* @param tutorial
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/save")
|
||||
@ResponseBody
|
||||
public Result<String> save(@RequestBody Tutorial tutorial) {
|
||||
return Result.isBool(tutorialService.save(tutorial));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新教程
|
||||
* @param tutorial
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
@ResponseBody
|
||||
public Result<String> update(@RequestBody Tutorial tutorial) {
|
||||
return Result.isBool(tutorialService.updateById(tutorial));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除教程
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping("/delete")
|
||||
@ResponseBody
|
||||
public Result<String> delete(Long id) {
|
||||
return Result.isBool(tutorialService.remove(id));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 教程管理首页
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/index")
|
||||
public String index() {
|
||||
return "admin/biz/tutorial/index";
|
||||
}
|
||||
|
||||
/**
|
||||
* 教程分类分页
|
||||
* @param tutorialCategory
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/category/page")
|
||||
@ResponseBody
|
||||
public Result<Page<TutorialCategory>> categoryPage(TutorialCategory tutorialCategory) {
|
||||
return Result.success(tutorialCategoryService.page(tutorialCategory));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增教程分类
|
||||
* @param tutorialCategory
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/category/save")
|
||||
@ResponseBody
|
||||
public Result<String> saveCategory(@RequestBody TutorialCategory tutorialCategory) {
|
||||
tutorialCategory.setType(TutorialCategory.typeCategory);
|
||||
return Result.isBool(tutorialCategoryService.save(tutorialCategory));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新教程分类
|
||||
* @param tutorialCategory
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/category/update")
|
||||
@ResponseBody
|
||||
public Result<String> updateCategory(@RequestBody TutorialCategory tutorialCategory) {
|
||||
return Result.isBool(tutorialCategoryService.updateById(tutorialCategory));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除教程分类
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping("/category/delete")
|
||||
@ResponseBody
|
||||
public Result<String> deleteCategory(Long id) {
|
||||
return Result.isBool(tutorialCategoryService.removeById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除教程分类
|
||||
* @param categoryBatchDeleteRequest
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/category/batchDelete")
|
||||
@ResponseBody
|
||||
public Result<String> batchDeleteCategory(@RequestBody CategoryBatchDeleteRequest categoryBatchDeleteRequest) {
|
||||
return Result.isBool(tutorialCategoryService.removeBatchByIds(categoryBatchDeleteRequest.getIds()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.corewing.app.modules.admin.biz;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.biz.user.BizUserBatchDeleteRequest;
|
||||
import com.corewing.app.dto.biz.user.BizUserIdRequest;
|
||||
import com.corewing.app.dto.biz.user.BizUserStatusRequest;
|
||||
import com.corewing.app.dto.biz.user.ResetPasswordRequest;
|
||||
import com.corewing.app.entity.User;
|
||||
import com.corewing.app.service.UserService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/biz/user")
|
||||
public class BizUserController {
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
/**
|
||||
* 跳转到用户管理页面
|
||||
* @return 页面名称
|
||||
*/
|
||||
@GetMapping("/index")
|
||||
public String index() {
|
||||
return "admin/biz/user/index";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分页信息
|
||||
* @param user 用户搜索对象
|
||||
* @return 用户信息集合
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@ResponseBody
|
||||
public Result<Page<User>> page(User user) {
|
||||
Page<User> sysUserPage = userService.page(user);
|
||||
return Result.success(sysUserPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param resetPasswordRequest 修改密码DTO
|
||||
* @return 成功 or 失败
|
||||
*/
|
||||
@PutMapping("/resetPassword")
|
||||
@ResponseBody
|
||||
public Result<String> resetPassword(@RequestBody ResetPasswordRequest resetPasswordRequest) {
|
||||
boolean flag = userService.resetPassword(resetPasswordRequest);
|
||||
return Result.isBoolAsMsg(flag, "修改密码成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增用户
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/save")
|
||||
@ResponseBody
|
||||
public Result<String> save(@RequestBody User user) {
|
||||
return Result.isBool(userService.save(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
@ResponseBody
|
||||
public Result<String> update(@RequestBody User user) {
|
||||
return Result.isBool(userService.update(user));
|
||||
}
|
||||
|
||||
/**
|
||||
* 单删用户
|
||||
* @param bizUserIdRequest
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/delete")
|
||||
@ResponseBody
|
||||
public Result<String> delete(@RequestBody BizUserIdRequest bizUserIdRequest) {
|
||||
return Result.isBool(userService.removeById(bizUserIdRequest.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除用户
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/batchDelete")
|
||||
@ResponseBody
|
||||
public Result<String> batchDelete(@RequestBody BizUserBatchDeleteRequest bizUserBatchDeleteRequest) {
|
||||
return Result.isBool(userService.removeByIds(bizUserBatchDeleteRequest.getIds()));
|
||||
}
|
||||
|
||||
@PostMapping("/batchStatus")
|
||||
@ResponseBody
|
||||
public Result<String> batchStatus(@RequestBody BizUserStatusRequest bizUserStatusRequests) {
|
||||
return Result.isBool(userService.batchStatus(bizUserStatusRequests));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.corewing.app.modules.admin.sys;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class SysMainController {
|
||||
|
||||
/**
|
||||
* 加载页
|
||||
* @return
|
||||
*/
|
||||
@GetMapping({"/", "/index.html"})
|
||||
public String loading() {
|
||||
return "admin/loading";
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录页
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/admin/login.html")
|
||||
public String login() {
|
||||
return "admin/login";
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台首页
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/admin/index.html")
|
||||
public String adminIndex() {
|
||||
return "admin/main";
|
||||
}
|
||||
|
||||
/**
|
||||
* 仪表盘
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/admin/dashboard")
|
||||
public String dashboard() {
|
||||
return "admin/dashboard";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.corewing.app.modules.admin.sys;
|
||||
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.entity.SysMenu;
|
||||
import com.corewing.app.service.SysMenuService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/sys/menu")
|
||||
public class SysMenuController {
|
||||
|
||||
@Resource
|
||||
private SysMenuService sysMenuService;
|
||||
|
||||
@GetMapping("/initSysMenu")
|
||||
@ResponseBody
|
||||
public Result<List<SysMenu>> initSysMenu() {
|
||||
List<SysMenu> sysMenus = sysMenuService.initSysMenu();
|
||||
return Result.success(sysMenus);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.corewing.app.modules.admin.sys;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
* 系统设置
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/sys/setting")
|
||||
public class SysSettingController {
|
||||
|
||||
@GetMapping
|
||||
public String index() {
|
||||
return "admin/sys/setting";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
package com.corewing.app.controller;
|
||||
package com.corewing.app.modules.admin.sys;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.SysLoginRequest;
|
||||
import com.corewing.app.dto.api.SysLoginRequest;
|
||||
import com.corewing.app.dto.sys.SysResetPasswordRequest;
|
||||
import com.corewing.app.dto.sys.SysUserIdRequest;
|
||||
import com.corewing.app.entity.SysUser;
|
||||
import com.corewing.app.service.SysUserService;
|
||||
import com.corewing.app.util.I18nUtil;
|
||||
import com.corewing.app.util.IpUtil;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -16,7 +20,7 @@ import java.util.Map;
|
||||
/**
|
||||
* 后台管理用户 Controller
|
||||
*/
|
||||
@RestController
|
||||
@Controller
|
||||
@RequestMapping("/sys/user")
|
||||
public class SysUserController {
|
||||
|
||||
@@ -26,13 +30,49 @@ public class SysUserController {
|
||||
this.sysUserService = sysUserService;
|
||||
}
|
||||
|
||||
@GetMapping("/index")
|
||||
public String index() {
|
||||
return "admin/sys/user/index";
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@ResponseBody
|
||||
public Result<Page<SysUser>> page(SysUser sysUser) {
|
||||
Page<SysUser> sysUserPage = sysUserService.page(sysUser);
|
||||
return Result.success(sysUserPage);
|
||||
}
|
||||
|
||||
@PostMapping("/update")
|
||||
@ResponseBody
|
||||
public Result<String> update(@RequestBody SysUser sysUser) {
|
||||
return Result.isBool(sysUserService.update(sysUser));
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
@ResponseBody
|
||||
public Result<String> save(@RequestBody SysUser sysUser) {
|
||||
return Result.isBool(sysUserService.save(sysUser));
|
||||
}
|
||||
|
||||
@PutMapping("/resetPassword")
|
||||
@ResponseBody
|
||||
public Result<String> resetPassword(@RequestBody SysResetPasswordRequest sysResetPasswordRequest) {
|
||||
return Result.isBool(sysUserService.resetPassword(sysResetPasswordRequest));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@ResponseBody
|
||||
public Result<String> delete(@RequestBody SysUserIdRequest sysUserIdRequest) {
|
||||
return Result.isBool(sysUserService.removeById(sysUserIdRequest.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台管理登录
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
@ResponseBody
|
||||
public Result<Map<String, Object>> login(@RequestBody SysLoginRequest request, HttpServletRequest httpRequest) {
|
||||
try {
|
||||
// 获取登录IP
|
||||
String loginIp = IpUtil.getClientIp(httpRequest);
|
||||
|
||||
// 执行登录
|
||||
@@ -57,6 +97,7 @@ public class SysUserController {
|
||||
* 后台管理登出
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
@ResponseBody
|
||||
public Result<String> logout() {
|
||||
StpUtil.logout();
|
||||
return Result.success(I18nUtil.getMessage("user.logout.success"));
|
||||
@@ -66,6 +107,7 @@ public class SysUserController {
|
||||
* 获取当前登录用户信息
|
||||
*/
|
||||
@GetMapping("/info")
|
||||
@ResponseBody
|
||||
public Result<SysUser> getUserInfo() {
|
||||
Long userId = StpUtil.getLoginIdAsLong();
|
||||
SysUser user = sysUserService.getById(userId);
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.corewing.app.modules.app;
|
||||
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.entity.ContactMsg;
|
||||
import com.corewing.app.service.ContactMsgService;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/contactMsg")
|
||||
public class AppContactMsgController {
|
||||
|
||||
@Resource
|
||||
private ContactMsgService contactMsgService;
|
||||
|
||||
@PostMapping("/save")
|
||||
public Result<String> save(@RequestBody ContactMsg contactMsg) {
|
||||
return Result.isBool(contactMsgService.save(contactMsg));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.corewing.app.controller;
|
||||
package com.corewing.app.modules.app;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.FeedbackRequest;
|
||||
import com.corewing.app.dto.api.FeedbackRequest;
|
||||
import com.corewing.app.entity.Feedback;
|
||||
import com.corewing.app.service.FeedbackService;
|
||||
import com.corewing.app.util.DingTalkUtil;
|
||||
@@ -23,13 +23,13 @@ import java.util.List;
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/feedback")
|
||||
public class FeedbackController {
|
||||
public class AppFeedbackController {
|
||||
|
||||
private final FeedbackService feedbackService;
|
||||
private final DingTalkUtil dingTalkUtil;
|
||||
private final Ip2RegionUtil ip2RegionUtil;
|
||||
|
||||
public FeedbackController(FeedbackService feedbackService, DingTalkUtil dingTalkUtil, Ip2RegionUtil ip2RegionUtil) {
|
||||
public AppFeedbackController(FeedbackService feedbackService, DingTalkUtil dingTalkUtil, Ip2RegionUtil ip2RegionUtil) {
|
||||
this.feedbackService = feedbackService;
|
||||
this.dingTalkUtil = dingTalkUtil;
|
||||
this.ip2RegionUtil = ip2RegionUtil;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.corewing.app.controller;
|
||||
package com.corewing.app.modules.app;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
@@ -15,11 +15,11 @@ import org.springframework.web.bind.annotation.*;
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/firmware")
|
||||
public class FirmwareController {
|
||||
public class AppFirmwareController {
|
||||
|
||||
private final FirmwareService firmwareService;
|
||||
|
||||
public FirmwareController(FirmwareService firmwareService) {
|
||||
public AppFirmwareController(FirmwareService firmwareService) {
|
||||
this.firmwareService = firmwareService;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.corewing.app.controller;
|
||||
package com.corewing.app.modules.app;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.CreateParamRequest;
|
||||
import com.corewing.app.dto.UpdateParamRequest;
|
||||
import com.corewing.app.dto.api.CreateParamRequest;
|
||||
import com.corewing.app.dto.api.UpdateParamRequest;
|
||||
import com.corewing.app.entity.ParamsCenter;
|
||||
import com.corewing.app.service.ParamsCenterService;
|
||||
import com.corewing.app.util.I18nUtil;
|
||||
@@ -20,11 +20,11 @@ import java.util.List;
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/params")
|
||||
public class ParamsCenterController {
|
||||
public class AppParamsCenterController {
|
||||
|
||||
private final ParamsCenterService paramsService;
|
||||
|
||||
public ParamsCenterController(ParamsCenterService paramsService) {
|
||||
public AppParamsCenterController(ParamsCenterService paramsService) {
|
||||
this.paramsService = paramsService;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.corewing.app.modules.app;
|
||||
|
||||
import com.corewing.app.entity.PrivacyPolicy;
|
||||
import com.corewing.app.service.PrivacyPolicyService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 隐私政策与协议
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/privacy_policy")
|
||||
public class AppPrivacyPolicyController {
|
||||
|
||||
@Resource
|
||||
private PrivacyPolicyService privacyPolicyService;
|
||||
|
||||
/**
|
||||
* 隐私政策列表
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/view_list/{lang}")
|
||||
public String viewList(@PathVariable String lang, ModelMap modelMap) {
|
||||
List<PrivacyPolicy> list = privacyPolicyService.list();
|
||||
modelMap.put("list", list);
|
||||
// 获取最新更新时间
|
||||
modelMap.put("lastUpdateTime", privacyPolicyService.getLastUpdateTime());
|
||||
return "app/privacyPolicy/index";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package com.corewing.app.controller;
|
||||
package com.corewing.app.modules.app;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.api.TutorialListRequest;
|
||||
import com.corewing.app.entity.Tutorial;
|
||||
import com.corewing.app.entity.TutorialCategory;
|
||||
import com.corewing.app.service.TutorialCategoryService;
|
||||
import com.corewing.app.service.TutorialService;
|
||||
import com.corewing.app.util.I18nUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -21,24 +21,39 @@ import java.util.List;
|
||||
* 教程接口
|
||||
*/
|
||||
@RequestMapping("/tutorial")
|
||||
@RestController
|
||||
@Controller
|
||||
@Slf4j
|
||||
public class TutorialController {
|
||||
public class AppTutorialController {
|
||||
|
||||
private final TutorialService tutorialService;
|
||||
private final TutorialCategoryService tutorialCategoryService;
|
||||
|
||||
public TutorialController(TutorialService tutorialService, TutorialCategoryService tutorialCategoryService) {
|
||||
public AppTutorialController(TutorialService tutorialService, TutorialCategoryService tutorialCategoryService) {
|
||||
this.tutorialService = tutorialService;
|
||||
this.tutorialCategoryService = tutorialCategoryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到界面查看教程详情
|
||||
* @param tutorialId 教程id
|
||||
* @param model 数据模型
|
||||
* @return 详情页
|
||||
*/
|
||||
@GetMapping("/viewDetail/{tutorialId}")
|
||||
public String viewDetail(@PathVariable Long tutorialId, ModelMap model) {
|
||||
Tutorial tutorial = tutorialService.getById(tutorialId);
|
||||
model.put("tutorial", tutorial);
|
||||
return "app/tutorial/viewDetail";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加查看次数
|
||||
* @param tutorialId 教程id
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/addViewCount")
|
||||
@ResponseBody
|
||||
public Result<String> addViewCount(@RequestParam Long tutorialId) {
|
||||
Tutorial tutorial = tutorialService.getById(tutorialId);
|
||||
if(tutorial == null) {
|
||||
@@ -56,6 +71,7 @@ public class TutorialController {
|
||||
*
|
||||
*/
|
||||
@GetMapping("/category")
|
||||
@ResponseBody
|
||||
public Result<List<TutorialCategory>> category(
|
||||
@RequestParam(required = false, defaultValue = "0") Integer firstStatus
|
||||
) {
|
||||
@@ -77,19 +93,19 @@ public class TutorialController {
|
||||
*
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@ResponseBody
|
||||
public Result<IPage<Tutorial>> getPageList(
|
||||
@RequestParam(defaultValue = "1") Long current,
|
||||
@RequestParam(defaultValue = "10") Long size,
|
||||
@RequestParam(required = false, defaultValue = "0") Integer categoryId,
|
||||
@RequestParam(required = false, defaultValue = "0") Long categoryId,
|
||||
@RequestParam(required = false) String tutorialTitle) {
|
||||
try {
|
||||
Page<Tutorial> page = new Page<>(current, size);
|
||||
IPage<Tutorial> pageResult = tutorialService.pageList(page, categoryId, tutorialTitle, I18nUtil.getCurrentLocale().getLanguage());
|
||||
Page<Tutorial> pageResult = tutorialService.pageList(page, categoryId, tutorialTitle, I18nUtil.getCurrentLocale().getLanguage());
|
||||
return Result.success(pageResult);
|
||||
} catch (Exception e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.corewing.app.controller;
|
||||
package com.corewing.app.modules.app;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.dto.LoginRequest;
|
||||
import com.corewing.app.dto.RegisterRequest;
|
||||
import com.corewing.app.dto.SendCodeRequest;
|
||||
import com.corewing.app.dto.UpdatePasswordRequest;
|
||||
import com.corewing.app.dto.api.LoginRequest;
|
||||
import com.corewing.app.dto.api.RegisterRequest;
|
||||
import com.corewing.app.dto.api.SendCodeRequest;
|
||||
import com.corewing.app.dto.api.UpdatePasswordRequest;
|
||||
import com.corewing.app.entity.User;
|
||||
import com.corewing.app.service.UserService;
|
||||
import com.corewing.app.service.VerifyCodeService;
|
||||
@@ -22,12 +22,12 @@ import java.util.Map;
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
public class UserController {
|
||||
public class AppUserController {
|
||||
|
||||
private final UserService userService;
|
||||
private final VerifyCodeService verifyCodeService;
|
||||
|
||||
public UserController(UserService userService, VerifyCodeService verifyCodeService) {
|
||||
public AppUserController(UserService userService, VerifyCodeService verifyCodeService) {
|
||||
this.userService = userService;
|
||||
this.verifyCodeService = verifyCodeService;
|
||||
}
|
||||
@@ -132,11 +132,12 @@ public class UserController {
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
@PutMapping
|
||||
@PutMapping("/update")
|
||||
public Result<String> update(@RequestBody User user) {
|
||||
Long userId = StpUtil.getLoginIdAsLong();
|
||||
user.setId(userId);
|
||||
// 不允许通过此接口修改密码
|
||||
user.setPassword(null);
|
||||
|
||||
boolean success = userService.updateById(user);
|
||||
if (success) {
|
||||
return Result.success(I18nUtil.getMessage("user.update.success"));
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.corewing.app.modules.directive;
|
||||
|
||||
import com.corewing.app.service.SysOptionService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Service("option")
|
||||
public class SysOptionDirective {
|
||||
|
||||
@Resource
|
||||
private SysOptionService sysOptionService;
|
||||
|
||||
public String getValue(String key) {
|
||||
return sysOptionService.getValueByKey(key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.entity.ContactMsg;
|
||||
|
||||
public interface ContactMsgService extends IService<ContactMsg> {
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.corewing.app.service;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.dto.biz.feedback.FeedbackBatchStatusRequest;
|
||||
import com.corewing.app.entity.Feedback;
|
||||
|
||||
import java.util.List;
|
||||
@@ -12,6 +13,9 @@ import java.util.List;
|
||||
*/
|
||||
public interface FeedbackService extends IService<Feedback> {
|
||||
|
||||
Page<Feedback> page(Feedback feedback);
|
||||
|
||||
|
||||
/**
|
||||
* 创建反馈
|
||||
*
|
||||
@@ -47,4 +51,11 @@ public interface FeedbackService extends IService<Feedback> {
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean updateStatus(Long id, Integer status);
|
||||
|
||||
/**
|
||||
* 批量修改状态
|
||||
* @param feedbackBatchStatusRequest
|
||||
* @return
|
||||
*/
|
||||
boolean batchStatus(FeedbackBatchStatusRequest feedbackBatchStatusRequest);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.common.Result;
|
||||
import com.corewing.app.entity.Firmware;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 固件 Service 接口
|
||||
*/
|
||||
public interface FirmwareService extends IService<Firmware> {
|
||||
|
||||
/**
|
||||
* 查询固件分页数据
|
||||
* @param firmware
|
||||
* @return
|
||||
*/
|
||||
Page<Firmware> page(Firmware firmware);
|
||||
|
||||
/**
|
||||
* 根据固件名称查询固件
|
||||
*
|
||||
@@ -15,4 +25,12 @@ public interface FirmwareService extends IService<Firmware> {
|
||||
* @return 固件信息
|
||||
*/
|
||||
Firmware getByFirmwareName(String firmwareName);
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param file
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
boolean uploadFile(MultipartFile file, Long id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.entity.PrivacyPolicy;
|
||||
|
||||
public interface PrivacyPolicyService extends IService<PrivacyPolicy> {
|
||||
|
||||
Page<PrivacyPolicy> page(PrivacyPolicy privacyPolicy);
|
||||
|
||||
String getLastUpdateTime();
|
||||
}
|
||||
10
src/main/java/com/corewing/app/service/SysMenuService.java
Normal file
10
src/main/java/com/corewing/app/service/SysMenuService.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.entity.SysMenu;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SysMenuService extends IService<SysMenu> {
|
||||
List<SysMenu> initSysMenu();
|
||||
}
|
||||
10
src/main/java/com/corewing/app/service/SysOptionService.java
Normal file
10
src/main/java/com/corewing/app/service/SysOptionService.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.entity.SysOption;
|
||||
|
||||
public interface SysOptionService extends IService<SysOption> {
|
||||
|
||||
String getValueByKey(String key);
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.dto.sys.SysResetPasswordRequest;
|
||||
import com.corewing.app.entity.SysUser;
|
||||
|
||||
/**
|
||||
@@ -8,6 +10,12 @@ import com.corewing.app.entity.SysUser;
|
||||
*/
|
||||
public interface SysUserService extends IService<SysUser> {
|
||||
|
||||
Page<SysUser> page(SysUser sysUser);
|
||||
|
||||
boolean save(SysUser sysUser);
|
||||
|
||||
boolean update(SysUser sysUser);
|
||||
|
||||
/**
|
||||
* 根据用户名查询用户
|
||||
*
|
||||
@@ -33,4 +41,11 @@ public interface SysUserService extends IService<SysUser> {
|
||||
* @param loginIp 登录IP
|
||||
*/
|
||||
void updateLoginInfo(Long userId, String loginIp);
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param sysResetPasswordRequest
|
||||
* @return
|
||||
*/
|
||||
boolean resetPassword(SysResetPasswordRequest sysResetPasswordRequest);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.entity.TutorialCategory;
|
||||
|
||||
public interface TutorialCategoryService extends IService<TutorialCategory> {
|
||||
|
||||
Page<TutorialCategory> page(TutorialCategory tutorialCategory);
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,21 @@ package com.corewing.app.service;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.dto.api.TutorialListRequest;
|
||||
import com.corewing.app.entity.Tutorial;
|
||||
import com.corewing.app.entity.TutorialCategory;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TutorialService extends IService<Tutorial> {
|
||||
IPage<Tutorial> pageList(Page<Tutorial> page, int categoryId, String tutorialTitle, String lang);
|
||||
Page<Tutorial> pageList(Page<Tutorial> page, Long categoryId, String tutorialTitle, String lang);
|
||||
|
||||
Page<Tutorial> page(Tutorial tutorial);
|
||||
|
||||
boolean save(Tutorial tutorial);
|
||||
|
||||
boolean update(Tutorial tutorial);
|
||||
|
||||
boolean remove(Long id);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,40 @@
|
||||
package com.corewing.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.corewing.app.dto.biz.user.BizUserIdRequest;
|
||||
import com.corewing.app.dto.biz.user.BizUserStatusRequest;
|
||||
import com.corewing.app.dto.biz.user.ResetPasswordRequest;
|
||||
import com.corewing.app.entity.User;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 应用用户 Service 接口
|
||||
*/
|
||||
public interface UserService extends IService<User> {
|
||||
|
||||
/**
|
||||
* 获取用户分页数据
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
Page<User> page(User user);
|
||||
|
||||
/**
|
||||
* 新增用户数据
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
boolean save(User user);
|
||||
|
||||
/**
|
||||
* 更新用户数据
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
boolean update(User user);
|
||||
|
||||
/**
|
||||
* 根据用户名查询用户
|
||||
*
|
||||
@@ -74,4 +101,18 @@ public interface UserService extends IService<User> {
|
||||
* @param loginIp 登录IP
|
||||
*/
|
||||
void updateLoginInfo(Long userId, String loginIp);
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param resetPasswordRequest
|
||||
* @return
|
||||
*/
|
||||
boolean resetPassword(ResetPasswordRequest resetPasswordRequest);
|
||||
|
||||
/**
|
||||
* 批量修改状态
|
||||
* @param bizUserStatusRequest
|
||||
* @return
|
||||
*/
|
||||
boolean batchStatus(BizUserStatusRequest bizUserStatusRequest);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.corewing.app.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.entity.ContactMsg;
|
||||
import com.corewing.app.mapper.ContactMsgMapper;
|
||||
import com.corewing.app.service.ContactMsgService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ContactMsgServiceImpl extends ServiceImpl<ContactMsgMapper, ContactMsg> implements ContactMsgService {
|
||||
|
||||
}
|
||||
@@ -1,24 +1,44 @@
|
||||
package com.corewing.app.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.common.page.PageContext;
|
||||
import com.corewing.app.dto.biz.feedback.FeedbackBatchStatusRequest;
|
||||
import com.corewing.app.entity.Feedback;
|
||||
import com.corewing.app.mapper.FeedbackMapper;
|
||||
import com.corewing.app.service.FeedbackService;
|
||||
import com.corewing.app.util.I18nUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
import static io.lettuce.core.GeoArgs.Unit.m;
|
||||
|
||||
/**
|
||||
* 问题反馈 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
public class FeedbackServiceImpl extends ServiceImpl<FeedbackMapper, Feedback> implements FeedbackService {
|
||||
|
||||
@Resource
|
||||
private FeedbackMapper feedbackMapper;
|
||||
|
||||
@Override
|
||||
public Page<Feedback> page(Feedback feedback) {
|
||||
Page<Feedback> page = PageContext.getPage(Feedback.class);
|
||||
// LambdaQueryWrapper<Feedback> wrapper = new LambdaQueryWrapper<>();
|
||||
// wrapper.eq(feedback.getStatus() != null, Feedback::getStatus, feedback.getStatus());
|
||||
// wrapper.like(StringUtils.hasText(feedback.getTitle()), Feedback::getTitle, feedback.getTitle());
|
||||
// wrapper.eq(StringUtils.hasText(feedback.getFeedbackType()), Feedback::getFeedbackType, feedback.getFeedbackType());
|
||||
return feedbackMapper.page(page, feedback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createFeedback(Feedback feedback) {
|
||||
// 设置默认状态为待处理
|
||||
@@ -75,4 +95,16 @@ public class FeedbackServiceImpl extends ServiceImpl<FeedbackMapper, Feedback> i
|
||||
|
||||
return this.updateById(feedback);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public boolean batchStatus(FeedbackBatchStatusRequest feedbackBatchStatusRequest) {
|
||||
feedbackBatchStatusRequest.getIds().forEach(id -> {
|
||||
LambdaUpdateWrapper<Feedback> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(Feedback::getId, id);
|
||||
wrapper.set(Feedback::getStatus, feedbackBatchStatusRequest.getStatus());
|
||||
update(wrapper);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package com.corewing.app.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.common.page.PageContext;
|
||||
import com.corewing.app.entity.Firmware;
|
||||
import com.corewing.app.mapper.FirmwareMapper;
|
||||
import com.corewing.app.service.FirmwareService;
|
||||
import com.corewing.app.util.OSSUploadUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 固件 Service 实现类
|
||||
@@ -13,10 +20,34 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class FirmwareServiceImpl extends ServiceImpl<FirmwareMapper, Firmware> implements FirmwareService {
|
||||
|
||||
@Override
|
||||
public Page<Firmware> page(Firmware firmware) {
|
||||
Page<Firmware> page = PageContext.getPage(Firmware.class);
|
||||
LambdaQueryWrapper<Firmware> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.like(StringUtils.hasText(firmware.getFirmwareName()), Firmware::getFirmwareName, firmware.getFirmwareName());
|
||||
queryWrapper.eq(firmware.getFirmwareType() != null, Firmware::getFirmwareType, firmware.getFirmwareType());
|
||||
return page(page, queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Firmware getByFirmwareName(String firmwareName) {
|
||||
LambdaQueryWrapper<Firmware> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Firmware::getFirmwareName, firmwareName);
|
||||
return this.getOne(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean uploadFile(MultipartFile file, Long id) {
|
||||
try {
|
||||
String downloadUrl = OSSUploadUtil.uploadFile(file.getInputStream(), "/");
|
||||
Firmware firmware = getById(id);
|
||||
firmware.setDownloadUrl(downloadUrl);
|
||||
updateById(firmware);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.corewing.app.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.common.page.PageContext;
|
||||
import com.corewing.app.entity.PrivacyPolicy;
|
||||
import com.corewing.app.mapper.PrivacyPolicyMapper;
|
||||
import com.corewing.app.service.PrivacyPolicyService;
|
||||
import com.corewing.app.util.DateUtils;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Service
|
||||
public class PrivacyPolicyServiceImpl extends ServiceImpl<PrivacyPolicyMapper, PrivacyPolicy> implements PrivacyPolicyService {
|
||||
@Override
|
||||
public Page<PrivacyPolicy> page(PrivacyPolicy privacyPolicy) {
|
||||
Page<PrivacyPolicy> page = PageContext.getPage(PrivacyPolicy.class);
|
||||
LambdaQueryWrapper<PrivacyPolicy> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.like(StringUtils.hasText(privacyPolicy.getTitle()), PrivacyPolicy::getTitle, privacyPolicy.getTitle());
|
||||
queryWrapper.eq(privacyPolicy.getVisible() != null, PrivacyPolicy::getVisible, privacyPolicy.getVisible());
|
||||
return page(page, queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastUpdateTime() {
|
||||
LambdaQueryWrapper<PrivacyPolicy> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.orderByDesc(PrivacyPolicy::getUpdateTime, PrivacyPolicy::getCreateTime);
|
||||
PrivacyPolicy privacyPolicy = list(queryWrapper).get(0);
|
||||
return DateUtils.format(privacyPolicy.getUpdateTime() == null ? privacyPolicy.getCreateTime() : privacyPolicy.getUpdateTime(), "yyyy年MM月dd日");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.corewing.app.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.entity.SysMenu;
|
||||
import com.corewing.app.mapper.SysMenuMapper;
|
||||
import com.corewing.app.service.SysMenuService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {
|
||||
|
||||
|
||||
@Override
|
||||
public List<SysMenu> initSysMenu() {
|
||||
LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(SysMenu::isVisible, Boolean.TRUE);
|
||||
queryWrapper.orderByAsc(SysMenu::getSort);
|
||||
return list(queryWrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.corewing.app.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.entity.SysOption;
|
||||
import com.corewing.app.mapper.SysOptionMapper;
|
||||
import com.corewing.app.service.SysOptionService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SysOptionServiceImpl extends ServiceImpl<SysOptionMapper, SysOption> implements SysOptionService {
|
||||
|
||||
@Override
|
||||
public String getValueByKey(String key) {
|
||||
LambdaQueryWrapper<SysOption> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(SysOption::getKey, key);
|
||||
return getOne(queryWrapper).getValue();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@ package com.corewing.app.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.common.page.PageContext;
|
||||
import com.corewing.app.dto.sys.SysResetPasswordRequest;
|
||||
import com.corewing.app.entity.SysUser;
|
||||
import com.corewing.app.mapper.SysUserMapper;
|
||||
import com.corewing.app.service.SysUserService;
|
||||
@@ -20,6 +23,24 @@ import java.time.LocalDateTime;
|
||||
@Service
|
||||
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
|
||||
|
||||
public Page<SysUser> page(SysUser sysUser) {
|
||||
Page<SysUser> page = PageContext.getPage(SysUser.class);
|
||||
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(sysUser.getStatus() != null, SysUser::getStatus, sysUser.getStatus());
|
||||
queryWrapper.like(StringUtils.hasText(sysUser.getUsername()), SysUser::getUsername, sysUser.getUsername());
|
||||
return page(page, queryWrapper);
|
||||
}
|
||||
|
||||
public boolean save(SysUser sysUser) {
|
||||
String encryptPassword = DigestUtils.md5DigestAsHex(sysUser.getPassword().getBytes(StandardCharsets.UTF_8));
|
||||
sysUser.setPassword(encryptPassword);
|
||||
return super.save(sysUser);
|
||||
}
|
||||
|
||||
public boolean update(SysUser sysUser) {
|
||||
return updateById(sysUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysUser getByUsername(String username) {
|
||||
if (!StringUtils.hasText(username)) {
|
||||
@@ -66,4 +87,16 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
user.setLoginTime(LocalDateTime.now());
|
||||
this.updateById(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetPassword(SysResetPasswordRequest sysResetPasswordRequest) {
|
||||
|
||||
SysUser sysUser = getById(sysResetPasswordRequest.getUserId());
|
||||
if(sysUser == null) {
|
||||
throw new RuntimeException(I18nUtil.getMessage("error.user.not.found"));
|
||||
}
|
||||
String encryptPassword = DigestUtils.md5DigestAsHex(sysResetPasswordRequest.getPassword().getBytes(StandardCharsets.UTF_8));
|
||||
sysUser.setPassword(encryptPassword);
|
||||
return updateById(sysUser);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
package com.corewing.app.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.common.page.PageContext;
|
||||
import com.corewing.app.entity.TutorialCategory;
|
||||
import com.corewing.app.mapper.TutorialCategoryMapper;
|
||||
import com.corewing.app.service.TutorialCategoryService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Service
|
||||
public class TutorialCategoryServiceImpl extends ServiceImpl<TutorialCategoryMapper, TutorialCategory> implements TutorialCategoryService {
|
||||
|
||||
public Page<TutorialCategory> page(TutorialCategory tutorialCategory){
|
||||
Page<TutorialCategory> page = PageContext.getPage(TutorialCategory.class);
|
||||
|
||||
LambdaQueryWrapper<TutorialCategory> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.like(StringUtils.hasText(tutorialCategory.getCategoryTitle()), TutorialCategory::getCategoryTitle, tutorialCategory.getCategoryTitle());
|
||||
return page(page, queryWrapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,24 +1,75 @@
|
||||
package com.corewing.app.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.common.page.PageContext;
|
||||
import com.corewing.app.dto.api.TutorialListRequest;
|
||||
import com.corewing.app.entity.Tutorial;
|
||||
import com.corewing.app.entity.TutorialCategory;
|
||||
import com.corewing.app.entity.TutorialCategoryRelation;
|
||||
import com.corewing.app.mapper.TutorialMapper;
|
||||
import com.corewing.app.service.TutorialCategoryRelationService;
|
||||
import com.corewing.app.service.TutorialCategoryService;
|
||||
import com.corewing.app.service.TutorialService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class TutorialServiceImpl extends ServiceImpl<TutorialMapper, Tutorial> implements TutorialService {
|
||||
|
||||
private final TutorialMapper tutorialMapper;
|
||||
@Resource
|
||||
private TutorialMapper tutorialMapper;
|
||||
|
||||
public TutorialServiceImpl(TutorialMapper tutorialMapper) {
|
||||
this.tutorialMapper = tutorialMapper;
|
||||
@Resource
|
||||
private TutorialCategoryRelationService tutorialCategoryRelationService;
|
||||
|
||||
@Resource
|
||||
private TutorialCategoryService tutorialCategoryService;
|
||||
|
||||
@Override
|
||||
public Page<Tutorial> pageList(Page<Tutorial> page, Long categoryId, String tutorialTitle, String lang) {
|
||||
return tutorialMapper.pageList(page, categoryId, tutorialTitle, lang);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<Tutorial> pageList(Page<Tutorial> page, int categoryId, String tutorialTitle, String lang) {
|
||||
return tutorialMapper.pageList(page, categoryId, tutorialTitle, lang);
|
||||
public Page<Tutorial> page(Tutorial tutorial) {
|
||||
Page<Tutorial> page = PageContext.getPage(Tutorial.class);
|
||||
return tutorialMapper.page(page, tutorial);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public boolean save(Tutorial tutorial) {
|
||||
super.save(tutorial);
|
||||
TutorialCategoryRelation tutorialCategoryRelation = new TutorialCategoryRelation();
|
||||
tutorialCategoryRelation.setTutorialId(tutorial.getId());
|
||||
tutorialCategoryRelation.setCategoryId(tutorial.getCategoryId());
|
||||
return tutorialCategoryRelationService.save(tutorialCategoryRelation);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public boolean update(Tutorial tutorial) {
|
||||
super.updateById(tutorial);
|
||||
tutorialCategoryRelationService.remove(new LambdaQueryWrapper<TutorialCategoryRelation>().eq(TutorialCategoryRelation::getCategoryId, tutorial.getCategoryId()));
|
||||
TutorialCategoryRelation tutorialCategoryRelation = new TutorialCategoryRelation();
|
||||
tutorialCategoryRelation.setTutorialId(tutorial.getId());
|
||||
tutorialCategoryRelation.setCategoryId(tutorial.getCategoryId());
|
||||
return tutorialCategoryRelationService.save(tutorialCategoryRelation);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public boolean remove(Long id) {
|
||||
Tutorial tutorial = getById(id);
|
||||
removeById(id);
|
||||
return tutorialCategoryRelationService.remove(new LambdaQueryWrapper<TutorialCategoryRelation>().eq(TutorialCategoryRelation::getCategoryId, tutorial.getCategoryId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,14 @@ package com.corewing.app.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.corewing.app.common.page.PageContext;
|
||||
import com.corewing.app.dto.biz.user.BizUserIdRequest;
|
||||
import com.corewing.app.dto.biz.user.BizUserStatusRequest;
|
||||
import com.corewing.app.dto.biz.user.ResetPasswordRequest;
|
||||
import com.corewing.app.entity.User;
|
||||
import com.corewing.app.mapper.UserMapper;
|
||||
import com.corewing.app.service.UserService;
|
||||
@@ -10,10 +17,13 @@ import com.corewing.app.service.VerifyCodeService;
|
||||
import com.corewing.app.util.I18nUtil;
|
||||
import com.corewing.app.util.Ip2RegionUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 应用用户 Service 实现类
|
||||
@@ -23,10 +33,64 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
|
||||
private final VerifyCodeService verifyCodeService;
|
||||
private final Ip2RegionUtil ip2RegionUtil;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
public UserServiceImpl(VerifyCodeService verifyCodeService, Ip2RegionUtil ip2RegionUtil) {
|
||||
public UserServiceImpl(VerifyCodeService verifyCodeService, Ip2RegionUtil ip2RegionUtil, UserMapper userMapper) {
|
||||
this.verifyCodeService = verifyCodeService;
|
||||
this.ip2RegionUtil = ip2RegionUtil;
|
||||
this.userMapper = userMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<User> page(User user) {
|
||||
Page<User> page = PageContext.getPage(User.class);
|
||||
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.like(StringUtils.hasText(user.getNickName()), User::getNickName, user.getNickName());
|
||||
queryWrapper.like(StringUtils.hasText(user.getUsername()), User::getUsername, user.getUsername());
|
||||
queryWrapper.like(user.getStatus() != null, User::getStatus, user.getStatus());
|
||||
return page(page, queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean save(User user) {
|
||||
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(User::getUsername, user.getUsername());
|
||||
if(count(queryWrapper) > 0) {
|
||||
throw new RuntimeException(I18nUtil.getMessage(I18nUtil.getMessage("error.username.exists")));
|
||||
}
|
||||
|
||||
|
||||
return super.save(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean update(User user) {
|
||||
|
||||
// 校验用户名
|
||||
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(User::getUsername, user.getUsername());
|
||||
queryWrapper.ne(User::getId, user.getId());
|
||||
if(count(queryWrapper) > 0) {
|
||||
throw new RuntimeException(I18nUtil.getMessage(I18nUtil.getMessage("error.username.exists")));
|
||||
}
|
||||
|
||||
// 校验手机号码
|
||||
LambdaQueryWrapper<User> checkPhoneWrapper = new LambdaQueryWrapper<>();
|
||||
checkPhoneWrapper.eq(User::getUsername, user.getUsername());
|
||||
checkPhoneWrapper.ne(User::getId, user.getId());
|
||||
if(count(checkPhoneWrapper) > 0) {
|
||||
throw new RuntimeException(I18nUtil.getMessage(I18nUtil.getMessage("error.phone.exists")));
|
||||
}
|
||||
|
||||
// 校验邮箱
|
||||
LambdaQueryWrapper<User> checkEmailWrapper = new LambdaQueryWrapper<>();
|
||||
checkEmailWrapper.eq(User::getUsername, user.getUsername());
|
||||
checkEmailWrapper.ne(User::getId, user.getId());
|
||||
if(count(checkEmailWrapper) > 0) {
|
||||
throw new RuntimeException(I18nUtil.getMessage(I18nUtil.getMessage("error.email.exists")));
|
||||
}
|
||||
|
||||
return updateById(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,6 +145,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
|
||||
// 验证密码(MD5加密)
|
||||
String encryptPassword = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
|
||||
// 客户端已经使用加密
|
||||
if (!encryptPassword.equals(user.getPassword())) {
|
||||
throw new RuntimeException(I18nUtil.getMessage("error.password.incorrect"));
|
||||
}
|
||||
@@ -166,4 +231,28 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
user.setLoginRegion(ip2RegionUtil.getRegion(loginIp));
|
||||
this.updateById(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetPassword(ResetPasswordRequest resetPasswordRequest) {
|
||||
User user = getById(resetPasswordRequest.getUserId());
|
||||
if(user == null) {
|
||||
throw new RuntimeException(I18nUtil.getMessage("error.user.not.found"));
|
||||
}
|
||||
// 更新新密码
|
||||
String newPasswordMd5 = DigestUtils.md5DigestAsHex(resetPasswordRequest.getPassword().getBytes(StandardCharsets.UTF_8));
|
||||
user.setPassword(newPasswordMd5);
|
||||
return updateById(user);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public boolean batchStatus(BizUserStatusRequest bizUserStatusRequest) {
|
||||
bizUserStatusRequest.getIds().forEach(id -> {
|
||||
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(User::getId, id);
|
||||
wrapper.set(User::getStatus, bizUserStatusRequest.getStatus());
|
||||
this.update(wrapper);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
14
src/main/java/com/corewing/app/util/DateUtils.java
Normal file
14
src/main/java/com/corewing/app/util/DateUtils.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.corewing.app.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class DateUtils {
|
||||
|
||||
// 格式化LocalDateTime为字符串
|
||||
public static String format(LocalDateTime time, String pattern) {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return time.format(formatter);
|
||||
}
|
||||
|
||||
}
|
||||
86
src/main/java/com/corewing/app/util/OSSUploadUtil.java
Normal file
86
src/main/java/com/corewing/app/util/OSSUploadUtil.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package com.corewing.app.util;
|
||||
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.OSSClientBuilder;
|
||||
import com.aliyun.oss.model.ObjectMetadata;
|
||||
import com.aliyun.oss.model.PutObjectRequest;
|
||||
import com.aliyun.oss.model.PutObjectResult;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
@Component
|
||||
public class OSSUploadUtil {
|
||||
|
||||
private static final String ENDPOINT = "oss-cn-shenzhen.aliyuncs.com"; // 地域节点
|
||||
private static final String ACCESS_KEY_ID = "your-access-key-id"; // 替换为你的 AccessKey ID
|
||||
private static final String ACCESS_KEY_SECRET = "your-access-key-secret"; // 替换为你的 AccessKey Secret
|
||||
private static final String BUCKET_NAME = "corewing-app"; // 替换为你的 Bucket 名称
|
||||
|
||||
/**
|
||||
* 上传文件到 OSS
|
||||
* @param inputStream 文件输入流
|
||||
* @param objectName OSS 中存储的文件路径(如 "firmware/20231104/update.bin")
|
||||
* @return 上传成功后的文件 URL
|
||||
*/
|
||||
public static String uploadFile(InputStream inputStream, String objectName) {
|
||||
// 创建 OSS 客户端实例
|
||||
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
|
||||
|
||||
try {
|
||||
// 设置文件元数据(可选,如 Content-Type)
|
||||
ObjectMetadata metadata = new ObjectMetadata();
|
||||
metadata.setContentType(getContentType(objectName)); // 自动识别文件类型
|
||||
|
||||
// 上传文件
|
||||
PutObjectRequest request = new PutObjectRequest(BUCKET_NAME, objectName, inputStream, metadata);
|
||||
PutObjectResult result = ossClient.putObject(request);
|
||||
|
||||
// 生成文件访问 URL(公网访问需 Bucket 设为公共读)
|
||||
return String.format("https://%s.%s/%s", BUCKET_NAME, ENDPOINT, objectName);
|
||||
} finally {
|
||||
// 关闭 OSS 客户端,释放资源
|
||||
if (ossClient != null) {
|
||||
ossClient.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传本地文件到 OSS
|
||||
* @param localFilePath 本地文件路径(如 "D:/firmware/update.bin")
|
||||
* @param objectName OSS 中存储的文件路径
|
||||
* @return 上传成功后的文件 URL
|
||||
*/
|
||||
public static String uploadLocalFile(String localFilePath, String objectName) {
|
||||
OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
|
||||
try {
|
||||
File file = new File(localFilePath);
|
||||
ossClient.putObject(BUCKET_NAME, objectName, file);
|
||||
return String.format("https://%s.%s/%s", BUCKET_NAME, ENDPOINT, objectName);
|
||||
} finally {
|
||||
if (ossClient != null) {
|
||||
ossClient.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件名获取 Content-Type
|
||||
* @param fileName 文件名(如 "update.bin")
|
||||
* @return Content-Type
|
||||
*/
|
||||
private static String getContentType(String fileName) {
|
||||
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
|
||||
switch (suffix) {
|
||||
case "bin": return "application/octet-stream";
|
||||
case "jpg": case "jpeg": return "image/jpeg";
|
||||
case "png": return "image/png";
|
||||
case "txt": return "text/plain";
|
||||
// 其他文件类型可自行扩展
|
||||
default: return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,4 +55,8 @@ public class TutorialVO {
|
||||
*/
|
||||
private String categoryTitle;
|
||||
|
||||
private String lang;
|
||||
|
||||
private Long categoryId;
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,11 @@ spring.redis.lettuce.pool.max-idle=8
|
||||
spring.redis.lettuce.pool.min-idle=0
|
||||
spring.redis.lettuce.pool.max-wait=-1ms
|
||||
|
||||
# MongoDB 配置
|
||||
spring.data.mongodb.host=120.24.204.180
|
||||
spring.data.mongodb.database=app
|
||||
spring.data.mongodb.port=27017
|
||||
|
||||
# Sa-Token 配置
|
||||
sa-token.token-name=Authorization
|
||||
sa-token.timeout=2592000
|
||||
|
||||
42
src/main/resources/mapper/FeedbackMapper.xml
Normal file
42
src/main/resources/mapper/FeedbackMapper.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.corewing.app.mapper.FeedbackMapper">
|
||||
|
||||
<!-- 结果映射 -->
|
||||
<resultMap id="VOResultMap" type="com.corewing.app.entity.Feedback">
|
||||
<id column="id" property="id"/>
|
||||
<result column="feedback_type" property="feedbackType"/>
|
||||
<result column="title" property="title"/>
|
||||
<result column="content" property="content"/>
|
||||
<result column="contact" property="contact"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="nick_name" property="nickName"/>
|
||||
<result column="username" property="username"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 基础查询SQL片段 -->
|
||||
<sql id="selectVOSql">
|
||||
select f.*,u.nick_name, u.username
|
||||
from app_feedback f
|
||||
left join app_user u on f.user_id = u.id
|
||||
</sql>
|
||||
|
||||
<!-- 分页查询 -->
|
||||
<select id="page" resultMap="VOResultMap">
|
||||
<include refid="selectVOSql"/>
|
||||
<where>
|
||||
<if test="feedback.status != null">
|
||||
AND f.status = #{feedback.status}
|
||||
</if>
|
||||
<if test="feedback.title != null and feedback.title != ''">
|
||||
AND f.title like CONCAT('%', #{feedback.title}, '%')
|
||||
</if>
|
||||
<if test="feedback.feedbackType != null and feedback.feedbackType != ''">
|
||||
AND f.feedbackType like CONCAT('%', #{feedback.feedbackType}, '%')
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -11,14 +11,16 @@
|
||||
<result column="view_count" property="viewCount"/>
|
||||
<result column="recommend_status" property="recommendStatus"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="lang" property="lang"/>
|
||||
<result column="category_title" property="categoryTitle"/>
|
||||
<result column="category_id" property="categoryId"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 基础查询SQL片段 -->
|
||||
<sql id="selectVOSql">
|
||||
select c.*, cc.category_title
|
||||
select c.*, cc.category_title, cc.id as category_id
|
||||
from app_tutorial c
|
||||
left join app_tutorial_category_relation ccr on c.id = ccr.tutorial_id
|
||||
left join app_tutorial_category cc on cc.id = ccr.category_id
|
||||
@@ -36,9 +38,28 @@
|
||||
<if test="tutorialTitle != null and tutorialTitle != ''">
|
||||
AND c.tutorial_title like CONCAT('%', #{tutorialTitle}, '%')
|
||||
</if>
|
||||
|
||||
</where>
|
||||
ORDER BY c.recommend_status,c.create_time asc
|
||||
</select>
|
||||
|
||||
<!-- 分页查询 -->
|
||||
<select id="page" resultMap="VOResultMap">
|
||||
<include refid="selectVOSql"/>
|
||||
<where>
|
||||
<if test="tutorial.categoryId != null and tutorial.categoryId != 0">
|
||||
AND cc.id = #{tutorial.categoryId}
|
||||
</if>
|
||||
<if test="tutorial.tutorialTitle != null and tutorial.tutorialTitle != ''">
|
||||
AND c.tutorial_title like CONCAT('%', #{tutorial.tutorialTitle}, '%')
|
||||
</if>
|
||||
<if test="tutorial.lang != null and tutorial.lang != ''">
|
||||
AND c.lang = #{tutorial.lang}
|
||||
</if>
|
||||
<if test="tutorial.status != null">
|
||||
AND c.status = #{tutorial.status}
|
||||
</if>
|
||||
|
||||
</where>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -1,494 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>后台管理系统</title>
|
||||
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Bootstrap Icons -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #5B5FDE;
|
||||
--secondary-color: #0EA5E9;
|
||||
--accent-color: #FF6B6B;
|
||||
--error-color: #F43F5E;
|
||||
--success-color: #10B981;
|
||||
--warning-color: #F59E0B;
|
||||
--info-color: #3B82F6;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #F3F4F6;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 侧边栏样式 */
|
||||
.sidebar {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
color: white;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 180px;
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 18px 12px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-header h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar-header i {
|
||||
margin-right: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
padding: 9px 10px;
|
||||
border-radius: 8px;
|
||||
margin: 3px 0;
|
||||
transition: all 0.3s;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
width: 18px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
.btn-logout {
|
||||
width: 100%;
|
||||
padding: 9px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-logout:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.navbar {
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
padding: 12px 25px;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #4B5563;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.user-info i {
|
||||
font-size: 20px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
margin-left: 180px;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-wrapper {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
/* 欢迎卡片 */
|
||||
.welcome-card {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
color: white;
|
||||
border-radius: 16px;
|
||||
padding: 35px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: 0 10px 30px rgba(91, 95, 222, 0.2);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.welcome-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
top: -80px;
|
||||
right: -80px;
|
||||
}
|
||||
|
||||
.welcome-card h2 {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.welcome-card p {
|
||||
font-size: 15px;
|
||||
opacity: 0.95;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s;
|
||||
border: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.stat-card i {
|
||||
font-size: 40px;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.stat-card h3 {
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
margin: 12px 0 6px;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.stat-card p {
|
||||
color: #6B7280;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.stat-card.primary i { color: var(--primary-color); }
|
||||
.stat-card.success i { color: var(--success-color); }
|
||||
.stat-card.warning i { color: var(--warning-color); }
|
||||
.stat-card.info i { color: var(--info-color); }
|
||||
|
||||
/* 页面标题 */
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 卡片容器 */
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #E5E7EB;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
position: static;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.welcome-card h2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<!-- 侧边栏 -->
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h4>
|
||||
<i class="bi bi-shield-lock-fill"></i>
|
||||
CoreWing
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-menu">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a :class="['nav-link', currentPage === 'dashboard' ? 'active' : '']"
|
||||
href="#"
|
||||
@click.prevent="currentPage = 'dashboard'">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
仪表盘
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a :class="['nav-link', currentPage === 'users' ? 'active' : '']"
|
||||
href="#"
|
||||
@click.prevent="currentPage = 'users'">
|
||||
<i class="bi bi-people-fill"></i>
|
||||
用户管理
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a :class="['nav-link', currentPage === 'settings' ? 'active' : '']"
|
||||
href="#"
|
||||
@click.prevent="currentPage = 'settings'">
|
||||
<i class="bi bi-gear-fill"></i>
|
||||
系统设置
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<button class="btn-logout" @click="handleLogout">
|
||||
<i class="bi bi-box-arrow-right me-2"></i>
|
||||
退出登录
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="main-content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light">
|
||||
<div class="container-fluid">
|
||||
<span class="navbar-brand">欢迎回来</span>
|
||||
<div class="user-info">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<span>{{ userInfo.realName || userInfo.username }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="content-wrapper">
|
||||
<!-- 仪表盘 -->
|
||||
<div v-if="currentPage === 'dashboard'">
|
||||
<div class="welcome-card">
|
||||
<h2>
|
||||
<i class="bi bi-emoji-smile"></i>
|
||||
你好,{{ userInfo.realName || userInfo.username }}!
|
||||
</h2>
|
||||
<p class="mb-0 mt-2">欢迎使用 CoreWing 后台管理系统</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="stat-card primary">
|
||||
<i class="bi bi-people-fill"></i>
|
||||
<h3>1,234</h3>
|
||||
<p>总用户数</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="stat-card success">
|
||||
<i class="bi bi-file-earmark-text-fill"></i>
|
||||
<h3>567</h3>
|
||||
<p>文档数量</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="stat-card warning">
|
||||
<i class="bi bi-graph-up"></i>
|
||||
<h3>89%</h3>
|
||||
<p>系统负载</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户管理 -->
|
||||
<div v-if="currentPage === 'users'">
|
||||
<h3 class="page-title">用户管理</h3>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>用户管理功能开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统设置 -->
|
||||
<div v-if="currentPage === 'settings'">
|
||||
<h3 class="page-title">系统设置</h3>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>系统设置功能开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Vue 3 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
|
||||
|
||||
<!-- Axios -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
|
||||
|
||||
<!-- Bootstrap 5 JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
const { createApp } = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
userInfo: {
|
||||
username: '',
|
||||
realName: '',
|
||||
userId: ''
|
||||
},
|
||||
currentPage: 'dashboard'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadUserInfo() {
|
||||
// 从 localStorage 获取用户信息
|
||||
this.userInfo.username = localStorage.getItem('username') || '';
|
||||
this.userInfo.realName = localStorage.getItem('realName') || '';
|
||||
this.userInfo.userId = localStorage.getItem('userId') || '';
|
||||
|
||||
// 也可以从服务器获取最新的用户信息
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
try {
|
||||
axios.defaults.headers.common['satoken'] = token;
|
||||
const response = await axios.get('/sys/user/info');
|
||||
if (response.data.code === 200) {
|
||||
const user = response.data.data;
|
||||
this.userInfo.username = user.username;
|
||||
this.userInfo.realName = user.realName || user.username;
|
||||
this.userInfo.userId = user.id;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async handleLogout() {
|
||||
if (confirm('确定要退出登录吗?')) {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
axios.defaults.headers.common['satoken'] = token;
|
||||
await axios.post('/sys/user/logout');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退出登录失败:', error);
|
||||
} finally {
|
||||
// 清除本地存储
|
||||
localStorage.clear();
|
||||
// 跳转到登录页
|
||||
window.location.href = '/admin/login.html';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
checkLogin() {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
// 未登录,跳转到登录页
|
||||
window.location.href = '/admin/login.html';
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 检查登录状态
|
||||
this.checkLogin();
|
||||
// 加载用户信息
|
||||
this.loadUserInfo();
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
112
src/main/resources/static/assets/css/dashboard.css
Normal file
112
src/main/resources/static/assets/css/dashboard.css
Normal file
@@ -0,0 +1,112 @@
|
||||
:root {
|
||||
--primary-color: #5B5FDE;
|
||||
--secondary-color: #0EA5E9;
|
||||
--accent-color: #FF6B6B;
|
||||
--error-color: #F43F5E;
|
||||
--success-color: #10B981;
|
||||
--warning-color: #F59E0B;
|
||||
--info-color: #3B82F6;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #F3F4F6;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
/* 欢迎卡片 */
|
||||
.welcome-card {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
color: white;
|
||||
border-radius: 16px;
|
||||
padding: 35px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: 0 10px 30px rgba(91, 95, 222, 0.2);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.welcome-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
top: -80px;
|
||||
right: -80px;
|
||||
}
|
||||
|
||||
.welcome-card h2 {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.welcome-card p {
|
||||
font-size: 15px;
|
||||
opacity: 0.95;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s;
|
||||
border: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.stat-card i {
|
||||
font-size: 40px;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.stat-card h3 {
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
margin: 12px 0 6px;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.stat-card p {
|
||||
color: #6B7280;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.stat-card.primary i {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.stat-card.success i {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.stat-card.warning i {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.stat-card.info i {
|
||||
color: var(--info-color);
|
||||
}
|
||||
217
src/main/resources/static/assets/css/main.css
Normal file
217
src/main/resources/static/assets/css/main.css
Normal file
@@ -0,0 +1,217 @@
|
||||
:root {
|
||||
--primary-color: #5B5FDE;
|
||||
--secondary-color: #0EA5E9;
|
||||
--accent-color: #FF6B6B;
|
||||
--error-color: #F43F5E;
|
||||
--success-color: #10B981;
|
||||
--warning-color: #F59E0B;
|
||||
--info-color: #3B82F6;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #F3F4F6;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 侧边栏样式 */
|
||||
.sidebar {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
color: white;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 180px;
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 18px 12px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-header h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar-header i {
|
||||
margin-right: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
padding: 9px 10px;
|
||||
border-radius: 8px;
|
||||
margin: 3px 0;
|
||||
transition: all 0.3s;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
width: 18px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
.btn-logout {
|
||||
width: 100%;
|
||||
padding: 9px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-logout:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.navbar {
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
padding: 12px 25px;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #4B5563;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.user-info i {
|
||||
font-size: 20px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
margin-left: 180px;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/* 页面标题 */
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 卡片容器 */
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #E5E7EB;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
/* iframe样式 */
|
||||
.page-iframe {
|
||||
width: 100%;
|
||||
height: calc(100vh - 72px); /* 减去导航栏高度 */
|
||||
border: none;
|
||||
transition: opacity 0.3s ease;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-state {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
position: static;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.welcome-card h2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 0;
|
||||
min-height: calc(100vh - 72px);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
124
src/main/resources/static/assets/css/table.css
Normal file
124
src/main/resources/static/assets/css/table.css
Normal file
@@ -0,0 +1,124 @@
|
||||
/* 基础样式优化 */
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 8px;
|
||||
padding: 18px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 批量操作样式(表格下方左侧) */
|
||||
.batch-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #6c757d;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
.table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.table-hover tbody tr:hover {
|
||||
background-color: rgba(14, 165, 233, 0.05);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.table tbody td, table thead th{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*.table tbody tr.selected {*/
|
||||
/* background-color: rgba(14, 165, 233, 0.1);*/
|
||||
/* border-color: #0ea5e9;*/
|
||||
/*}*/
|
||||
|
||||
/*.table th {*/
|
||||
/* font-weight: 600;*/
|
||||
/* color: #212529;*/
|
||||
/* border-bottom-width: 2px;*/
|
||||
/*}*/
|
||||
|
||||
/*.table td, .table th {*/
|
||||
/* vertical-align: middle;*/
|
||||
/* padding: 12px 16px;*/
|
||||
/*}*/
|
||||
|
||||
/* 搜索框样式 */
|
||||
.search-item {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
min-width: 200px;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
background-color: #f1f3f5;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
|
||||
/* 按钮样式优化 */
|
||||
.btn {
|
||||
padding: 6px 16px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
||||
/* 徽章样式 */
|
||||
.badge {
|
||||
padding: 4px 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
/* 无数据提示 */
|
||||
.no-data {
|
||||
color: #6c757d;
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.no-data i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
.ellipsis-single {
|
||||
white-space: nowrap; /* 禁止换行 */
|
||||
overflow: hidden; /* 隐藏超出部分 */
|
||||
text-overflow: ellipsis; /* 显示省略号 */
|
||||
max-width: 200px; /* 自定义最大宽度(根据需求调整,如 150px/300px) */
|
||||
}
|
||||
83
src/main/resources/static/assets/js/axiosRequest.js
Normal file
83
src/main/resources/static/assets/js/axiosRequest.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// 引入 axios@1.4.0(确保已引入 axios.min.js)
|
||||
if (!window.axios) throw new Error("请先引入 axios@1.4.0 脚本!");
|
||||
|
||||
|
||||
const service = axios.create({
|
||||
baseURL: "",
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
"Content-Type": "application/json;charset=utf-8"
|
||||
}
|
||||
});
|
||||
|
||||
// -------------------------- 请求拦截器:自动携带 Token --------------------------
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
if (token) {
|
||||
config.headers["Authorization"] = token;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// -------------------------- 响应拦截器:统一处理结果 --------------------------
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
return response.data;
|
||||
},
|
||||
(error) => {
|
||||
let errorMsg = "请求失败,请稍后重试";
|
||||
|
||||
if (error.response) {
|
||||
switch (error.response.status) {
|
||||
case 401:
|
||||
errorMsg = "Token 已过期,请重新登录";
|
||||
localStorage.removeItem("token");
|
||||
window.location.href = "/admin/login.html";
|
||||
break;
|
||||
case 403:
|
||||
errorMsg = "暂无权限访问该接口";
|
||||
break;
|
||||
case 500:
|
||||
errorMsg = "服务器内部错误";
|
||||
break;
|
||||
default:
|
||||
errorMsg = error.response.data?.msg || errorMsg;
|
||||
}
|
||||
} else if (error.request) {
|
||||
errorMsg = "网络异常,请检查网络连接";
|
||||
}
|
||||
console.error("接口请求错误:", errorMsg);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// -------------------------- 封装常用请求方法(get/post/put/delete) --------------------------
|
||||
const request = {
|
||||
// GET
|
||||
get(url, params = {}) {
|
||||
return service.get(url, { params });
|
||||
},
|
||||
|
||||
// POST
|
||||
post(url, data = {}) {
|
||||
return service.post(url, data);
|
||||
},
|
||||
|
||||
// PUT
|
||||
put(url, data = {}) {
|
||||
return service.put(url, data);
|
||||
},
|
||||
|
||||
// DELETE
|
||||
delete(url, params = {}) {
|
||||
return service.delete(url, { params });
|
||||
}
|
||||
};
|
||||
|
||||
window.request = request;
|
||||
55
src/main/resources/static/assets/js/confirmModal.js
Normal file
55
src/main/resources/static/assets/js/confirmModal.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// assets/js/confirmModal.js
|
||||
(function(window) {
|
||||
// 全局确认弹窗函数
|
||||
window.showConfirmModal = function(options = {}) {
|
||||
// 默认配置
|
||||
const {
|
||||
title = '系统提示',
|
||||
content = '确定执行此操作吗?',
|
||||
onConfirm = () => {}, // 确认回调
|
||||
onCancel = () => {} // 取消回调(可选)
|
||||
} = options;
|
||||
|
||||
// 生成唯一ID(避免重复)
|
||||
const modalId = `confirm-modal-${Date.now()}`;
|
||||
|
||||
// 动态创建模态框DOM
|
||||
const modalHtml = `
|
||||
<div class="modal fade" id="${modalId}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5">${title}</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">${content}</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" id="${modalId}-confirm">确认</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 插入到页面
|
||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||
|
||||
// 初始化并显示弹窗
|
||||
const modal = new bootstrap.Modal(document.getElementById(modalId));
|
||||
modal.show();
|
||||
|
||||
// 绑定确认按钮事件
|
||||
document.getElementById(`${modalId}-confirm`).addEventListener('click', () => {
|
||||
onConfirm(); // 执行确认逻辑
|
||||
modal.hide();
|
||||
});
|
||||
|
||||
// 弹窗关闭后清理DOM
|
||||
const modalElement = document.getElementById(modalId);
|
||||
modalElement.addEventListener('hidden.bs.modal', () => {
|
||||
onCancel(); // 执行取消逻辑(可选)
|
||||
modalElement.remove(); // 移除DOM,避免冗余
|
||||
});
|
||||
};
|
||||
})(window);
|
||||
145
src/main/resources/static/assets/js/message.js
Normal file
145
src/main/resources/static/assets/js/message.js
Normal file
@@ -0,0 +1,145 @@
|
||||
if (!window.Vue) {
|
||||
const vueScript = document.createElement('script');
|
||||
vueScript.src = 'https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js';
|
||||
document.head.appendChild(vueScript);
|
||||
}
|
||||
|
||||
if (!document.querySelector('link[href*="bootstrap-icons"]')) {
|
||||
const iconLink = document.createElement('link');
|
||||
iconLink.rel = 'stylesheet';
|
||||
iconLink.href = 'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css';
|
||||
document.head.appendChild(iconLink);
|
||||
}
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.h5-message-container {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.h5-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 14px 24px;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
|
||||
animation: h5MsgShow 0.4s ease-out forwards;
|
||||
}
|
||||
.h5-message.success { background-color: rgba(40, 167, 69, 0.95); }
|
||||
.h5-message.error { background-color: rgba(220, 53, 69, 0.95); }
|
||||
.h5-message.warning { background-color: rgba(255, 193, 7, 0.95); color: #333; }
|
||||
.h5-message.info { background-color: rgba(13, 110, 253, 0.95); }
|
||||
.h5-message-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 20px;
|
||||
}
|
||||
@keyframes h5MsgShow {
|
||||
from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
|
||||
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||||
}
|
||||
.h5-message.hide { animation: h5MsgHide 0.4s ease-in forwards; }
|
||||
@keyframes h5MsgHide {
|
||||
from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||||
to { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
const messageTemplate = `
|
||||
<div class="h5-message" :class="[type, { hide: !visible }]">
|
||||
<i class="h5-message-icon" :class="iconClass"></i>
|
||||
<span>{{ message }}</span>
|
||||
</div>
|
||||
`;
|
||||
const messageInstances = [];
|
||||
function showMessage(options) {
|
||||
if (typeof options === 'string') {
|
||||
options = { message: options };
|
||||
}
|
||||
|
||||
const config = {
|
||||
message: '',
|
||||
type: 'info',
|
||||
duration: 3000,
|
||||
...options,
|
||||
duration: Math.max(1000, options.duration || 3000)
|
||||
};
|
||||
|
||||
const checkVueLoaded = setInterval(() => {
|
||||
if (window.Vue) {
|
||||
clearInterval(checkVueLoaded);
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.className = 'h5-message-container';
|
||||
document.body.appendChild(container);
|
||||
const app = Vue.createApp({
|
||||
template: messageTemplate,
|
||||
data() {
|
||||
return {
|
||||
message: config.message,
|
||||
type: config.type,
|
||||
visible: true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
iconClass() {
|
||||
const icons = {
|
||||
success: 'bi bi-check-circle',
|
||||
error: 'bi bi-exclamation-circle',
|
||||
warning: 'bi bi-exclamation-triangle',
|
||||
info: 'bi bi-info-circle'
|
||||
};
|
||||
return icons[this.type] || icons.info;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const showTimer = setTimeout(() => {
|
||||
this.visible = false;
|
||||
const hideTimer = setTimeout(() => {
|
||||
|
||||
const index = messageInstances.indexOf(app);
|
||||
if (index > -1) {
|
||||
messageInstances.splice(index, 1);
|
||||
}
|
||||
app.unmount(container);
|
||||
document.body.removeChild(container);
|
||||
clearTimeout(hideTimer);
|
||||
}, 400);
|
||||
|
||||
clearTimeout(showTimer);
|
||||
}, config.duration);
|
||||
}
|
||||
});
|
||||
const instance = app.mount(container);
|
||||
messageInstances.push(instance);
|
||||
}
|
||||
}, 50);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(checkVueLoaded);
|
||||
if (!window.Vue) {
|
||||
alert(config.message);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
|
||||
window.$message = showMessage;
|
||||
|
||||
['success', 'error', 'warning', 'info'].forEach(type => {
|
||||
window.$message[type] = (message, duration) => {
|
||||
window.$message({
|
||||
message,
|
||||
type,
|
||||
// 确保时长有效(用户未传则用默认3秒)
|
||||
duration: duration ? Math.max(1000, duration) : 500
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>教程展示</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
打包测试推送全部分支
|
||||
|
||||
</body>
|
||||
</html>
|
||||
593
src/main/resources/templates/admin/biz/feedback/index.html
Normal file
593
src/main/resources/templates/admin/biz/feedback/index.html
Normal file
@@ -0,0 +1,593 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>反馈管理</title>
|
||||
<!-- 外部引入库文件 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<!-- 外部引入自定义样式 -->
|
||||
<link rel="stylesheet" href="/assets/css/table.css">
|
||||
</head>
|
||||
<body id="app">
|
||||
<div class="main-container">
|
||||
<div class="table-container">
|
||||
<h4 class="mb-3">反馈管理</h4>
|
||||
|
||||
<div class="search-bar">
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
名称
|
||||
</span>
|
||||
<input
|
||||
v-model="searchParams.title"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="请输入名称"
|
||||
@keyup.enter="fetchData()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
流程
|
||||
</span>
|
||||
<select
|
||||
v-model="searchParams.status"
|
||||
class="form-select"
|
||||
@change="fetchData()"
|
||||
>
|
||||
<option value="">全部</option>
|
||||
<option value="0">待处理</option>
|
||||
<option value="1">处理中</option>
|
||||
<option value="2">已完成</option>
|
||||
<option value="3">已关闭</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary" @click="fetchData()">
|
||||
<i class="bi bi-search me-1"></i> 搜索
|
||||
</button>
|
||||
<button class="btn btn-success" @click="resetSearch()">
|
||||
<i class="bi bi-arrow-counterclockwise me-1"></i> 重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="mb-2">-->
|
||||
<!-- <button class="btn btn-info" @click="openAddModal()">-->
|
||||
<!-- <i class="bi bi-plus-circle me-1"></i> 新增-->
|
||||
<!-- </button>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped table-hover table-bordered ">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<!-- 全选复选框 -->
|
||||
<th style="width: 50px;">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="selectAll"
|
||||
@change="toggleSelectAll()"
|
||||
>
|
||||
</th>
|
||||
<th>反馈问题</th>
|
||||
<th>反馈详情</th>
|
||||
<th>反馈用户</th>
|
||||
<th>联系方式</th>
|
||||
<th>流程</th>
|
||||
<th>创建时间</th>
|
||||
<th style="width: 120px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 加载中状态 -->
|
||||
<tr v-if="loading">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="loading-container">
|
||||
<div class="spinner-border text-primary" role="status" style="width: 2rem; height: 2rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2 text-muted">加载中,请稍候...</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 无数据状态 -->
|
||||
<tr v-else-if="tableData.length === 0">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="no-data">
|
||||
<i class="bi bi-folder-x"></i>
|
||||
<h5>暂无匹配数据</h5>
|
||||
<p class="text-muted">请尝试调整搜索条件或重置查询</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<tr v-else v-for="(item, index) in tableData" :key="item.id"
|
||||
:class="{ selected: selectedIds.includes(item.id) }">
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="selectedIds"
|
||||
:value="item.id"
|
||||
@change="toggleSelectItem(item.id)"
|
||||
>
|
||||
</td>
|
||||
<td>{{ item.title }}</td>
|
||||
<td class="ellipsis-single">{{ item.content }}</td>
|
||||
<td>{{ item.nickName }}</td>
|
||||
<td>{{ item.contact }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="item.status === 0 ? 'bg-warning' : (item.status === 2 ? 'bg-success': (item.status === 3 ? 'bg-dark':'bg-info'))">
|
||||
{{ item.statusName }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatTime(item.createTime) }}</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-sm btn-primary" @click="openEditModal(item)">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" @click="handleDelete(item.id)">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="batch-actions mt-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="text-muted">已选中 {{ selectedIds.length }} 条数据</span>
|
||||
<select
|
||||
class="form-select"
|
||||
v-model="batchAction"
|
||||
:disabled="selectedIds.length === 0"
|
||||
@change="handleBatchOperation"
|
||||
style="width: auto; min-width: 160px;"
|
||||
>
|
||||
<option value="">-- 批量操作 --</option>
|
||||
<option value="delete">批量删除</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="clearSelected()"
|
||||
:disabled="selectedIds.length === 0"
|
||||
>
|
||||
<i class="bi bi-x-circle me-1"></i> 取消选择
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div class="page-info">
|
||||
共 {{ total }} 条数据,当前第 {{ pageNum }}/{{ totalPages }} 页
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(1)">首页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(pageNum - 1)">上一页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="page === pageNum ? 'active' : ''" v-for="page in pageList"
|
||||
:key="page">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(page)">{{ page }}</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(pageNum + 1)">下一页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(totalPages)">末页</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- 新增 or 编辑 -->
|
||||
<div class="modal fade" id="addOrEditModel" tabindex="-1" aria-labelledby="addOrEditModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="addOrEditModalLabel">{{ addOrEditTitle }}</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">反馈标题</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" v-model="addOrEditDto.title"
|
||||
placeholder="请输入名称">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">联系方式</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" v-model="addOrEditDto.contact"
|
||||
placeholder="请输入联系方式">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">反馈内容</label>
|
||||
<div class="col-sm-9">
|
||||
<!-- <!– 富文本编辑器容器 –>-->
|
||||
<!-- <div id="contentEditor" style="border: 1px solid #dee2e6; border-radius: 0.375rem;"></div>-->
|
||||
<!-- <!– 错误提示 –>-->
|
||||
<!-- <div class="invalid-feedback" id="contentError" style="display: none; margin-top: 0.25rem;"></div>-->
|
||||
<textarea class="form-control" v-model="addOrEditDto.content"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">流程</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" v-model="addOrEditDto.status">
|
||||
<option value="0">待处理</option>
|
||||
<option value="1">处理中</option>
|
||||
<option value="2">已完成</option>
|
||||
<option value="3">已关闭</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">反馈用户</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" readonly v-model="addOrEditDto.nickName">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
<button type="button" class="btn btn-primary" @click.prevent="saveEntity">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 外部引入库文件 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://unpkg.com/wangeditor@4.7.15/dist/wangEditor.min.js"></script>
|
||||
<!-- 外部引入模拟请求文件 -->
|
||||
<script src="/assets/js/axiosRequest.js"></script>
|
||||
<script src="/assets/js/message.js"></script>
|
||||
<script src="/assets/js/confirmModal.js"></script>
|
||||
|
||||
<!-- 页面核心逻辑 -->
|
||||
<script>
|
||||
const {createApp} = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
tableData: [], // 表格数据源
|
||||
loading: false, // 加载状态
|
||||
searchParams: {
|
||||
title: '',
|
||||
status: '',
|
||||
},
|
||||
// 分页参数
|
||||
pageNum: 1, // 当前页码
|
||||
pageSize: 10, // 每页条数
|
||||
total: 0, // 总数据量
|
||||
totalPages: 0, // 总页数
|
||||
// 批量操作
|
||||
selectedIds: [], // 选中的ID集合
|
||||
selectAll: false, // 全选状态
|
||||
batchAction: '', // 批量操作选择
|
||||
pageRange: 5,
|
||||
modalInstances: {},
|
||||
addOrEditTitle: '',
|
||||
addOrEditDto: {
|
||||
id: null,
|
||||
title: null,
|
||||
content: null,
|
||||
contact: null,
|
||||
status: 0,
|
||||
nickName: 0,
|
||||
},
|
||||
editor: null, // WangEditor 实例
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算当前显示的页码列表
|
||||
pageList() {
|
||||
const list = [];
|
||||
if (this.totalPages === 0) return list;
|
||||
|
||||
// 总页数小于等于显示范围,直接显示所有页码
|
||||
if (this.totalPages <= this.pageRange) {
|
||||
for (let i = 1; i <= this.totalPages; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
} else {
|
||||
// 总页数大于显示范围,显示当前页前后2个页码
|
||||
let start = Math.max(1, this.pageNum - 2);
|
||||
let end = Math.min(this.totalPages, this.pageNum + 2);
|
||||
|
||||
// 确保显示5个页码
|
||||
if (end - start < this.pageRange - 1) {
|
||||
if (start === 1) {
|
||||
end = this.pageRange;
|
||||
} else if (end === this.totalPages) {
|
||||
start = this.totalPages - this.pageRange + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatTime(time) {
|
||||
if (!time) return '-';
|
||||
const date = new Date(time);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
async fetchData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await request.get('/biz/feedback/page', {
|
||||
...this.searchParams,
|
||||
current: this.pageNum,
|
||||
size: this.pageSize
|
||||
});
|
||||
console.log(response)
|
||||
|
||||
if (response.code === 200) {
|
||||
this.tableData = response.data.records; // 列表数据
|
||||
this.total = response.data.total; // 总条数
|
||||
this.totalPages = response.data.pages; // 总页数
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error);
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
this.totalPages = 0;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.clearSelected();
|
||||
}
|
||||
},
|
||||
|
||||
// 切换页码
|
||||
changePage(page) {
|
||||
if (page < 1 || page > this.totalPages || page === this.pageNum) return;
|
||||
this.pageNum = page;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
resetSearch() {
|
||||
this.searchParams = {
|
||||
title: '',
|
||||
status: '',
|
||||
};
|
||||
this.pageNum = 1;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
// 全选/取消全选
|
||||
toggleSelectAll() {
|
||||
if (this.selectAll) {
|
||||
this.selectedIds = this.tableData.map(item => item.id);
|
||||
} else {
|
||||
this.selectedIds = [];
|
||||
}
|
||||
},
|
||||
|
||||
// 单个选中/取消选中
|
||||
toggleSelectItem(id) {
|
||||
this.selectAll = this.selectedIds.length === this.tableData.length && this.tableData.length > 0;
|
||||
},
|
||||
|
||||
// 清空选中状态
|
||||
clearSelected() {
|
||||
this.selectedIds = [];
|
||||
this.selectAll = false;
|
||||
this.batchAction = '';
|
||||
},
|
||||
|
||||
// 处理批量操作
|
||||
handleBatchOperation() {
|
||||
if (!this.batchAction || this.selectedIds.length === 0) return;
|
||||
|
||||
switch (this.batchAction) {
|
||||
case 'delete':
|
||||
this.handleBatchDelete();
|
||||
break;
|
||||
}
|
||||
|
||||
this.batchAction = '';
|
||||
},
|
||||
|
||||
// 批量删除
|
||||
async handleBatchDelete() {
|
||||
showConfirmModal({
|
||||
title: '删除确认',
|
||||
content: `确定要删除选中的 ${this.selectedIds.length} 条数据吗?`,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const response = await request.post('/biz/feedback/batchDelete', {ids: this.selectedIds});
|
||||
if (response.code === 200) {
|
||||
$message.success("删除成功");
|
||||
this.fetchData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
$message.error('删除失败,请重试!');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 单个删除
|
||||
async handleDelete(id) {
|
||||
showConfirmModal({
|
||||
title: '删除确认',
|
||||
content: '确定要删除这条数据吗?',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const response = await request.delete('/biz/feedback/delete', {id});
|
||||
if (response.code === 200) {
|
||||
$message.success("删除成功");
|
||||
this.fetchData(); // 重新加载数据
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
$message.error('删除失败,请重试!');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// 打开新增模态框
|
||||
openAddModal() {
|
||||
this.addOrEditTitle = '新增';
|
||||
this.clearForm();
|
||||
this.modalInstances['addOrEditModel'].show();
|
||||
},
|
||||
|
||||
// 打开编辑模态框
|
||||
openEditModal(item) {
|
||||
this.addOrEditTitle = '编辑';
|
||||
this.addOrEditDto = {...item};
|
||||
// 延迟回显富文本内容
|
||||
setTimeout(() => {
|
||||
if (this.editor) this.editor.txt.html(item.content || '');
|
||||
}, 300);
|
||||
this.modalInstances['addOrEditModel'].show();
|
||||
},
|
||||
saveEntity() {
|
||||
let url = (this.addOrEditDto === null || this.addOrEditDto.id === null) ? '/biz/feedback/save' : '/biz/feedback/update';
|
||||
request.post(url, this.addOrEditDto)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
$message.success('保存成功!', 500);
|
||||
this.fetchData();
|
||||
this.clearForm();
|
||||
this.modalInstances['addOrEditModel'].hide();
|
||||
} else {
|
||||
$message.error('保存失败!', 500);
|
||||
}
|
||||
|
||||
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
},
|
||||
// 清空表单数据
|
||||
clearForm() {
|
||||
this.addOrEditDto = {
|
||||
id: null,
|
||||
title: null,
|
||||
content: null,
|
||||
contact: null,
|
||||
status: 0,
|
||||
nickName: 0,
|
||||
};
|
||||
},
|
||||
// 初始化 WangEditor
|
||||
initWangEditor() {
|
||||
const _this = this;
|
||||
// 获取编辑器容器
|
||||
const editorDom = document.getElementById('contentEditor');
|
||||
// 创建编辑器实例
|
||||
this.editor = new wangEditor(editorDom);
|
||||
|
||||
// 编辑器配置
|
||||
this.editor.config.height = 200; // 高度
|
||||
this.editor.config.zIndex = 1050; // 层级(确保在模态框上方)
|
||||
this.editor.config.uploadImgShowBase64 = true; // 图片以 Base64 格式保存(无需后端接口)
|
||||
this.editor.config.menus = [
|
||||
'head', // 标题
|
||||
'bold', // 粗体
|
||||
'italic', // 斜体
|
||||
'underline', // 下划线
|
||||
'strikeThrough', // 删除线
|
||||
'foreColor', // 文字颜色
|
||||
'backColor', // 背景颜色
|
||||
'link', // 插入链接
|
||||
'list', // 列表
|
||||
'justify', // 对齐方式
|
||||
'image', // 插入图片
|
||||
'table', // 表格
|
||||
'code', // 插入代码
|
||||
'undo', // 撤销
|
||||
'redo' // 重做
|
||||
];
|
||||
|
||||
// 内容变化时同步到 Vue 数据
|
||||
this.editor.config.onchange = function (html) {
|
||||
_this.addOrEditDto.content = html;
|
||||
};
|
||||
|
||||
// 创建编辑器
|
||||
this.editor.create();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
const modalIds = ['uploadFileModal', 'addOrEditModel'];
|
||||
modalIds.forEach(id => {
|
||||
const modalElement = document.getElementById(id);
|
||||
if (modalElement) {
|
||||
this.modalInstances[id] = new bootstrap.Modal(modalElement, {
|
||||
backdrop: 'static',
|
||||
keyboard: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// // 初始化 WangEditor(延迟初始化,确保DOM加载完成)
|
||||
// setTimeout(() => {
|
||||
// this.initWangEditor();
|
||||
// }, 500);
|
||||
//
|
||||
// // 监听模态框关闭事件,避免内存泄漏
|
||||
// const modal = document.getElementById('addOrEditModel');
|
||||
// modal.addEventListener('hidden.bs.modal', () => {
|
||||
// if (this.editor) {
|
||||
// this.editor.txt.clear(); // 清空内容
|
||||
// document.getElementById('contentEditor').style.borderColor = '#dee2e6';
|
||||
// document.getElementById('contentError').style.display = 'none';
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
613
src/main/resources/templates/admin/biz/firmware/index.html
Normal file
613
src/main/resources/templates/admin/biz/firmware/index.html
Normal file
@@ -0,0 +1,613 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>固件管理</title>
|
||||
<!-- 外部引入库文件 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<!-- 外部引入自定义样式 -->
|
||||
<link rel="stylesheet" href="/assets/css/table.css">
|
||||
</head>
|
||||
<body id="app">
|
||||
<div class="main-container">
|
||||
<div class="table-container">
|
||||
<h4 class="mb-3">固件管理</h4>
|
||||
|
||||
<div class="search-bar">
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
固件名
|
||||
</span>
|
||||
<input
|
||||
v-model="searchParams.firmwareName"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="请输入固件名称"
|
||||
@keyup.enter="fetchData()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
类型
|
||||
</span>
|
||||
<select
|
||||
v-model="searchParams.firmwareType"
|
||||
class="form-select"
|
||||
@change="fetchData()"
|
||||
>
|
||||
<option value="">全部状态</option>
|
||||
<option value="1">调参固件</option>
|
||||
<option value="2">AP固件</option>
|
||||
<option value="3">INAV固件</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary" @click="fetchData()">
|
||||
<i class="bi bi-search me-1"></i> 搜索
|
||||
</button>
|
||||
<button class="btn btn-success" @click="resetSearch()">
|
||||
<i class="bi bi-arrow-counterclockwise me-1"></i> 重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<button class="btn btn-info" @click="openAddModal()">
|
||||
<i class="bi bi-plus-circle me-1"></i> 新增
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped table-hover table-bordered ">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<!-- 全选复选框 -->
|
||||
<th style="width: 50px;">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="selectAll"
|
||||
@change="toggleSelectAll()"
|
||||
>
|
||||
</th>
|
||||
<th>固件名</th>
|
||||
<th>大小(字节)</th>
|
||||
<th>描述</th>
|
||||
<th>类型</th>
|
||||
<th>创建时间</th>
|
||||
<th style="width: 120px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 加载中状态 -->
|
||||
<tr v-if="loading">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="loading-container">
|
||||
<div class="spinner-border text-primary" role="status" style="width: 2rem; height: 2rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2 text-muted">加载中,请稍候...</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 无数据状态 -->
|
||||
<tr v-else-if="tableData.length === 0">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="no-data">
|
||||
<i class="bi bi-folder-x"></i>
|
||||
<h5>暂无匹配数据</h5>
|
||||
<p class="text-muted">请尝试调整搜索条件或重置查询</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<tr v-else v-for="(item, index) in tableData" :key="item.id"
|
||||
:class="{ selected: selectedIds.includes(item.id) }">
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="selectedIds"
|
||||
:value="item.id"
|
||||
@change="toggleSelectItem(item.id)"
|
||||
>
|
||||
</td>
|
||||
<td>{{ item.firmwareName }}</td>
|
||||
<td>{{ item.firmwareSize }}</td>
|
||||
<td>{{ item.firmwareDescription }}</td>
|
||||
<td>{{ item.firmwareTypeName }}</td>
|
||||
<td>{{ formatTime(item.createTime) }}</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-sm btn-primary" @click="openUploadModal(item)" title="上传固件">
|
||||
<i class="bi bi-cloud-upload-fill"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary" @click="openEditModal(item)">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" @click="handleDelete(item.id)">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="batch-actions mt-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="text-muted">已选中 {{ selectedIds.length }} 条数据</span>
|
||||
<select
|
||||
class="form-select"
|
||||
v-model="batchAction"
|
||||
:disabled="selectedIds.length === 0"
|
||||
@change="handleBatchOperation"
|
||||
style="width: auto; min-width: 160px;"
|
||||
>
|
||||
<option value="">-- 批量操作 --</option>
|
||||
<option value="delete">批量删除</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="clearSelected()"
|
||||
:disabled="selectedIds.length === 0"
|
||||
>
|
||||
<i class="bi bi-x-circle me-1"></i> 取消选择
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div class="page-info">
|
||||
共 {{ total }} 条数据,当前第 {{ pageNum }}/{{ totalPages }} 页
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(1)">首页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(pageNum - 1)">上一页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="page === pageNum ? 'active' : ''" v-for="page in pageList"
|
||||
:key="page">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(page)">{{ page }}</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(pageNum + 1)">下一页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(totalPages)">末页</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 上传文件 -->
|
||||
<div class="modal fade" id="uploadFileModal" tabindex="-1" aria-labelledby="uploadFileModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="uploadFileModalLabel">上传固件</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">选择固件</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
id="firmwareFile"
|
||||
name="file"
|
||||
accept=".bin"
|
||||
ref="firmwareFileRef"
|
||||
>
|
||||
<div class="form-text text-muted mt-1">支持 .bin 格式,单个文件不超过 100MB</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
<button type="button" class="btn btn-primary" @click.prevent="uploadFile">上传</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 新增 or 编辑 -->
|
||||
<div class="modal fade" id="addOrEditModel" tabindex="-1" aria-labelledby="addOrEditModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="addOrEditModalLabel">{{ addOrEditTitle }}</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">固件名称</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" v-model="addOrEditDto.firmwareName"
|
||||
placeholder="请输入固件名称">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">固件描述</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" v-model="addOrEditDto.firmwareDescription"
|
||||
placeholder="请输入固件描述">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">固件类型</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" v-model="addOrEditDto.firmwareType">
|
||||
<option value=1>调参固件</option>
|
||||
<option value=2>AP固件</option>
|
||||
<option value=3>INAV固件</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">固件地址</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" v-model="addOrEditDto.downloadUrl"
|
||||
placeholder="请输入固件地址">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
<button type="button" class="btn btn-primary" @click.prevent="saveEntity">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 外部引入库文件 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- 外部引入模拟请求文件 -->
|
||||
<script src="/assets/js/axiosRequest.js"></script>
|
||||
<script src="/assets/js/message.js"></script>
|
||||
<script src="/assets/js/confirmModal.js"></script>
|
||||
|
||||
<!-- 页面核心逻辑 -->
|
||||
<script>
|
||||
const {createApp} = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
tableData: [], // 表格数据源
|
||||
loading: false, // 加载状态
|
||||
searchParams: {
|
||||
firmwareName: '',
|
||||
firmwareType: '',
|
||||
},
|
||||
// 分页参数
|
||||
pageNum: 1, // 当前页码
|
||||
pageSize: 10, // 每页条数
|
||||
total: 0, // 总数据量
|
||||
totalPages: 0, // 总页数
|
||||
// 批量操作
|
||||
selectedIds: [], // 选中的ID集合
|
||||
selectAll: false, // 全选状态
|
||||
batchAction: '', // 批量操作选择
|
||||
pageRange: 5,
|
||||
modalInstances: {},
|
||||
resetPasswordDto: {
|
||||
userId: null,
|
||||
nickName: null,
|
||||
username: null,
|
||||
password: null,
|
||||
},
|
||||
addOrEditTitle: '',
|
||||
addOrEditDto: {
|
||||
id: null,
|
||||
firmwareName: null,
|
||||
firmwareDescription: null,
|
||||
firmwareType: 1,
|
||||
downloadUrl: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算当前显示的页码列表
|
||||
pageList() {
|
||||
const list = [];
|
||||
if (this.totalPages === 0) return list;
|
||||
|
||||
// 总页数小于等于显示范围,直接显示所有页码
|
||||
if (this.totalPages <= this.pageRange) {
|
||||
for (let i = 1; i <= this.totalPages; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
} else {
|
||||
// 总页数大于显示范围,显示当前页前后2个页码
|
||||
let start = Math.max(1, this.pageNum - 2);
|
||||
let end = Math.min(this.totalPages, this.pageNum + 2);
|
||||
|
||||
// 确保显示5个页码
|
||||
if (end - start < this.pageRange - 1) {
|
||||
if (start === 1) {
|
||||
end = this.pageRange;
|
||||
} else if (end === this.totalPages) {
|
||||
start = this.totalPages - this.pageRange + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatTime(time) {
|
||||
if (!time) return '-';
|
||||
const date = new Date(time);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
async fetchData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await request.get('/biz/firmware/page', {
|
||||
...this.searchParams,
|
||||
current: this.pageNum,
|
||||
size: this.pageSize
|
||||
});
|
||||
console.log(response)
|
||||
|
||||
if (response.code === 200) {
|
||||
this.tableData = response.data.records; // 列表数据
|
||||
this.total = response.data.total; // 总条数
|
||||
this.totalPages = response.data.pages; // 总页数
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error);
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
this.totalPages = 0;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.clearSelected();
|
||||
}
|
||||
},
|
||||
|
||||
// 切换页码
|
||||
changePage(page) {
|
||||
if (page < 1 || page > this.totalPages || page === this.pageNum) return;
|
||||
this.pageNum = page;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
resetSearch() {
|
||||
|
||||
this.searchParams = {
|
||||
firmwareName: '',
|
||||
firmwareType: '',
|
||||
};
|
||||
this.pageNum = 1;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
// 全选/取消全选
|
||||
toggleSelectAll() {
|
||||
if (this.selectAll) {
|
||||
this.selectedIds = this.tableData.map(item => item.id);
|
||||
} else {
|
||||
this.selectedIds = [];
|
||||
}
|
||||
},
|
||||
|
||||
// 单个选中/取消选中
|
||||
toggleSelectItem(id) {
|
||||
this.selectAll = this.selectedIds.length === this.tableData.length && this.tableData.length > 0;
|
||||
},
|
||||
|
||||
// 清空选中状态
|
||||
clearSelected() {
|
||||
this.selectedIds = [];
|
||||
this.selectAll = false;
|
||||
this.batchAction = '';
|
||||
},
|
||||
|
||||
// 处理批量操作
|
||||
handleBatchOperation() {
|
||||
if (!this.batchAction || this.selectedIds.length === 0) return;
|
||||
|
||||
switch (this.batchAction) {
|
||||
case 'delete':
|
||||
this.handleBatchDelete();
|
||||
break;
|
||||
}
|
||||
|
||||
this.batchAction = '';
|
||||
},
|
||||
|
||||
// 批量删除
|
||||
async handleBatchDelete() {
|
||||
showConfirmModal({
|
||||
title: '删除确认',
|
||||
content: `确定要删除选中的 ${this.selectedIds.length} 条数据吗?`,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const response = await request.post('/biz/firmware/batchDelete', {ids: this.selectedIds});
|
||||
if (response.code === 200) {
|
||||
$message.success("删除成功");
|
||||
this.fetchData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
$message.error('删除失败,请重试!');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 单个删除
|
||||
async handleDelete(id) {
|
||||
showConfirmModal({
|
||||
title: '删除确认',
|
||||
content: '确定要删除这条数据吗?',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const response = await request.delete('/biz/firmware/delete', {id});
|
||||
if (response.code === 200) {
|
||||
$message.success("删除成功");
|
||||
this.fetchData(); // 重新加载数据
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
$message.error('删除失败,请重试!');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// 打开文件上传模态框
|
||||
openUploadModal(item) {
|
||||
this.addOrEditDto = item;
|
||||
this.modalInstances['uploadFileModal'].show();
|
||||
},
|
||||
async uploadFile() {
|
||||
// 1. 获取选中的文件
|
||||
const fileInput = this.$refs.firmwareFileRef;
|
||||
const file = fileInput.files[0];
|
||||
|
||||
// 2. 校验文件(格式、大小)
|
||||
if (!file) {
|
||||
alert("请选择 .bin 格式的固件文件");
|
||||
return;
|
||||
}
|
||||
if (file.type !== "application/octet-stream" && !file.name.endsWith(".bin")) {
|
||||
alert("仅支持 .bin 格式文件");
|
||||
return;
|
||||
}
|
||||
if (file.size > 100 * 1024 * 1024) { // 100MB
|
||||
alert("文件大小不能超过 100MB");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("id", this.addOrEditDto.id);
|
||||
|
||||
// 4. 发送上传请求(Axios)
|
||||
try {
|
||||
const response = await axios({
|
||||
url: "/biz/firmware/uploadFile",
|
||||
method: "POST",
|
||||
data: formData,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const progress = (progressEvent.loaded / progressEvent.total) * 100;
|
||||
console.log(`上传进度:${progress.toFixed(2)}%`);
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
$message.success('固件上传成功');
|
||||
this.$refs.firmwareFileRef.value = "";
|
||||
this.modalInstances['uploadFileModal'].hide();
|
||||
} else {
|
||||
$message.error("上传失败:" + response.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("上传出错:", error);
|
||||
$message.error("网络异常,上传失败");
|
||||
}
|
||||
|
||||
},
|
||||
// 打开新增模态框
|
||||
openAddModal() {
|
||||
this.addOrEditTitle = '新增';
|
||||
this.modalInstances['addOrEditModel'].show();
|
||||
},
|
||||
|
||||
// 打开编辑模态框
|
||||
openEditModal(item) {
|
||||
this.addOrEditTitle = '编辑';
|
||||
this.addOrEditDto = item;
|
||||
this.modalInstances['addOrEditModel'].show();
|
||||
},
|
||||
saveEntity() {
|
||||
let url = (this.addOrEditDto === null || this.addOrEditDto.id === null) ? '/biz/firmware/save' : '/biz/firmware/update';
|
||||
request.post(url, this.addOrEditDto)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
$message.success('保存成功!', 500);
|
||||
this.fetchData();
|
||||
this.modalInstances['addOrEditModel'].hide();
|
||||
} else {
|
||||
$message.error('保存失败!', 500);
|
||||
}
|
||||
this.clearForm();
|
||||
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
},
|
||||
// 清空表单数据
|
||||
clearForm() {
|
||||
this.addOrEditDto = {
|
||||
id: null,
|
||||
firmwareName: null,
|
||||
firmwareDescription: null,
|
||||
firmwareType: 1,
|
||||
downloadUrl: ''
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
const modalIds = ['uploadFileModal', 'addOrEditModel'];
|
||||
modalIds.forEach(id => {
|
||||
const modalElement = document.getElementById(id);
|
||||
if (modalElement) {
|
||||
this.modalInstances[id] = new bootstrap.Modal(modalElement, {
|
||||
backdrop: 'static',
|
||||
keyboard: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
581
src/main/resources/templates/admin/biz/privacyPolicy/index.html
Normal file
581
src/main/resources/templates/admin/biz/privacyPolicy/index.html
Normal file
@@ -0,0 +1,581 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>隐私政策</title>
|
||||
<!-- 外部引入库文件 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<!-- 外部引入自定义样式 -->
|
||||
<link rel="stylesheet" href="/assets/css/table.css">
|
||||
</head>
|
||||
<body id="app">
|
||||
<div class="main-container">
|
||||
<div class="table-container">
|
||||
<h4 class="mb-3">隐私政策</h4>
|
||||
|
||||
<div class="search-bar">
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
名称
|
||||
</span>
|
||||
<input
|
||||
v-model="searchParams.title"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="请输入名称"
|
||||
@keyup.enter="fetchData()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
是否显示
|
||||
</span>
|
||||
<select
|
||||
v-model="searchParams.visible"
|
||||
class="form-select"
|
||||
@change="fetchData()"
|
||||
>
|
||||
<option value="">全部状态</option>
|
||||
<option value="1">显示</option>
|
||||
<option value="2">隐藏</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary" @click="fetchData()">
|
||||
<i class="bi bi-search me-1"></i> 搜索
|
||||
</button>
|
||||
<button class="btn btn-success" @click="resetSearch()">
|
||||
<i class="bi bi-arrow-counterclockwise me-1"></i> 重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<button class="btn btn-info" @click="openAddModal()">
|
||||
<i class="bi bi-plus-circle me-1"></i> 新增
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped table-hover table-bordered ">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<!-- 全选复选框 -->
|
||||
<th style="width: 50px;">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="selectAll"
|
||||
@change="toggleSelectAll()"
|
||||
>
|
||||
</th>
|
||||
<th>名称</th>
|
||||
<th>是否显示</th>
|
||||
<th>排序</th>
|
||||
<th>创建时间</th>
|
||||
<th style="width: 120px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 加载中状态 -->
|
||||
<tr v-if="loading">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="loading-container">
|
||||
<div class="spinner-border text-primary" role="status" style="width: 2rem; height: 2rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2 text-muted">加载中,请稍候...</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 无数据状态 -->
|
||||
<tr v-else-if="tableData.length === 0">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="no-data">
|
||||
<i class="bi bi-folder-x"></i>
|
||||
<h5>暂无匹配数据</h5>
|
||||
<p class="text-muted">请尝试调整搜索条件或重置查询</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<tr v-else v-for="(item, index) in tableData" :key="item.id"
|
||||
:class="{ selected: selectedIds.includes(item.id) }">
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="selectedIds"
|
||||
:value="item.id"
|
||||
@change="toggleSelectItem(item.id)"
|
||||
>
|
||||
</td>
|
||||
<td>{{ item.title }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="item.visible === 1 ? 'bg-success' : 'bg-danger'">
|
||||
{{ item.visible === 1 ? '启用' : '禁用' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ item.sort }}</td>
|
||||
<td>{{ formatTime(item.createTime) }}</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-sm btn-primary" @click="openEditModal(item)">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" @click="handleDelete(item.id)">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="batch-actions mt-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="text-muted">已选中 {{ selectedIds.length }} 条数据</span>
|
||||
<select
|
||||
class="form-select"
|
||||
v-model="batchAction"
|
||||
:disabled="selectedIds.length === 0"
|
||||
@change="handleBatchOperation"
|
||||
style="width: auto; min-width: 160px;"
|
||||
>
|
||||
<option value="">-- 批量操作 --</option>
|
||||
<option value="delete">批量删除</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="clearSelected()"
|
||||
:disabled="selectedIds.length === 0"
|
||||
>
|
||||
<i class="bi bi-x-circle me-1"></i> 取消选择
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div class="page-info">
|
||||
共 {{ total }} 条数据,当前第 {{ pageNum }}/{{ totalPages }} 页
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(1)">首页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(pageNum - 1)">上一页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="page === pageNum ? 'active' : ''" v-for="page in pageList"
|
||||
:key="page">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(page)">{{ page }}</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(pageNum + 1)">下一页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(totalPages)">末页</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- 新增 or 编辑 -->
|
||||
<div class="modal fade" id="addOrEditModel" tabindex="-1" aria-labelledby="addOrEditModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="addOrEditModalLabel">{{ addOrEditTitle }}</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">名称</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" v-model="addOrEditDto.title"
|
||||
placeholder="请输入名称">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">内容</label>
|
||||
<div class="col-sm-9">
|
||||
<!-- 富文本编辑器容器 -->
|
||||
<div id="contentEditor" style="border: 1px solid #dee2e6; border-radius: 0.375rem;"></div>
|
||||
<!-- 错误提示 -->
|
||||
<div class="invalid-feedback" id="contentError" style="display: none; margin-top: 0.25rem;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">是否显示</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" v-model="addOrEditDto.visible">
|
||||
<option value=1>显示</option>
|
||||
<option value=2>隐藏</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 row">
|
||||
<label class="col-sm-3 col-form-label">序号</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="number" class="form-control" v-model="addOrEditDto.sort"
|
||||
placeholder="请输入序号">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
<button type="button" class="btn btn-primary" @click.prevent="saveEntity">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 外部引入库文件 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://unpkg.com/wangeditor@4.7.15/dist/wangEditor.min.js"></script>
|
||||
<!-- 外部引入模拟请求文件 -->
|
||||
<script src="/assets/js/axiosRequest.js"></script>
|
||||
<script src="/assets/js/message.js"></script>
|
||||
<script src="/assets/js/confirmModal.js"></script>
|
||||
|
||||
<!-- 页面核心逻辑 -->
|
||||
<script>
|
||||
const {createApp} = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
tableData: [], // 表格数据源
|
||||
loading: false, // 加载状态
|
||||
searchParams: {
|
||||
title: '',
|
||||
visible: '',
|
||||
},
|
||||
// 分页参数
|
||||
pageNum: 1, // 当前页码
|
||||
pageSize: 10, // 每页条数
|
||||
total: 0, // 总数据量
|
||||
totalPages: 0, // 总页数
|
||||
// 批量操作
|
||||
selectedIds: [], // 选中的ID集合
|
||||
selectAll: false, // 全选状态
|
||||
batchAction: '', // 批量操作选择
|
||||
pageRange: 5,
|
||||
modalInstances: {},
|
||||
resetPasswordDto: {
|
||||
userId: null,
|
||||
nickName: null,
|
||||
username: null,
|
||||
password: null,
|
||||
},
|
||||
addOrEditTitle: '',
|
||||
addOrEditDto: {
|
||||
id: null,
|
||||
title: null,
|
||||
content: null,
|
||||
visible: 1,
|
||||
sort: 99
|
||||
},
|
||||
editor: null, // WangEditor 实例
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算当前显示的页码列表
|
||||
pageList() {
|
||||
const list = [];
|
||||
if (this.totalPages === 0) return list;
|
||||
|
||||
// 总页数小于等于显示范围,直接显示所有页码
|
||||
if (this.totalPages <= this.pageRange) {
|
||||
for (let i = 1; i <= this.totalPages; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
} else {
|
||||
// 总页数大于显示范围,显示当前页前后2个页码
|
||||
let start = Math.max(1, this.pageNum - 2);
|
||||
let end = Math.min(this.totalPages, this.pageNum + 2);
|
||||
|
||||
// 确保显示5个页码
|
||||
if (end - start < this.pageRange - 1) {
|
||||
if (start === 1) {
|
||||
end = this.pageRange;
|
||||
} else if (end === this.totalPages) {
|
||||
start = this.totalPages - this.pageRange + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatTime(time) {
|
||||
if (!time) return '-';
|
||||
const date = new Date(time);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
async fetchData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await request.get('/biz/privacy_policy/page', {
|
||||
...this.searchParams,
|
||||
current: this.pageNum,
|
||||
size: this.pageSize
|
||||
});
|
||||
console.log(response)
|
||||
|
||||
if (response.code === 200) {
|
||||
this.tableData = response.data.records; // 列表数据
|
||||
this.total = response.data.total; // 总条数
|
||||
this.totalPages = response.data.pages; // 总页数
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error);
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
this.totalPages = 0;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.clearSelected();
|
||||
}
|
||||
},
|
||||
|
||||
// 切换页码
|
||||
changePage(page) {
|
||||
if (page < 1 || page > this.totalPages || page === this.pageNum) return;
|
||||
this.pageNum = page;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
resetSearch() {
|
||||
this.searchParams = {
|
||||
title: '',
|
||||
visible: '',
|
||||
};
|
||||
this.pageNum = 1;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
// 全选/取消全选
|
||||
toggleSelectAll() {
|
||||
if (this.selectAll) {
|
||||
this.selectedIds = this.tableData.map(item => item.id);
|
||||
} else {
|
||||
this.selectedIds = [];
|
||||
}
|
||||
},
|
||||
|
||||
// 单个选中/取消选中
|
||||
toggleSelectItem(id) {
|
||||
this.selectAll = this.selectedIds.length === this.tableData.length && this.tableData.length > 0;
|
||||
},
|
||||
|
||||
// 清空选中状态
|
||||
clearSelected() {
|
||||
this.selectedIds = [];
|
||||
this.selectAll = false;
|
||||
this.batchAction = '';
|
||||
},
|
||||
|
||||
// 处理批量操作
|
||||
handleBatchOperation() {
|
||||
if (!this.batchAction || this.selectedIds.length === 0) return;
|
||||
|
||||
switch (this.batchAction) {
|
||||
case 'delete':
|
||||
this.handleBatchDelete();
|
||||
break;
|
||||
}
|
||||
|
||||
this.batchAction = '';
|
||||
},
|
||||
|
||||
// 批量删除
|
||||
async handleBatchDelete() {
|
||||
showConfirmModal({
|
||||
title: '删除确认',
|
||||
content: `确定要删除选中的 ${this.selectedIds.length} 条数据吗?`,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const response = await request.post('/biz/privacy_policy/batchDelete', {ids: this.selectedIds});
|
||||
if (response.code === 200) {
|
||||
$message.success("删除成功");
|
||||
this.fetchData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
$message.error('删除失败,请重试!');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 单个删除
|
||||
async handleDelete(id) {
|
||||
showConfirmModal({
|
||||
title: '删除确认',
|
||||
content: '确定要删除这条数据吗?',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const response = await request.delete('/biz/privacy_policy/delete', {id});
|
||||
if (response.code === 200) {
|
||||
$message.success("删除成功");
|
||||
this.fetchData(); // 重新加载数据
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
$message.error('删除失败,请重试!');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// 打开新增模态框
|
||||
openAddModal() {
|
||||
this.addOrEditTitle = '新增';
|
||||
this.clearForm();
|
||||
this.modalInstances['addOrEditModel'].show();
|
||||
},
|
||||
|
||||
// 打开编辑模态框
|
||||
openEditModal(item) {
|
||||
this.addOrEditTitle = '编辑';
|
||||
this.addOrEditDto = {...item};
|
||||
// 延迟回显富文本内容
|
||||
setTimeout(() => {
|
||||
if (this.editor) this.editor.txt.html(item.content || '');
|
||||
}, 300);
|
||||
this.modalInstances['addOrEditModel'].show();
|
||||
},
|
||||
saveEntity() {
|
||||
let url = (this.addOrEditDto === null || this.addOrEditDto.id === null) ? '/biz/privacy_policy/save' : '/biz/privacy_policy/update';
|
||||
request.post(url, this.addOrEditDto)
|
||||
.then((res) => {
|
||||
if (res.code === 200) {
|
||||
$message.success('保存成功!', 500);
|
||||
this.fetchData();
|
||||
this.modalInstances['addOrEditModel'].hide();
|
||||
} else {
|
||||
$message.error('保存失败!', 500);
|
||||
}
|
||||
this.clearForm();
|
||||
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
},
|
||||
// 清空表单数据
|
||||
clearForm() {
|
||||
this.addOrEditDto = {
|
||||
id: null,
|
||||
title: null,
|
||||
content: null,
|
||||
visible: 1,
|
||||
sort: 99
|
||||
};
|
||||
},
|
||||
// 初始化 WangEditor
|
||||
initWangEditor() {
|
||||
const _this = this;
|
||||
// 获取编辑器容器
|
||||
const editorDom = document.getElementById('contentEditor');
|
||||
// 创建编辑器实例
|
||||
this.editor = new wangEditor(editorDom);
|
||||
|
||||
// 编辑器配置
|
||||
this.editor.config.height = 200; // 高度
|
||||
this.editor.config.zIndex = 1050; // 层级(确保在模态框上方)
|
||||
this.editor.config.uploadImgShowBase64 = true; // 图片以 Base64 格式保存(无需后端接口)
|
||||
this.editor.config.menus = [
|
||||
'head', // 标题
|
||||
'bold', // 粗体
|
||||
'italic', // 斜体
|
||||
'underline', // 下划线
|
||||
'strikeThrough', // 删除线
|
||||
'foreColor', // 文字颜色
|
||||
'backColor', // 背景颜色
|
||||
'link', // 插入链接
|
||||
'list', // 列表
|
||||
'justify', // 对齐方式
|
||||
'image', // 插入图片
|
||||
'table', // 表格
|
||||
'code', // 插入代码
|
||||
'undo', // 撤销
|
||||
'redo' // 重做
|
||||
];
|
||||
|
||||
// 内容变化时同步到 Vue 数据
|
||||
this.editor.config.onchange = function (html) {
|
||||
_this.addOrEditDto.content = html;
|
||||
};
|
||||
|
||||
// 创建编辑器
|
||||
this.editor.create();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
const modalIds = ['uploadFileModal', 'addOrEditModel'];
|
||||
modalIds.forEach(id => {
|
||||
const modalElement = document.getElementById(id);
|
||||
if (modalElement) {
|
||||
this.modalInstances[id] = new bootstrap.Modal(modalElement, {
|
||||
backdrop: 'static',
|
||||
keyboard: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化 WangEditor(延迟初始化,确保DOM加载完成)
|
||||
setTimeout(() => {
|
||||
this.initWangEditor();
|
||||
}, 500);
|
||||
|
||||
// 监听模态框关闭事件,避免内存泄漏
|
||||
const modal = document.getElementById('addOrEditModel');
|
||||
modal.addEventListener('hidden.bs.modal', () => {
|
||||
if (this.editor) {
|
||||
this.editor.txt.clear(); // 清空内容
|
||||
document.getElementById('contentEditor').style.borderColor = '#dee2e6';
|
||||
document.getElementById('contentError').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user