feat: collaboration and statistics

This commit is contained in:
gin
2026-05-15 09:19:09 +08:00
parent cdee21ee8e
commit 2757a4fb49
91 changed files with 4504 additions and 1301 deletions
+1 -1
View File
@@ -5,6 +5,7 @@ root = true
charset = utf-8
indent_style = space
indent_size = 2
tab_width = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
@@ -12,4 +13,3 @@ trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
+71
View File
@@ -0,0 +1,71 @@
# Common
.DS_Store
.idea/
*.iws
*.iml
*.ipr
*.suo
*.ntvs*
*.njsproj
*.sln
*.log
*.swp
# Frontend workspace
/frontend/**/node_modules/
/frontend/**/.eslintcache
/frontend/**/tsconfig.tsbuildinfo
/frontend/**/*.local
# Frontend app
/frontend/app/dist/
/frontend/app/deploy_versions/
/frontend/app/.temp/
/frontend/app/.rn_temp/
/frontend/app/.swc
# Frontend web
/frontend/web/dist/
/frontend/web/dist-ssr/
/frontend/web/report.html
/frontend/web/yarn.lock
/frontend/web/npm-debug.log*
/frontend/web/.pnpm-error.log*
/frontend/web/.pnpm-debug.log
/frontend/web/tests/**/coverage/
/frontend/web/.vscode/launch.json
# Backend build tools
/backend/.gradle/
/backend/build/
!/backend/gradle/wrapper/gradle-wrapper.jar
/backend/**/target/
!/backend/.mvn/wrapper/maven-wrapper.jar
# Backend IDE
/backend/**/.apt_generated
/backend/**/.classpath
/backend/**/.factorypath
/backend/**/.project
/backend/**/.settings/
/backend/**/.springBeans
# Backend JRebel
/backend/**/rebel.xml
# Backend NetBeans
/backend/nbproject/private/
/backend/build/*
/backend/nbbuild/
/backend/dist/
/backend/nbdist/
/backend/.nb-gradle/
# Backend generated files
/backend/**/*.xml.versionsBackup
!/backend/*/build/*.java
!/backend/*/build/*.html
!/backend/*/build/*.xml
# Backend local configuration
/backend/agileboot-admin/src/main/resources/application-prod.yml
+12
View File
@@ -0,0 +1,12 @@
{
"recommendations": [
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"Vue.volar",
"bradlc.vscode-tailwindcss",
"mikestead.dotenv",
"antfu.iconify"
]
}
+26 -15
View File
@@ -1,32 +1,43 @@
{
"editor.formatOnType": true,
"editor.formatOnSave": true,
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.detectIndentation": false,
"editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnType": true,
"editor.guides.bracketPairs": "active",
"files.autoSave": "afterDelay",
"git.confirmSync": false,
"workbench.startupEditor": "newUntitledFile",
"editor.snippetSuggestions": "top",
"editor.suggestSelection": "first",
"editor.acceptSuggestionOnCommitCharacter": false,
"css.lint.propertyIgnoredDueToDisplay": "ignore",
"editor.quickSuggestions": {
"other": true,
"comments": true,
"strings": true
},
"files.associations": {
"editor.snippetSuggestions": "top"
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "vue"],
"stylelint.validate": ["css", "scss", "vue"],
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"iconify.excludes": ["el"],
"cSpell.words": ["iconify", "Qrcode"]
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"css.lint.propertyIgnoredDueToDisplay": "ignore",
"files.autoSave": "afterDelay",
"git.confirmSync": false,
"workbench.startupEditor": "newUntitledFile",
"iconify.excludes": ["el"]
}
+15
View File
@@ -0,0 +1,15 @@
# Agent Rules
- 新增或修改代码前,必须阅读并遵循 `docs/clean-code-contract.md`
- 后端新增或修改业务功能前,必须阅读 `docs/backend-feature-development.md`
- 后端代码必须遵循 `Controller -> ApplicationService -> Model -> db Service/Mapper` 的组织方式。
- 不要把业务规则写在 Controller。
- 不要让 Controller 直接调用 Mapper。
- 不要直接把 Entity 或 DO 返回给前端。
- 复杂查询结果对象可以使用 `XxxDO`,放在 `db` 包下,并由 ApplicationService 转换为 DTO。
- 字典类需求不要默认创建字典管理表;本项目现有字典数据使用 Enum 和缓存。
- `frontend/web` 新增或修改业务功能前,必须阅读 `docs/web-feature-development.md`
- Web 前端接口必须通过 `@/utils/http` 封装调用,不要直接使用 Axios。
- Web 列表页复杂状态和行为应放在 `utils/hook.tsx`,不要堆在 `index.vue`
- Web 页面私有组件放在当前模块的 `components/`,多模块复用组件提升到 `frontend/web/src/components`
- Web 字典展示优先使用 `useUserStoreHook().dictionaryList``dictionaryMap`,不要硬编码状态文本和值。
+26 -3
View File
@@ -34,8 +34,31 @@ cd backend
docker compose up -d
```
On a fresh Docker volume, Compose creates the MySQL database `agileboot_pure`.
Import the SQL files under `backend/sql/` before starting the backend.
The compose configuration mounts `backend/sql/agileboot.sql` into the MySQL
container as `/docker-entrypoint-initdb.d/01-agileboot.sql`.
The official MySQL image only runs files in `/docker-entrypoint-initdb.d` when
the database directory is empty, during the first initialization of the
`mysql_data` volume.
If MySQL has already been started before, the `mysql_data` volume already
contains data and `docker compose up -d` will not import the SQL again. To
reinitialize the local database, remove the volumes first:
```bash
cd backend
docker compose down -v
docker compose up -d
```
Warning: `docker compose down -v` deletes the local MySQL and Redis volumes,
including existing database data and Redis data.
If you do not want to delete the volumes, import the SQL manually:
```bash
cd backend
docker exec -i mysql-server mysql -uroot -proot123 agileboot_pure < sql/agileboot.sql
```
### Install Frontend Dependencies
@@ -50,7 +73,7 @@ Start the backend:
```bash
cd backend
./mvnw -pl agileboot-admin -am spring-boot:run
./mvnw -pl agileboot-admin spring-boot:run
```
Start the web frontend:
-36
View File
@@ -1,36 +0,0 @@
---
name: Bug 报告
about: 创建BUG报告以改进项目
title: ''
labels: ''
assignees: ''
---
**BUG描述**
关于BUG清晰简洁的描述。
**复现步骤**
详细的复现步骤。
**正确的行为**
你认为这个修复这个BUG后,正确的行为应该是什么。
**详细截图**
如果可以的话,请添加截图以帮助调查BUG.
**桌面端:**
- 操作系统: [例如. iOS]
- 浏览器及版本 [例如. chrome 11]
- 项目版本 [例如. 1.6.0]
**手机端:**
- 设备: [例如. iPhone6]
- 操作系统: [例如. iOS8.1]
- 浏览器及版本 [例如.safari 8]
- 项目版本 [例如. 1.6.0]
**Additional context**
任何其他你认为有助于排查错误的信息,或者你的猜测。
-20
View File
@@ -1,20 +0,0 @@
---
name: 功能建议
about: 关于该项目的建议
title: ''
labels: ''
assignees: ''
---
**您的功能请求是否与问题相关? 请描述。**
清楚简明地描述问题所在。
**描述您想要的解决方案**
对您所设想的问题的清晰简洁的描述。
**描述您考虑过的替代方案**
对您考虑过的任何替代解决方案或功能的清晰简洁的描述。
**附加上下文**
在此处添加有关功能请求的任何其他上下文或屏幕截图。
-116
View File
@@ -1,116 +0,0 @@
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
# 权限声明,确保 workflow 有权限写 checks 和 security-events
permissions:
contents: read
checks: write
security-events: write
name: Java CI with Maven
on:
push:
branches: [ "main" ]
paths-ignore:
- 'README.md'
- 'LICENSE'
- '.gitignore'
- '.gitattributes'
- 'picture'
pull_request:
branches: [ "main" ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
java-version: ['8', '17', '21']
fail-fast: false
name: Build with Java ${{ matrix.java-version }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
cache: 'maven'
# 优化Maven本地仓库缓存策略
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}-${{ matrix.java-version }}
restore-keys: |
${{ runner.os }}-m2-
# 编译和测试:去掉failOnWarning,避免因为警告导致失败
- name: Build and Test with Maven
run: |
mvn -B verify --file pom.xml -Dmaven.test.failure.ignore=false -Dgpg.skip -Dmaven.javadoc.skip=false
env:
MAVEN_OPTS: -Xmx4g -XX:MaxMetaspaceSize=1g
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: success() || failure()
with:
report_paths: '**/target/surefire-reports/TEST-*.xml'
detailed_summary: true
include_passed: true
fail_on_failure: true
- name: Run SonarQube Analysis
if: matrix.java-version == '17' && github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
continue-on-error: true
run: |
if [[ ! -z "${{ secrets.SONAR_TOKEN }}" ]]; then
mvn sonar:sonar \
-Dsonar.projectKey=agileboot \
-Dsonar.organization=${{ secrets.SONAR_ORGANIZATION || 'default' }} \
-Dsonar.host.url=${{ secrets.SONAR_HOST_URL || 'https://sonarcloud.io' }} \
-Dsonar.login=${{ secrets.SONAR_TOKEN }} \
-Dsonar.java.source=${{ matrix.java-version }}
else
echo "Skipping SonarQube analysis - SONAR_TOKEN not configured"
fi
# 上传构建产物,if-no-files-found 改为 warn
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
name: agileboot-artifacts-java-${{ matrix.java-version }}
path: |
**/target/*.jar
!**/target/original-*.jar
retention-days: 5
if-no-files-found: warn
# # 只在 Java 17 版本上更新依赖图(权限和token已修复)
# - name: Update dependency graph
# uses: advanced-security/maven-dependency-submission-action@v4
# if: matrix.java-version == '17' && success()
# with:
# token: ${{ secrets.GITHUB_TOKEN }}
# # 发送构建状态通知
# - name: Notify Build Status
# if: always()
# uses: rtCamp/action-slack-notify@v2.2.1
# env:
# SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK || '' }}
# SLACK_CHANNEL: build-notifications
# SLACK_COLOR: ${{ job.status }}
# SLACK_TITLE: Build Status for Java ${{ matrix.java-version }}
# SLACK_MESSAGE: 'Build ${{ job.status }} on Java ${{ matrix.java-version }}'
-50
View File
@@ -1,50 +0,0 @@
######################################################################
# Build Tools
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### JRebel ###
rebel.xml
### NetBeans ###
nbproject/private/
build/*
nbbuild/
dist/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp
!*/build/*.java
!*/build/*.html
!*/build/*.xml
/agileboot-admin/src/main/resources/application-prod.yml
+3 -1
View File
@@ -148,7 +148,9 @@
<option name="FOR_BRACE_FORCE" value="3" />
<option name="WRAP_ON_TYPING" value="0" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JSON">
@@ -564,4 +566,4 @@
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</code_scheme>
@@ -0,0 +1,104 @@
package com.agileboot.admin.controller.collaboration;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import com.agileboot.domain.collaboration.record.CollaborationRecordApplicationService;
import com.agileboot.domain.collaboration.record.command.AddCollaborationRecordCommand;
import com.agileboot.domain.collaboration.record.command.UpdateCollaborationRecordCommand;
import com.agileboot.domain.collaboration.record.dto.CollaborationMonthlyStatisticsDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationOptionDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationRecordDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationRecordDetailDTO;
import com.agileboot.domain.collaboration.record.query.CollaborationRecordQuery;
import com.agileboot.domain.common.command.BulkOperationCommand;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author codex
*/
@Tag(name = "合作记录API", description = "合作记录相关的增删查改和统计")
@RestController
@RequestMapping("/collaboration/record")
@Validated
@RequiredArgsConstructor
public class CollaborationRecordController extends BaseController {
private final CollaborationRecordApplicationService recordApplicationService;
@Operation(summary = "合作记录列表")
@PreAuthorize("@permission.has('collaboration:record:list')")
@GetMapping("/list")
public ResponseDTO<PageDTO<CollaborationRecordDTO>> list(CollaborationRecordQuery query) {
return ResponseDTO.ok(recordApplicationService.getRecordList(query));
}
@Operation(summary = "合作记录详情")
@PreAuthorize("@permission.has('collaboration:record:query')")
@GetMapping("/{recordId}")
public ResponseDTO<CollaborationRecordDetailDTO> getInfo(@PathVariable @Positive Long recordId) {
return ResponseDTO.ok(recordApplicationService.getRecordInfo(recordId));
}
@Operation(summary = "合作记录选项")
@PreAuthorize("@permission.has('collaboration:record:list')")
@GetMapping("/options")
public ResponseDTO<List<CollaborationOptionDTO>> options() {
return ResponseDTO.ok(recordApplicationService.getOptions());
}
@Operation(summary = "合作记录月度统计")
@PreAuthorize("@permission.has('collaboration:record:statistics')")
@GetMapping("/monthly-statistics")
public ResponseDTO<List<CollaborationMonthlyStatisticsDTO>> monthlyStatistics(@RequestParam Integer year) {
return ResponseDTO.ok(recordApplicationService.getMonthlyStatistics(year));
}
@Operation(summary = "新增合作记录")
@PreAuthorize("@permission.has('collaboration:record:add')")
@AccessLog(title = "合作记录", businessType = BusinessTypeEnum.ADD)
@PostMapping
public ResponseDTO<Void> add(@Valid @RequestBody AddCollaborationRecordCommand command) {
recordApplicationService.addRecord(command);
return ResponseDTO.ok();
}
@Operation(summary = "修改合作记录")
@PreAuthorize("@permission.has('collaboration:record:edit')")
@AccessLog(title = "合作记录", businessType = BusinessTypeEnum.MODIFY)
@PutMapping
public ResponseDTO<Void> edit(@Valid @RequestBody UpdateCollaborationRecordCommand command) {
recordApplicationService.updateRecord(command);
return ResponseDTO.ok();
}
@Operation(summary = "删除合作记录")
@PreAuthorize("@permission.has('collaboration:record:remove')")
@AccessLog(title = "合作记录", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping
public ResponseDTO<Void> remove(@RequestParam @NotNull @NotEmpty List<Long> ids) {
recordApplicationService.deleteRecord(new BulkOperationCommand<>(ids));
return ResponseDTO.ok();
}
}
@@ -50,7 +50,7 @@ spring:
datasource:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/agileboot_pure?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://localhost:33061/todo_agileboot_pure?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root123
# 从库数据源
@@ -64,7 +64,7 @@ spring:
# 地址
host: localhost
# 端口,默认为6379
port: 6379
port: 63791
# 数据库索引
database: 0
# 密码
@@ -84,7 +84,7 @@ spring:
logging:
file:
path: D:/logs/agileboot-dev
path: /home/agileboot/logs/agileboot-dev
springdoc:
@@ -98,8 +98,8 @@ springdoc:
# 项目相关配置
agileboot:
# 文件基路径 示例( Windows配置D:\agilebootLinux配置 /home/agileboot
file-base-dir: D:\agileboot
# 文件基路径 示例(Linux配置 /home/agileboot
file-base-dir: /home/agileboot
# 前端url请求转发前缀
api-prefix: /dev-api
demo-enabled: false
@@ -6,14 +6,11 @@ import com.agileboot.common.config.AgileBootConfig;
import com.agileboot.common.constant.Constants.UploadSubDir;
import java.io.File;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest(classes = AgileBootAdminApplication.class)
@RunWith(SpringRunner.class)
public class AgileBootConfigTest {
@Resource
@@ -21,7 +18,7 @@ public class AgileBootConfigTest {
@Test
public void testConfig() {
String fileBaseDir = "D:\\agileboot\\profile";
String fileBaseDir = "/home/agileboot/profile";
Assertions.assertEquals("AgileBoot", config.getName());
Assertions.assertEquals("1.8.0", config.getVersion());
@@ -31,13 +28,13 @@ public class AgileBootConfigTest {
Assertions.assertFalse(AgileBootConfig.isAddressEnabled());
Assertions.assertEquals("math", AgileBootConfig.getCaptchaType());
Assertions.assertEquals("math", AgileBootConfig.getCaptchaType());
Assertions.assertEquals(fileBaseDir + "\\import",
Assertions.assertEquals(fileBaseDir + "/import",
AgileBootConfig.getFileBaseDir() + File.separator + UploadSubDir.IMPORT_PATH);
Assertions.assertEquals(fileBaseDir + "\\avatar",
Assertions.assertEquals(fileBaseDir + "/avatar",
AgileBootConfig.getFileBaseDir() + File.separator + UploadSubDir.AVATAR_PATH);
Assertions.assertEquals(fileBaseDir + "\\download",
Assertions.assertEquals(fileBaseDir + "/download",
AgileBootConfig.getFileBaseDir() + File.separator + UploadSubDir.DOWNLOAD_PATH);
Assertions.assertEquals(fileBaseDir + "\\upload",
Assertions.assertEquals(fileBaseDir + "/upload",
AgileBootConfig.getFileBaseDir() + File.separator + UploadSubDir.UPLOAD_PATH);
}
@@ -110,10 +110,10 @@ class FileUploadUtilsTest {
@Test
void getFileAbsolutePath() {
AgileBootConfig agileBootConfig = new AgileBootConfig();
agileBootConfig.setFileBaseDir("D:\\agileboot");
agileBootConfig.setFileBaseDir("/home/agileboot");
String fileAbsolutePath = FileUploadUtils.getFileAbsolutePath(UploadSubDir.AVATAR_PATH, "test.jpg");
Assertions.assertEquals("D:\\agileboot\\profile\\avatar\\test.jpg", fileAbsolutePath);
Assertions.assertEquals("/home/agileboot/profile/avatar/test.jpg", fileAbsolutePath);
}
}
@@ -0,0 +1,402 @@
package com.agileboot.domain.collaboration.record;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.collaboration.record.command.AddCollaborationRecordCommand;
import com.agileboot.domain.collaboration.record.command.CollaborationExpenditureCommand;
import com.agileboot.domain.collaboration.record.command.CollaborationFileCommand;
import com.agileboot.domain.collaboration.record.command.CollaborationSettlementCommand;
import com.agileboot.domain.collaboration.record.command.CollaborationTaskCommand;
import com.agileboot.domain.collaboration.record.command.UpdateCollaborationRecordCommand;
import com.agileboot.domain.collaboration.record.db.CollaborationExpenditureEntity;
import com.agileboot.domain.collaboration.record.db.CollaborationExpenditureService;
import com.agileboot.domain.collaboration.record.db.CollaborationFileEntity;
import com.agileboot.domain.collaboration.record.db.CollaborationFileService;
import com.agileboot.domain.collaboration.record.db.CollaborationRecordEntity;
import com.agileboot.domain.collaboration.record.db.CollaborationRecordService;
import com.agileboot.domain.collaboration.record.db.CollaborationSettlementEntity;
import com.agileboot.domain.collaboration.record.db.CollaborationSettlementService;
import com.agileboot.domain.collaboration.record.db.CollaborationTaskEntity;
import com.agileboot.domain.collaboration.record.db.CollaborationTaskService;
import com.agileboot.domain.collaboration.record.dto.CollaborationExpenditureDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationFileDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationMonthlyStatisticsDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationOptionDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationRecordDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationRecordDetailDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationSettlementDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationTaskDTO;
import com.agileboot.domain.collaboration.record.dto.SettlementStatusDTO;
import com.agileboot.domain.collaboration.record.enumtype.CollaborationOptionEnum;
import com.agileboot.domain.collaboration.record.enumtype.SettlementStatusEnum;
import com.agileboot.domain.collaboration.record.model.CollaborationRecordModel;
import com.agileboot.domain.collaboration.record.model.CollaborationRecordModelFactory;
import com.agileboot.domain.collaboration.record.query.CollaborationRecordQuery;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author codex
*/
@Service
@RequiredArgsConstructor
public class CollaborationRecordApplicationService {
private static final String PURCHASE_FEE = "拍单费用";
private static final String DELIVERY_FEE = "快递费用";
private static final String REMUNERATION_FEE = "稿费";
private final CollaborationRecordModelFactory recordModelFactory;
private final CollaborationRecordService recordService;
private final CollaborationTaskService taskService;
private final CollaborationExpenditureService expenditureService;
private final CollaborationSettlementService settlementService;
private final CollaborationFileService fileService;
public PageDTO<CollaborationRecordDTO> getRecordList(CollaborationRecordQuery query) {
Page<CollaborationRecordEntity> page = recordService.page(query.toPage(), query.toQueryWrapper());
List<CollaborationRecordDTO> records = page.getRecords().stream()
.map(this::buildRecordDTO)
.collect(Collectors.toList());
return new PageDTO<>(records, page.getTotal());
}
public CollaborationRecordDetailDTO getRecordInfo(Long recordId) {
CollaborationRecordModel model = recordModelFactory.loadById(recordId);
CollaborationRecordDetailDTO dto = new CollaborationRecordDetailDTO(model);
fillDetailChildren(dto, recordId);
fillRecordStats(dto, recordId);
return dto;
}
@Transactional(rollbackFor = Exception.class)
public void addRecord(AddCollaborationRecordCommand command) {
CollaborationRecordModel model = recordModelFactory.create();
model.loadFromAddCommand(command);
model.saveRecord();
saveChildren(model.getRecordId(), command);
}
@Transactional(rollbackFor = Exception.class)
public void updateRecord(UpdateCollaborationRecordCommand command) {
CollaborationRecordModel model = recordModelFactory.loadById(command.getRecordId());
model.loadFromUpdateCommand(command);
model.updateRecord();
replaceChildren(command.getRecordId(), command);
}
@Transactional(rollbackFor = Exception.class)
public void deleteRecord(BulkOperationCommand<Long> command) {
Set<Long> ids = command.getIds();
if (ids == null || ids.isEmpty()) {
return;
}
taskService.removeByRecordIds(ids);
expenditureService.removeByRecordIds(ids);
settlementService.removeByRecordIds(ids);
fileService.removeByRecordIds(ids);
recordService.removeBatchByIds(ids);
}
public List<CollaborationOptionDTO> getOptions() {
return Arrays.stream(CollaborationOptionEnum.values())
.map(item -> new CollaborationOptionDTO(item.getType(), item.getLabel(), Arrays.asList(item.getValues())))
.collect(Collectors.toList());
}
public List<CollaborationMonthlyStatisticsDTO> getMonthlyStatistics(Integer year) {
List<CollaborationMonthlyStatisticsDTO> statistics = new ArrayList<>();
for (int month = 1; month <= 12; month++) {
statistics.add(buildMonthlyStatistics(year, month));
}
return statistics;
}
private CollaborationRecordDTO buildRecordDTO(CollaborationRecordEntity entity) {
CollaborationRecordDTO dto = new CollaborationRecordDTO(entity);
fillRecordStats(dto, entity.getRecordId());
return dto;
}
private void fillDetailChildren(CollaborationRecordDetailDTO dto, Long recordId) {
dto.setTasks(taskService.listByRecordId(recordId).stream()
.map(CollaborationTaskDTO::new)
.collect(Collectors.toList()));
dto.setExpenditures(expenditureService.listByRecordId(recordId).stream()
.map(CollaborationExpenditureDTO::new)
.collect(Collectors.toList()));
dto.setSettlements(settlementService.listByRecordId(recordId).stream()
.map(CollaborationSettlementDTO::new)
.collect(Collectors.toList()));
dto.setFiles(fileService.listByRecordId(recordId).stream()
.map(CollaborationFileDTO::new)
.collect(Collectors.toList()));
}
private void fillRecordStats(CollaborationRecordDTO dto, Long recordId) {
List<CollaborationTaskEntity> tasks = taskService.listByRecordId(recordId);
List<CollaborationExpenditureEntity> expenditures = expenditureService.listByRecordId(recordId);
List<CollaborationSettlementEntity> settlements = settlementService.listByRecordId(recordId);
dto.setTasksNum(tasks.size());
dto.setCompletedTasksNum((int) tasks.stream().filter(item -> item.getReleaseDate() != null).count());
dto.setPurchaseSettlementStatus(getStatus(dto.getPurchasePrice(), settlements, PURCHASE_FEE));
dto.setDeliverySettlementStatus(getStatus(sumExpenditure(expenditures, DELIVERY_FEE), settlements, DELIVERY_FEE));
dto.setRemunerationSettlementStatus(getStatus(dto.getRemuneration(), settlements, REMUNERATION_FEE));
}
private SettlementStatusDTO getStatus(
BigDecimal expectedAmount, List<CollaborationSettlementEntity> settlements, String purpose) {
List<CollaborationSettlementEntity> matched = filterSettlements(settlements, purpose);
if (matched.isEmpty()) {
return toStatusDTO(SettlementStatusEnum.NONE);
}
BigDecimal settledAmount = sumSettlement(matched);
BigDecimal expected = defaultAmount(expectedAmount);
if (isAllSettlementPending(matched)) {
return toStatusDTO(SettlementStatusEnum.UNSETTLED);
}
if (isAllSettlementDue(matched) && settledAmount.compareTo(expected) >= 0) {
return toStatusDTO(SettlementStatusEnum.SETTLED);
}
return toStatusDTO(SettlementStatusEnum.PARTIAL);
}
private List<CollaborationSettlementEntity> filterSettlements(
List<CollaborationSettlementEntity> settlements, String purpose) {
if (settlements == null) {
return Collections.emptyList();
}
return settlements.stream()
.filter(item -> purpose.equals(item.getPurpose()))
.collect(Collectors.toList());
}
private SettlementStatusDTO toStatusDTO(SettlementStatusEnum status) {
return new SettlementStatusDTO(status.getValue(), status.getLabel());
}
private boolean isAllSettlementDue(List<CollaborationSettlementEntity> settlements) {
Date today = getTodayStart();
return settlements.stream()
.allMatch(item -> item.getSettleDate() != null && !item.getSettleDate().after(today));
}
private boolean isAllSettlementPending(List<CollaborationSettlementEntity> settlements) {
Date today = getTodayStart();
return settlements.stream()
.allMatch(item -> item.getSettleDate() == null || item.getSettleDate().after(today));
}
private void replaceChildren(Long recordId, AddCollaborationRecordCommand command) {
List<Long> ids = Collections.singletonList(recordId);
taskService.removeByRecordIds(ids);
expenditureService.removeByRecordIds(ids);
settlementService.removeByRecordIds(ids);
fileService.removeByRecordIds(ids);
saveChildren(recordId, command);
}
private void saveChildren(Long recordId, AddCollaborationRecordCommand command) {
saveTasks(recordId, command.getTasks());
saveExpenditures(recordId, command.getExpenditures());
saveSettlements(recordId, command.getSettlements());
saveFiles(recordId, command.getFiles());
}
private void saveTasks(Long recordId, List<CollaborationTaskCommand> tasks) {
List<CollaborationTaskEntity> entities = toTaskEntities(recordId, tasks);
if (!entities.isEmpty()) {
taskService.saveBatch(entities);
}
}
private void saveExpenditures(Long recordId, List<CollaborationExpenditureCommand> expenditures) {
List<CollaborationExpenditureEntity> entities = toExpenditureEntities(recordId, expenditures);
if (!entities.isEmpty()) {
expenditureService.saveBatch(entities);
}
}
private void saveSettlements(Long recordId, List<CollaborationSettlementCommand> settlements) {
List<CollaborationSettlementEntity> entities = toSettlementEntities(recordId, settlements);
if (!entities.isEmpty()) {
settlementService.saveBatch(entities);
}
}
private void saveFiles(Long recordId, List<CollaborationFileCommand> files) {
List<CollaborationFileEntity> entities = toFileEntities(recordId, files);
if (!entities.isEmpty()) {
fileService.saveBatch(entities);
}
}
private List<CollaborationTaskEntity> toTaskEntities(Long recordId, List<CollaborationTaskCommand> tasks) {
List<CollaborationTaskEntity> entities = new ArrayList<>();
if (tasks == null) {
return entities;
}
for (int i = 0; i < tasks.size(); i++) {
entities.add(toTaskEntity(recordId, tasks.get(i), i));
}
return entities;
}
private CollaborationTaskEntity toTaskEntity(Long recordId, CollaborationTaskCommand command, int index) {
CollaborationTaskEntity entity = new CollaborationTaskEntity();
entity.setRecordId(recordId);
entity.setReleaseDate(command.getReleaseDate());
entity.setSortOrder(index);
return entity;
}
private List<CollaborationExpenditureEntity> toExpenditureEntities(
Long recordId, List<CollaborationExpenditureCommand> expenditures) {
if (expenditures == null) {
return Collections.emptyList();
}
return expenditures.stream()
.map(item -> toExpenditureEntity(recordId, item))
.collect(Collectors.toList());
}
private CollaborationExpenditureEntity toExpenditureEntity(Long recordId, CollaborationExpenditureCommand command) {
CollaborationExpenditureEntity entity = new CollaborationExpenditureEntity();
entity.setRecordId(recordId);
entity.setSpendDate(command.getSpendDate());
entity.setAmount(command.getAmount());
entity.setPurpose(command.getPurpose());
return entity;
}
private List<CollaborationSettlementEntity> toSettlementEntities(
Long recordId, List<CollaborationSettlementCommand> settlements) {
if (settlements == null) {
return Collections.emptyList();
}
return settlements.stream()
.map(item -> toSettlementEntity(recordId, item))
.collect(Collectors.toList());
}
private CollaborationSettlementEntity toSettlementEntity(Long recordId, CollaborationSettlementCommand command) {
CollaborationSettlementEntity entity = new CollaborationSettlementEntity();
entity.setRecordId(recordId);
entity.setSettleDate(command.getSettleDate());
entity.setMethod(command.getMethod());
entity.setIncome(command.getIncome());
entity.setPurpose(command.getPurpose());
return entity;
}
private List<CollaborationFileEntity> toFileEntities(Long recordId, List<CollaborationFileCommand> files) {
List<CollaborationFileEntity> entities = new ArrayList<>();
if (files == null) {
return entities;
}
for (int i = 0; i < files.size(); i++) {
entities.add(toFileEntity(recordId, files.get(i), i));
}
return entities;
}
private CollaborationFileEntity toFileEntity(Long recordId, CollaborationFileCommand command, int index) {
CollaborationFileEntity entity = new CollaborationFileEntity();
entity.setRecordId(recordId);
entity.setFileType(command.getFileType());
entity.setUrl(command.getUrl());
entity.setFileName(command.getFileName());
entity.setNewFileName(command.getNewFileName());
entity.setOriginalFilename(command.getOriginalFilename());
entity.setSortOrder(index);
return entity;
}
private CollaborationMonthlyStatisticsDTO buildMonthlyStatistics(Integer year, Integer month) {
Date[] range = getMonthRange(year, month);
BigDecimal purchasePrice = sumPurchasePrice(range);
BigDecimal expenditureAmount = sumExpenditure(range);
BigDecimal settledRemuneration = sumSettlement(range, REMUNERATION_FEE);
BigDecimal settledTotal = sumSettlement(range, null);
return new CollaborationMonthlyStatisticsDTO(month, purchasePrice, expenditureAmount,
settledRemuneration, settledTotal);
}
private BigDecimal sumPurchasePrice(Date[] range) {
List<CollaborationRecordEntity> records = recordService.list(new QueryWrapper<CollaborationRecordEntity>()
.ge("purchase_date", range[0])
.le("purchase_date", range[1]));
return records.stream()
.map(CollaborationRecordEntity::getPurchasePrice)
.map(this::defaultAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
private BigDecimal sumExpenditure(Date[] range) {
List<CollaborationExpenditureEntity> expenditures = expenditureService.list(
new QueryWrapper<CollaborationExpenditureEntity>().ge("spend_date", range[0]).le("spend_date", range[1]));
return expenditures.stream()
.map(CollaborationExpenditureEntity::getAmount)
.map(this::defaultAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
private BigDecimal sumSettlement(Date[] range, String purpose) {
QueryWrapper<CollaborationSettlementEntity> wrapper = new QueryWrapper<CollaborationSettlementEntity>()
.ge("settle_date", range[0])
.le("settle_date", range[1])
.eq(purpose != null, "purpose", purpose);
return sumSettlement(settlementService.list(wrapper));
}
private BigDecimal sumExpenditure(List<CollaborationExpenditureEntity> expenditures, String purpose) {
return expenditures.stream()
.filter(item -> purpose.equals(item.getPurpose()))
.map(CollaborationExpenditureEntity::getAmount)
.map(this::defaultAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
private BigDecimal sumSettlement(List<CollaborationSettlementEntity> settlements) {
return settlements.stream()
.map(CollaborationSettlementEntity::getIncome)
.map(this::defaultAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
private BigDecimal defaultAmount(BigDecimal amount) {
return amount == null ? BigDecimal.ZERO : amount;
}
private Date[] getMonthRange(Integer year, Integer month) {
Calendar start = Calendar.getInstance();
start.set(year, month - 1, 1, 0, 0, 0);
start.set(Calendar.MILLISECOND, 0);
Calendar end = (Calendar) start.clone();
end.add(Calendar.MONTH, 1);
end.add(Calendar.MILLISECOND, -1);
return new Date[]{start.getTime(), end.getTime()};
}
private Date getTodayStart() {
Calendar today = Calendar.getInstance();
today.set(Calendar.HOUR_OF_DAY, 0);
today.set(Calendar.MINUTE, 0);
today.set(Calendar.SECOND, 0);
today.set(Calendar.MILLISECOND, 0);
return today.getTime();
}
}
@@ -0,0 +1,83 @@
package com.agileboot.domain.collaboration.record.command;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author codex
*/
@Data
public class AddCollaborationRecordCommand {
@NotBlank(message = "品牌不能为空")
@Size(max = 100, message = "品牌长度不能超过100个字符")
protected String brand;
@NotBlank(message = "物品不能为空")
@Size(max = 100, message = "物品长度不能超过100个字符")
protected String goods;
@Size(max = 50, message = "合作平台长度不能超过50个字符")
protected String cooperationPlatform;
@NotNull(message = "返图数量不能为空")
@Min(value = 1, message = "返图数量至少为1")
protected Integer imageReturnNum;
@Size(max = 30, message = "留存方式长度不能超过30个字符")
protected String retainedMethod;
@Size(max = 30, message = "合作方式长度不能超过30个字符")
protected String cooperatedMethod;
@Size(max = 30, message = "购入方式长度不能超过30个字符")
protected String purchaseMethod;
@PositiveOrZero(message = "购入金额不能小于0")
protected BigDecimal purchasePrice;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
protected Date purchaseDate;
@Size(max = 30, message = "购入平台长度不能超过30个字符")
protected String purchasePlatform;
@NotNull(message = "预完成日期不能为空")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
protected Date deadline;
@PositiveOrZero(message = "稿费不能小于0")
protected BigDecimal remuneration;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
protected Date completeDate;
@Size(max = 1000, message = "拍摄要求长度不能超过1000个字符")
protected String requirements;
@Size(max = 1000, message = "备注长度不能超过1000个字符")
protected String remark;
@Valid
protected List<CollaborationTaskCommand> tasks = new ArrayList<>();
@Valid
protected List<CollaborationExpenditureCommand> expenditures = new ArrayList<>();
@Valid
protected List<CollaborationSettlementCommand> settlements = new ArrayList<>();
@Valid
protected List<CollaborationFileCommand> files = new ArrayList<>();
}
@@ -0,0 +1,25 @@
package com.agileboot.domain.collaboration.record.command;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author codex
*/
@Data
public class CollaborationExpenditureCommand {
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date spendDate;
@PositiveOrZero(message = "支出金额不能小于0")
private BigDecimal amount;
@Size(max = 30, message = "支出用途长度不能超过30个字符")
private String purpose;
}
@@ -0,0 +1,30 @@
package com.agileboot.domain.collaboration.record.command;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author codex
*/
@Data
public class CollaborationFileCommand {
@NotBlank(message = "文件类型不能为空")
@Size(max = 30, message = "文件类型长度不能超过30个字符")
private String fileType;
@NotBlank(message = "文件地址不能为空")
@Size(max = 500, message = "文件地址长度不能超过500个字符")
private String url;
@Size(max = 300, message = "文件路径长度不能超过300个字符")
private String fileName;
@Size(max = 200, message = "服务端文件名长度不能超过200个字符")
private String newFileName;
@Size(max = 300, message = "原始文件名长度不能超过300个字符")
private String originalFilename;
}
@@ -0,0 +1,28 @@
package com.agileboot.domain.collaboration.record.command;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author codex
*/
@Data
public class CollaborationSettlementCommand {
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date settleDate;
@Size(max = 30, message = "结款方式长度不能超过30个字符")
private String method;
@PositiveOrZero(message = "结款金额不能小于0")
private BigDecimal income;
@Size(max = 30, message = "结款用途长度不能超过30个字符")
private String purpose;
}
@@ -0,0 +1,16 @@
package com.agileboot.domain.collaboration.record.command;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
import lombok.Data;
/**
* @author codex
*/
@Data
public class CollaborationTaskCommand {
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date releaseDate;
}
@@ -0,0 +1,19 @@
package com.agileboot.domain.collaboration.record.command;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author codex
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UpdateCollaborationRecordCommand extends AddCollaborationRecordCommand {
@NotNull(message = "合作记录ID不能为空")
@Positive
private Long recordId;
}
@@ -0,0 +1,46 @@
package com.agileboot.domain.collaboration.record.db;
import com.agileboot.common.core.base.BaseEntity;
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.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Getter;
import lombok.Setter;
/**
* @author codex
*/
@Getter
@Setter
@TableName("collaboration_expenditure")
@ApiModel(value = "CollaborationExpenditureEntity对象", description = "合作支出表")
public class CollaborationExpenditureEntity extends BaseEntity<CollaborationExpenditureEntity> {
@TableId(value = "expenditure_id", type = IdType.AUTO)
private Long expenditureId;
@TableField("record_id")
private Long recordId;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@TableField("spend_date")
private Date spendDate;
@TableField("amount")
private BigDecimal amount;
@TableField("purpose")
private String purpose;
@Override
public Serializable pkVal() {
return this.expenditureId;
}
}
@@ -0,0 +1,10 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author codex
*/
public interface CollaborationExpenditureMapper extends BaseMapper<CollaborationExpenditureEntity> {
}
@@ -0,0 +1,16 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Collection;
import java.util.List;
/**
* @author codex
*/
public interface CollaborationExpenditureService extends IService<CollaborationExpenditureEntity> {
List<CollaborationExpenditureEntity> listByRecordId(Long recordId);
void removeByRecordIds(Collection<Long> recordIds);
}
@@ -0,0 +1,36 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.stereotype.Service;
/**
* @author codex
*/
@Service
public class CollaborationExpenditureServiceImpl
extends ServiceImpl<CollaborationExpenditureMapper, CollaborationExpenditureEntity>
implements CollaborationExpenditureService {
@Override
public List<CollaborationExpenditureEntity> listByRecordId(Long recordId) {
if (recordId == null) {
return Collections.emptyList();
}
return list(new QueryWrapper<CollaborationExpenditureEntity>()
.eq("record_id", recordId)
.orderByAsc("spend_date"));
}
@Override
public void removeByRecordIds(Collection<Long> recordIds) {
if (recordIds == null || recordIds.isEmpty()) {
return;
}
remove(new QueryWrapper<CollaborationExpenditureEntity>().in("record_id", recordIds));
}
}
@@ -0,0 +1,51 @@
package com.agileboot.domain.collaboration.record.db;
import com.agileboot.common.core.base.BaseEntity;
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 io.swagger.annotations.ApiModel;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* @author codex
*/
@Getter
@Setter
@TableName("collaboration_file")
@ApiModel(value = "CollaborationFileEntity对象", description = "合作文件表")
public class CollaborationFileEntity extends BaseEntity<CollaborationFileEntity> {
@TableId(value = "file_id", type = IdType.AUTO)
private Long fileId;
@TableField("record_id")
private Long recordId;
@TableField("file_type")
private String fileType;
@TableField("url")
private String url;
@TableField("file_name")
private String fileName;
@TableField("new_file_name")
private String newFileName;
@TableField("original_filename")
private String originalFilename;
@TableField("sort_order")
private Integer sortOrder;
@Override
public Serializable pkVal() {
return this.fileId;
}
}
@@ -0,0 +1,10 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author codex
*/
public interface CollaborationFileMapper extends BaseMapper<CollaborationFileEntity> {
}
@@ -0,0 +1,16 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Collection;
import java.util.List;
/**
* @author codex
*/
public interface CollaborationFileService extends IService<CollaborationFileEntity> {
List<CollaborationFileEntity> listByRecordId(Long recordId);
void removeByRecordIds(Collection<Long> recordIds);
}
@@ -0,0 +1,36 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.stereotype.Service;
/**
* @author codex
*/
@Service
public class CollaborationFileServiceImpl
extends ServiceImpl<CollaborationFileMapper, CollaborationFileEntity>
implements CollaborationFileService {
@Override
public List<CollaborationFileEntity> listByRecordId(Long recordId) {
if (recordId == null) {
return Collections.emptyList();
}
return list(new QueryWrapper<CollaborationFileEntity>()
.eq("record_id", recordId)
.orderByAsc("file_type", "sort_order"));
}
@Override
public void removeByRecordIds(Collection<Long> recordIds) {
if (recordIds == null || recordIds.isEmpty()) {
return;
}
remove(new QueryWrapper<CollaborationFileEntity>().in("record_id", recordIds));
}
}
@@ -0,0 +1,100 @@
package com.agileboot.domain.collaboration.record.db;
import com.agileboot.common.core.base.BaseEntity;
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.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Getter;
import lombok.Setter;
/**
* @author codex
*/
@Getter
@Setter
@TableName("collaboration_record")
@ApiModel(value = "CollaborationRecordEntity对象", description = "合作记录表")
public class CollaborationRecordEntity extends BaseEntity<CollaborationRecordEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("合作记录ID")
@TableId(value = "record_id", type = IdType.AUTO)
private Long recordId;
@ApiModelProperty("品牌")
@TableField("brand")
private String brand;
@ApiModelProperty("物品")
@TableField("goods")
private String goods;
@ApiModelProperty("合作平台")
@TableField("cooperation_platform")
private String cooperationPlatform;
@ApiModelProperty("返图数量")
@TableField("image_return_num")
private Integer imageReturnNum;
@ApiModelProperty("留存方式")
@TableField("retained_method")
private String retainedMethod;
@ApiModelProperty("合作方式")
@TableField("cooperated_method")
private String cooperatedMethod;
@ApiModelProperty("购入方式")
@TableField("purchase_method")
private String purchaseMethod;
@ApiModelProperty("购入金额")
@TableField("purchase_price")
private BigDecimal purchasePrice;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@ApiModelProperty("购入日期")
@TableField("purchase_date")
private Date purchaseDate;
@ApiModelProperty("购入平台")
@TableField("purchase_platform")
private String purchasePlatform;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@ApiModelProperty("预完成日期")
@TableField("deadline")
private Date deadline;
@ApiModelProperty("稿费")
@TableField("remuneration")
private BigDecimal remuneration;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@ApiModelProperty("完成日期")
@TableField("complete_date")
private Date completeDate;
@ApiModelProperty("拍摄要求")
@TableField("requirements")
private String requirements;
@ApiModelProperty("备注")
@TableField("remark")
private String remark;
@Override
public Serializable pkVal() {
return this.recordId;
}
}
@@ -0,0 +1,10 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author codex
*/
public interface CollaborationRecordMapper extends BaseMapper<CollaborationRecordEntity> {
}
@@ -0,0 +1,10 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author codex
*/
public interface CollaborationRecordService extends IService<CollaborationRecordEntity> {
}
@@ -0,0 +1,14 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* @author codex
*/
@Service
public class CollaborationRecordServiceImpl
extends ServiceImpl<CollaborationRecordMapper, CollaborationRecordEntity>
implements CollaborationRecordService {
}
@@ -0,0 +1,49 @@
package com.agileboot.domain.collaboration.record.db;
import com.agileboot.common.core.base.BaseEntity;
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.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Getter;
import lombok.Setter;
/**
* @author codex
*/
@Getter
@Setter
@TableName("collaboration_settlement")
@ApiModel(value = "CollaborationSettlementEntity对象", description = "合作结款表")
public class CollaborationSettlementEntity extends BaseEntity<CollaborationSettlementEntity> {
@TableId(value = "settlement_id", type = IdType.AUTO)
private Long settlementId;
@TableField("record_id")
private Long recordId;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@TableField("settle_date")
private Date settleDate;
@TableField("method")
private String method;
@TableField("income")
private BigDecimal income;
@TableField("purpose")
private String purpose;
@Override
public Serializable pkVal() {
return this.settlementId;
}
}
@@ -0,0 +1,10 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author codex
*/
public interface CollaborationSettlementMapper extends BaseMapper<CollaborationSettlementEntity> {
}
@@ -0,0 +1,16 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Collection;
import java.util.List;
/**
* @author codex
*/
public interface CollaborationSettlementService extends IService<CollaborationSettlementEntity> {
List<CollaborationSettlementEntity> listByRecordId(Long recordId);
void removeByRecordIds(Collection<Long> recordIds);
}
@@ -0,0 +1,36 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.stereotype.Service;
/**
* @author codex
*/
@Service
public class CollaborationSettlementServiceImpl
extends ServiceImpl<CollaborationSettlementMapper, CollaborationSettlementEntity>
implements CollaborationSettlementService {
@Override
public List<CollaborationSettlementEntity> listByRecordId(Long recordId) {
if (recordId == null) {
return Collections.emptyList();
}
return list(new QueryWrapper<CollaborationSettlementEntity>()
.eq("record_id", recordId)
.orderByAsc("settle_date"));
}
@Override
public void removeByRecordIds(Collection<Long> recordIds) {
if (recordIds == null || recordIds.isEmpty()) {
return;
}
remove(new QueryWrapper<CollaborationSettlementEntity>().in("record_id", recordIds));
}
}
@@ -0,0 +1,42 @@
package com.agileboot.domain.collaboration.record.db;
import com.agileboot.common.core.base.BaseEntity;
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.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import java.io.Serializable;
import java.util.Date;
import lombok.Getter;
import lombok.Setter;
/**
* @author codex
*/
@Getter
@Setter
@TableName("collaboration_task")
@ApiModel(value = "CollaborationTaskEntity对象", description = "合作笔记任务表")
public class CollaborationTaskEntity extends BaseEntity<CollaborationTaskEntity> {
@TableId(value = "task_id", type = IdType.AUTO)
private Long taskId;
@TableField("record_id")
private Long recordId;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@TableField("release_date")
private Date releaseDate;
@TableField("sort_order")
private Integer sortOrder;
@Override
public Serializable pkVal() {
return this.taskId;
}
}
@@ -0,0 +1,10 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author codex
*/
public interface CollaborationTaskMapper extends BaseMapper<CollaborationTaskEntity> {
}
@@ -0,0 +1,16 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Collection;
import java.util.List;
/**
* @author codex
*/
public interface CollaborationTaskService extends IService<CollaborationTaskEntity> {
List<CollaborationTaskEntity> listByRecordId(Long recordId);
void removeByRecordIds(Collection<Long> recordIds);
}
@@ -0,0 +1,36 @@
package com.agileboot.domain.collaboration.record.db;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.stereotype.Service;
/**
* @author codex
*/
@Service
public class CollaborationTaskServiceImpl
extends ServiceImpl<CollaborationTaskMapper, CollaborationTaskEntity>
implements CollaborationTaskService {
@Override
public List<CollaborationTaskEntity> listByRecordId(Long recordId) {
if (recordId == null) {
return Collections.emptyList();
}
return list(new QueryWrapper<CollaborationTaskEntity>()
.eq("record_id", recordId)
.orderByAsc("sort_order"));
}
@Override
public void removeByRecordIds(Collection<Long> recordIds) {
if (recordIds == null || recordIds.isEmpty()) {
return;
}
remove(new QueryWrapper<CollaborationTaskEntity>().in("record_id", recordIds));
}
}
@@ -0,0 +1,33 @@
package com.agileboot.domain.collaboration.record.dto;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.domain.collaboration.record.db.CollaborationExpenditureEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author codex
*/
@NoArgsConstructor
@Data
public class CollaborationExpenditureDTO {
public CollaborationExpenditureDTO(CollaborationExpenditureEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
}
private Long expenditureId;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date spendDate;
private BigDecimal amount;
private String purpose;
}
@@ -0,0 +1,35 @@
package com.agileboot.domain.collaboration.record.dto;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.domain.collaboration.record.db.CollaborationFileEntity;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author codex
*/
@NoArgsConstructor
@Data
public class CollaborationFileDTO {
public CollaborationFileDTO(CollaborationFileEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
}
private Long fileId;
private String fileType;
private String url;
private String fileName;
private String newFileName;
private String originalFilename;
private Integer sortOrder;
}
@@ -0,0 +1,26 @@
package com.agileboot.domain.collaboration.record.dto;
import java.math.BigDecimal;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author codex
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CollaborationMonthlyStatisticsDTO {
private Integer month;
private BigDecimal purchasePrice;
private BigDecimal expenditureAmount;
private BigDecimal settledRemuneration;
private BigDecimal settledTotal;
}
@@ -0,0 +1,22 @@
package com.agileboot.domain.collaboration.record.dto;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author codex
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CollaborationOptionDTO {
private String type;
private String label;
private List<String> values;
}
@@ -0,0 +1,71 @@
package com.agileboot.domain.collaboration.record.dto;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.domain.collaboration.record.db.CollaborationRecordEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author codex
*/
@NoArgsConstructor
@Data
public class CollaborationRecordDTO {
public CollaborationRecordDTO(CollaborationRecordEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
}
private Long recordId;
private String brand;
private String goods;
private String cooperationPlatform;
private Integer imageReturnNum;
private String retainedMethod;
private String cooperatedMethod;
private String purchaseMethod;
private BigDecimal purchasePrice;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date purchaseDate;
private String purchasePlatform;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date deadline;
private BigDecimal remuneration;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date completeDate;
private String requirements;
private String remark;
private Integer tasksNum;
private Integer completedTasksNum;
private SettlementStatusDTO purchaseSettlementStatus;
private SettlementStatusDTO deliverySettlementStatus;
private SettlementStatusDTO remunerationSettlementStatus;
private Date createTime;
}
@@ -0,0 +1,30 @@
package com.agileboot.domain.collaboration.record.dto;
import com.agileboot.domain.collaboration.record.db.CollaborationRecordEntity;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @author codex
*/
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@Data
public class CollaborationRecordDetailDTO extends CollaborationRecordDTO {
public CollaborationRecordDetailDTO(CollaborationRecordEntity entity) {
super(entity);
}
private List<CollaborationTaskDTO> tasks = new ArrayList<>();
private List<CollaborationExpenditureDTO> expenditures = new ArrayList<>();
private List<CollaborationSettlementDTO> settlements = new ArrayList<>();
private List<CollaborationFileDTO> files = new ArrayList<>();
}
@@ -0,0 +1,35 @@
package com.agileboot.domain.collaboration.record.dto;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.domain.collaboration.record.db.CollaborationSettlementEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author codex
*/
@NoArgsConstructor
@Data
public class CollaborationSettlementDTO {
public CollaborationSettlementDTO(CollaborationSettlementEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
}
private Long settlementId;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date settleDate;
private String method;
private BigDecimal income;
private String purpose;
}
@@ -0,0 +1,30 @@
package com.agileboot.domain.collaboration.record.dto;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.domain.collaboration.record.db.CollaborationTaskEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author codex
*/
@NoArgsConstructor
@Data
public class CollaborationTaskDTO {
public CollaborationTaskDTO(CollaborationTaskEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
}
private Long taskId;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date releaseDate;
private Integer sortOrder;
}
@@ -0,0 +1,19 @@
package com.agileboot.domain.collaboration.record.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author codex
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class SettlementStatusDTO {
private String status;
private String label;
}
@@ -0,0 +1,12 @@
package com.agileboot.domain.collaboration.record.enumtype;
/**
* @author codex
*/
public enum CollaborationFileTypeEnum {
GOODS_IMAGE,
ATTACHMENT
}
@@ -0,0 +1,45 @@
package com.agileboot.domain.collaboration.record.enumtype;
import lombok.Getter;
/**
* @author codex
*/
@Getter
public enum CollaborationOptionEnum {
COOPERATION_PLATFORM("cooperationPlatform", "合作平台",
new String[]{"小红书", "得物", "抖音", "淘宝", "京东", "微博", "B站", "其他"}),
RETAINED_METHOD("retainedMethod", "留存方式",
new String[]{"寄拍", "送拍", "置换", "无需寄样"}),
COOPERATED_METHOD("cooperatedMethod", "合作方式",
new String[]{"水下", "蒲公英"}),
PURCHASE_METHOD("purchaseMethod", "购入方式",
new String[]{"拍单", "商家寄出"}),
PURCHASE_PLATFORM("purchasePlatform", "购入平台",
new String[]{"淘宝", "小红书", "京东", "抖音"}),
EXPENDITURE_PURPOSE("expenditurePurpose", "支出用途",
new String[]{"快递费用", "返点", "蒲公英扣税费用"}),
SETTLEMENT_METHOD("settlementMethod", "结款方式",
new String[]{"微信", "支付宝", "蒲公英", "京灵平台"}),
SETTLEMENT_PURPOSE("settlementPurpose", "结款用途",
new String[]{"稿费", "快递费用", "拍单费用", "蒲公英扣税费用"});
private final String type;
private final String label;
private final String[] values;
CollaborationOptionEnum(String type, String label, String[] values) {
this.type = type;
this.label = label;
this.values = values;
}
}
@@ -0,0 +1,27 @@
package com.agileboot.domain.collaboration.record.enumtype;
import lombok.Getter;
/**
* @author codex
*/
@Getter
public enum SettlementStatusEnum {
NONE("NONE", "无结款项"),
SETTLED("SETTLED", "已结"),
UNSETTLED("UNSETTLED", "未结"),
PARTIAL("PARTIAL", "未结清");
private final String value;
private final String label;
SettlementStatusEnum(String value, String label) {
this.value = value;
this.label = label;
}
}
@@ -0,0 +1,79 @@
package com.agileboot.domain.collaboration.record.model;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.collaboration.record.command.AddCollaborationRecordCommand;
import com.agileboot.domain.collaboration.record.command.UpdateCollaborationRecordCommand;
import com.agileboot.domain.collaboration.record.db.CollaborationRecordEntity;
import com.agileboot.domain.collaboration.record.db.CollaborationRecordService;
import lombok.NoArgsConstructor;
/**
* @author codex
*/
@NoArgsConstructor
public class CollaborationRecordModel extends CollaborationRecordEntity {
private CollaborationRecordService recordService;
public CollaborationRecordModel(CollaborationRecordService recordService) {
this.recordService = recordService;
}
public CollaborationRecordModel(CollaborationRecordEntity entity, CollaborationRecordService recordService) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
this.recordService = recordService;
}
public void loadFromAddCommand(AddCollaborationRecordCommand command) {
if (command == null) {
return;
}
BeanUtil.copyProperties(command, this, "recordId");
loadDefaultValues();
}
public void loadFromUpdateCommand(UpdateCollaborationRecordCommand command) {
if (command == null) {
return;
}
loadFromAddCommand(command);
}
public void checkRequiredFields() {
if (StrUtil.isBlank(getBrand())) {
throw new ApiException(ErrorCode.FAILED);
}
if (StrUtil.isBlank(getGoods())) {
throw new ApiException(ErrorCode.FAILED);
}
}
public void saveRecord() {
recordService.save(this);
}
public void updateRecord() {
recordService.updateById(this);
}
private void loadDefaultValues() {
if (getImageReturnNum() == null) {
setImageReturnNum(1);
}
if (StrUtil.isBlank(getRetainedMethod())) {
setRetainedMethod("寄拍");
}
if (StrUtil.isBlank(getCooperatedMethod())) {
setCooperatedMethod("水下");
}
if (StrUtil.isBlank(getPurchaseMethod())) {
setPurchaseMethod("拍单");
}
}
}
@@ -0,0 +1,31 @@
package com.agileboot.domain.collaboration.record.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.domain.collaboration.record.db.CollaborationRecordEntity;
import com.agileboot.domain.collaboration.record.db.CollaborationRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* @author codex
*/
@Component
@RequiredArgsConstructor
public class CollaborationRecordModelFactory {
private final CollaborationRecordService recordService;
public CollaborationRecordModel loadById(Long recordId) {
CollaborationRecordEntity entity = recordService.getById(recordId);
if (entity == null) {
throw new ApiException(Business.COMMON_OBJECT_NOT_FOUND, recordId, "合作记录");
}
return new CollaborationRecordModel(entity, recordService);
}
public CollaborationRecordModel create() {
return new CollaborationRecordModel(recordService);
}
}
@@ -0,0 +1,49 @@
package com.agileboot.domain.collaboration.record.query;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.agileboot.common.utils.time.DatePickUtil;
import com.agileboot.domain.collaboration.record.db.CollaborationRecordEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author codex
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CollaborationRecordQuery extends AbstractPageQuery<CollaborationRecordEntity> {
private String brand;
private String goods;
private String cooperationPlatform;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date purchaseBeginTime;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date purchaseEndTime;
@Override
public QueryWrapper<CollaborationRecordEntity> addQueryCondition() {
QueryWrapper<CollaborationRecordEntity> queryWrapper = new QueryWrapper<CollaborationRecordEntity>()
.like(StrUtil.isNotEmpty(brand), "brand", brand)
.like(StrUtil.isNotEmpty(goods), "goods", goods)
.eq(StrUtil.isNotEmpty(cooperationPlatform), "cooperation_platform", cooperationPlatform)
.ge(purchaseBeginTime != null, "purchase_date", DatePickUtil.getBeginOfTheDay(purchaseBeginTime))
.le(purchaseEndTime != null, "purchase_date", DatePickUtil.getEndOfTheDay(purchaseEndTime));
if (StrUtil.isEmpty(this.getOrderColumn())) {
this.setOrderColumn("deadline");
this.setOrderDirection("descending");
}
this.setTimeRangeColumn("deadline");
return queryWrapper;
}
}
-67
View File
@@ -1,67 +0,0 @@
@echo off
rem jar平级目录
set AppName=agileboot-admin.jar
rem JVM参数
set JVM_OPTS="-Dname=%AppName% -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC"
ECHO.
ECHO. [1] 启动%AppName%
ECHO. [2] 关闭%AppName%
ECHO. [3] 重启%AppName%
ECHO. [4] 启动状态 %AppName%
ECHO. [5] 退 出
ECHO.
ECHO.请输入选择项目的序号:
set /p ID=
IF "%id%"=="1" GOTO start
IF "%id%"=="2" GOTO stop
IF "%id%"=="3" GOTO restart
IF "%id%"=="4" GOTO status
IF "%id%"=="5" EXIT
PAUSE
:start
for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
set pid=%%a
set image_name=%%b
)
if defined pid (
echo %%is running
PAUSE
)
start javaw %JVM_OPTS% -jar %AppName%
echo starting……
echo Start %AppName% success...
goto:eof
rem 函数stop通过jps命令查找pid并结束进程
:stop
for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
set pid=%%a
set image_name=%%b
)
if not defined pid (echo process %AppName% does not exists) else (
echo prepare to kill %image_name%
echo start kill %pid% ...
rem 根据进程IDkill进程
taskkill /f /pid %pid%
)
goto:eof
:restart
call :stop
call :start
goto:eof
:status
for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
set pid=%%a
set image_name=%%b
)
if not defined pid (echo process %AppName% is dead ) else (
echo %image_name% is running
)
goto:eof
-86
View File
@@ -1,86 +0,0 @@
#!/bin/sh
# ./ag-admin.sh start 启动 stop 停止 restart 重启 status 状态
AppName=agileboot-admin.jar
# JVM参数
JVM_OPTS="-Dname=$AppName -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC"
APP_HOME=`pwd`
LOG_PATH=$APP_HOME/logs/$AppName.log
if [ "$1" = "" ];
then
echo -e "\033[0;31m 未输入操作名 \033[0m \033[0;34m {start|stop|restart|status} \033[0m"
exit 1
fi
if [ "$AppName" = "" ];
then
echo -e "\033[0;31m 未输入应用名 \033[0m"
exit 1
fi
function start()
{
PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'`
if [ x"$PID" != x"" ]; then
echo "$AppName is running..."
else
nohup java $JVM_OPTS -jar $AppName > /dev/null 2>&1 &
echo "Start $AppName success..."
fi
}
function stop()
{
echo "Stop $AppName"
PID=""
query(){
PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'`
}
query
if [ x"$PID" != x"" ]; then
kill -TERM $PID
echo "$AppName (pid:$PID) exiting..."
while [ x"$PID" != x"" ]
do
sleep 1
query
done
echo "$AppName exited."
else
echo "$AppName already stopped."
fi
}
function restart()
{
stop
sleep 2
start
}
function status()
{
PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l`
if [ $PID != 0 ];then
echo "$AppName is running..."
else
echo "$AppName is not running..."
fi
}
case $1 in
start)
start;;
stop)
stop;;
restart)
restart;;
status)
status;;
*)
esac
-14
View File
@@ -1,14 +0,0 @@
@echo off
echo.
echo [信息] 使用Jar命令运行Web工程。
echo.
cd %~dp0
cd ../agileboot-admin/target
set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
java -jar %JAVA_OPTS% agileboot-admin.jar
cd bin
pause
+16 -12
View File
@@ -1,34 +1,38 @@
services:
mysql:
image: mysql:8.0
container_name: mysql-server
container_name: todo-mysql-server
restart: always
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: agileboot_pure
MYSQL_DATABASE: todo_agileboot_pure
ports:
- "3306:3306"
- "33061:3306"
volumes:
- mysql_data:/var/lib/mysql
- todo_mysql_data:/var/lib/mysql
- ./sql/agileboot.sql:/docker-entrypoint-initdb.d/01-agileboot.sql:ro
networks:
- db-network
- todo-db-network
redis:
image: redis:7.2-alpine
container_name: redis-server
container_name: todo-redis-server
restart: always
command: redis-server --requirepass redis123
ports:
- "6379:6379"
- "63791:6379"
volumes:
- redis_data:/data
- todo_redis_data:/data
networks:
- db-network
- todo-db-network
volumes:
mysql_data:
redis_data:
todo_mysql_data:
todo_redis_data:
networks:
db-network:
todo-db-network:
driver: bridge
Vendored Regular → Executable
View File
@@ -1,3 +1,8 @@
SET NAMES utf8mb4;
SET character_set_client = utf8mb4;
SET character_set_connection = utf8mb4;
SET character_set_results = utf8mb4;
create table sys_config
(
config_id int auto_increment comment '参数主键'
@@ -101,68 +106,66 @@ create table sys_menu
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (1, '系统管理', 2, '', 0, '/system', 0, '', '{"title":"系统管理","icon":"ep:management","showParent":true,"rank":1}', 1, '系统管理目录', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:08:50', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (2, '系统监控', 2, '', 0, '/monitor', 0, '', '{"title":"系统监控","icon":"ep:monitor","showParent":true,"rank":3}', 1, '系统监控目录', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:09:15', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (3, '系统工具', 2, '', 0, '/tool', 0, '', '{"title":"系统工具","icon":"ep:tools","showParent":true,"rank":2}', 1, '系统工具目录', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:09:03', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (4, 'AgileBoot官网', 3, 'AgileBootguanwangIframeRouter', 0, '/AgileBootguanwangIframeLink', 0, '', '{"title":"AgileBoot官网","icon":"ep:link","showParent":true,"frameSrc":"https://element-plus.org/zh-CN/","rank":8}', 1, 'Agileboot官网地址', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:09:40', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (5, '用户管理', 1, 'SystemUser', 1, '/system/user/index', 0, 'system:user:list', '{"title":"用户管理","icon":"ep:user-filled","showParent":true}', 1, '用户管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:16:13', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (6, '角色管理', 1, 'SystemRole', 1, '/system/role/index', 0, 'system:role:list', '{"title":"角色管理","icon":"ep:user","showParent":true}', 1, '角色管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:16:23', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (7, '菜单管理', 1, 'MenuManagement', 1, '/system/menu/index', 0, 'system:menu:list', '{"title":"菜单管理","icon":"ep:menu","showParent":true}', 1, '菜单管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:41', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (8, '部门管理', 1, 'Department', 1, '/system/dept/index', 0, 'system:dept:list', '{"title":"部门管理","icon":"fa-solid:code-branch","showParent":true}', 1, '部门管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:35', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (9, '岗位管理', 1, 'Post', 1, '/system/post/index', 0, 'system:post:list', '{"title":"岗位管理","icon":"ep:postcard","showParent":true}', 1, '岗位管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:11', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (10, '参数设置', 1, 'Config', 1, '/system/config/index', 0, 'system:config:list', '{"title":"参数设置","icon":"ep:setting","showParent":true}', 1, '参数设置菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:03', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (11, '通知公告', 1, 'SystemNotice', 1, '/system/notice/index', 0, 'system:notice:list', '{"title":"通知公告","icon":"ep:notification","showParent":true}', 1, '通知公告菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:56', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (12, '日志管理', 1, 'LogManagement', 1, '/system/logd', 0, '', '{"title":"日志管理","icon":"ep:document","showParent":true}', 1, '日志管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:47', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (13, '在线用户', 1, 'OnlineUser', 2, '/system/monitor/onlineUser/index', 0, 'monitor:online:list', '{"title":"在线用户","icon":"fa-solid:users","showParent":true}', 1, '在线用户菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:13', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (14, '数据监控', 1, 'DataMonitor', 2, '/system/monitor/druid/index', 0, 'monitor:druid:list', '{"title":"数据监控","icon":"fa:database","showParent":true,"frameSrc":"/druid/login.html","isFrameSrcInternal":true}', 1, '数据监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:25', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (15, '服务监控', 1, 'ServerInfo', 2, '/system/monitor/server/index', 0, 'monitor:server:list', '{"title":"服务监控","icon":"fa:server","showParent":true}', 1, '服务监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:34', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (16, '缓存监控', 1, 'CacheInfo', 2, '/system/monitor/cache/index', 0, 'monitor:cache:list', '{"title":"缓存监控","icon":"ep:reading","showParent":true}', 1, '缓存监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:12:59', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (17, '系统接口', 1, 'SystemAPI', 3, '/tool/swagger/index', 0, 'tool:swagger:list', '{"title":"系统接口","icon":"ep:document-remove","showParent":true,"frameSrc":"/swagger-ui/index.html","isFrameSrcInternal":true}', 1, '系统接口菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:01', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (18, '操作日志', 1, 'OperationLog', 12, '/system/log/operationLog/index', 0, 'monitor:operlog:list', '{"title":"操作日志"}', 1, '操作日志菜单', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (19, '登录日志', 1, 'LoginLog', 12, '/system/log/loginLog/index', 0, 'monitor:logininfor:list', '{"title":"登录日志"}', 1, '登录日志菜单', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (20, '用户查询', 0, ' ', 5, '', 1, 'system:user:query', '{"title":"用户查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (21, '用户新增', 0, ' ', 5, '', 1, 'system:user:add', '{"title":"用户新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (22, '用户修改', 0, ' ', 5, '', 1, 'system:user:edit', '{"title":"用户修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (23, '用户删除', 0, ' ', 5, '', 1, 'system:user:remove', '{"title":"用户删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (24, '用户导', 0, ' ', 5, '', 1, 'system:user:export', '{"title":"用户导"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (25, '用户导入', 0, ' ', 5, '', 1, 'system:user:import', '{"title":"用户导入"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (26, '重置密码', 0, ' ', 5, '', 1, 'system:user:resetPwd', '{"title":"重置密码"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (27, '角色查询', 0, ' ', 6, '', 1, 'system:role:query', '{"title":"角色查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (28, '角色新增', 0, ' ', 6, '', 1, 'system:role:add', '{"title":"角色新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (29, '角色修改', 0, ' ', 6, '', 1, 'system:role:edit', '{"title":"角色修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (30, '角色删除', 0, ' ', 6, '', 1, 'system:role:remove', '{"title":"角色删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (31, '角色导出', 0, ' ', 6, '', 1, 'system:role:export', '{"title":"角色导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (32, '菜单查询', 0, ' ', 7, '', 1, 'system:menu:query', '{"title":"菜单查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (33, '菜单新增', 0, ' ', 7, '', 1, 'system:menu:add', '{"title":"菜单新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (34, '菜单修改', 0, ' ', 7, '', 1, 'system:menu:edit', '{"title":"菜单修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (35, '菜单删除', 0, ' ', 7, '', 1, 'system:menu:remove', '{"title":"菜单删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (36, '部门查询', 0, ' ', 8, '', 1, 'system:dept:query', '{"title":"部门查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (37, '部门新增', 0, ' ', 8, '', 1, 'system:dept:add', '{"title":"部门新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (38, '部门修改', 0, ' ', 8, '', 1, 'system:dept:edit', '{"title":"部门修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (39, '部门删除', 0, ' ', 8, '', 1, 'system:dept:remove', '{"title":"部门删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (40, '岗位查询', 0, ' ', 9, '', 1, 'system:post:query', '{"title":"岗位查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (41, '岗位新增', 0, ' ', 9, '', 1, 'system:post:add', '{"title":"岗位新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (42, '岗位修改', 0, ' ', 9, '', 1, 'system:post:edit', '{"title":"岗位修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (43, '岗位删除', 0, ' ', 9, '', 1, 'system:post:remove', '{"title":"岗位删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (44, '岗位导出', 0, ' ', 9, '', 1, 'system:post:export', '{"title":"岗位导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (45, '参数查询', 0, ' ', 10, '', 1, 'system:config:query', '{"title":"参数查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (46, '参数新增', 0, ' ', 10, '', 1, 'system:config:add', '{"title":"参数新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (47, '参数修改', 0, ' ', 10, '', 1, 'system:config:edit', '{"title":"参数修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (48, '参数删除', 0, ' ', 10, '', 1, 'system:config:remove', '{"title":"参数删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (49, '参数导出', 0, ' ', 10, '', 1, 'system:config:export', '{"title":"参数导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (50, '公告查询', 0, ' ', 11, '', 1, 'system:notice:query', '{"title":"公告查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (51, '公告新增', 0, ' ', 11, '', 1, 'system:notice:add', '{"title":"公告新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (52, '公告修改', 0, ' ', 11, '', 1, 'system:notice:edit', '{"title":"公告修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (53, '公告删除', 0, ' ', 11, '', 1, 'system:notice:remove', '{"title":"公告删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (54, '操作查询', 0, ' ', 18, '', 1, 'monitor:operlog:query', '{"title":"操作查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (55, '操作删除', 0, ' ', 18, '', 1, 'monitor:operlog:remove', '{"title":"操作删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (56, '日志导出', 0, ' ', 18, '', 1, 'monitor:operlog:export', '{"title":"日志导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (57, '登录查询', 0, ' ', 19, '', 1, 'monitor:logininfor:query', '{"title":"登录查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (58, '登录删除', 0, ' ', 19, '', 1, 'monitor:logininfor:remove', '{"title":"登录删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (59, '日志导出', 0, ' ', 19, '', 1, 'monitor:logininfor:export', '{"title":"日志导出","rank":22}', 1, '', 0, '2022-05-21 08:30:54', 1, '2023-07-22 17:02:28', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (60, '在线查询', 0, ' ', 13, '', 1, 'monitor:online:query', '{"title":"在线查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (61, '批量强退', 0, ' ', 13, '', 1, 'monitor:online:batchLogout', '{"title":"批量强退"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (62, '单条强退', 0, ' ', 13, '', 1, 'monitor:online:forceLogout', '{"title":"单条强退"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (63, 'AgileBoot Github地址', 4, 'https://github.com/valarchie/AgileBoot-Back-End', 0, '/external', 0, '', '{"title":"AgileBoot Github地址","icon":"fa-solid:external-link-alt","showParent":true,"rank":9}', 1, 'Agileboot github地址', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:12:13', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (64, '首页', 2, '', 0, '/global', 0, '121212', '{"title":"首页","showParent":true,"rank":3}', 1, '', 1, '2023-07-24 22:36:03', 1, '2023-07-24 22:38:37', 1);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (65, '个人中心', 1, 'PersonalCenter', 2053, '/system/user/profile', 0, '434sdf', '{"title":"个人中心","showParent":true,"rank":3}', 1, '', 1, '2023-07-24 22:36:55', null, null, 1);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (4, '用户管理', 1, 'SystemUser', 1, '/system/user/index', 0, 'system:user:list', '{"title":"用户管理","icon":"ep:user-filled","showParent":true}', 1, '用户管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:16:13', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (5, '角色管理', 1, 'SystemRole', 1, '/system/role/index', 0, 'system:role:list', '{"title":"角色管理","icon":"ep:user","showParent":true}', 1, '角色管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:16:23', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (6, '菜单管理', 1, 'MenuManagement', 1, '/system/menu/index', 0, 'system:menu:list', '{"title":"菜单管理","icon":"ep:menu","showParent":true}', 1, '菜单管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:41', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (7, '部门管理', 1, 'Department', 1, '/system/dept/index', 0, 'system:dept:list', '{"title":"部门管理","icon":"fa-solid:code-branch","showParent":true}', 1, '部门管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:35', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (8, '岗位管理', 1, 'Post', 1, '/system/post/index', 0, 'system:post:list', '{"title":"岗位管理","icon":"ep:postcard","showParent":true}', 1, '岗位管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:11', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (9, '参数设置', 1, 'Config', 1, '/system/config/index', 0, 'system:config:list', '{"title":"参数设置","icon":"ep:setting","showParent":true}', 1, '参数设置菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:03', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (10, '通知公告', 1, 'SystemNotice', 1, '/system/notice/index', 0, 'system:notice:list', '{"title":"通知公告","icon":"ep:notification","showParent":true}', 1, '通知公告菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:56', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (11, '日志管理', 1, 'LogManagement', 1, '/system/logd', 0, '', '{"title":"日志管理","icon":"ep:document","showParent":true}', 1, '日志管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:47', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (12, '在线用户', 1, 'OnlineUser', 2, '/system/monitor/onlineUser/index', 0, 'monitor:online:list', '{"title":"在线用户","icon":"fa-solid:users","showParent":true}', 1, '在线用户菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:13', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (13, '数据监控', 1, 'DataMonitor', 2, '/system/monitor/druid/index', 0, 'monitor:druid:list', '{"title":"数据监控","icon":"fa:database","showParent":true,"frameSrc":"/druid/login.html","isFrameSrcInternal":true}', 1, '数据监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:25', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (14, '服务监控', 1, 'ServerInfo', 2, '/system/monitor/server/index', 0, 'monitor:server:list', '{"title":"服务监控","icon":"fa:server","showParent":true}', 1, '服务监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:34', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (15, '缓存监控', 1, 'CacheInfo', 2, '/system/monitor/cache/index', 0, 'monitor:cache:list', '{"title":"缓存监控","icon":"ep:reading","showParent":true}', 1, '缓存监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:12:59', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (16, '系统接口', 1, 'SystemAPI', 3, '/tool/swagger/index', 0, 'tool:swagger:list', '{"title":"系统接口","icon":"ep:document-remove","showParent":true,"frameSrc":"/swagger-ui/index.html","isFrameSrcInternal":true}', 1, '系统接口菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:01', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (17, '操作日志', 1, 'OperationLog', 12, '/system/log/operationLog/index', 0, 'monitor:operlog:list', '{"title":"操作日志"}', 1, '操作日志菜单', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (18, '登录日志', 1, 'LoginLog', 12, '/system/log/loginLog/index', 0, 'monitor:logininfor:list', '{"title":"登录日志"}', 1, '登录日志菜单', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (19, '用户查询', 0, ' ', 5, '', 1, 'system:user:query', '{"title":"用户查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (20, '用户新增', 0, ' ', 5, '', 1, 'system:user:add', '{"title":"用户新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (21, '用户修改', 0, ' ', 5, '', 1, 'system:user:edit', '{"title":"用户修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (22, '用户删除', 0, ' ', 5, '', 1, 'system:user:remove', '{"title":"用户删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (23, '用户导出', 0, ' ', 5, '', 1, 'system:user:export', '{"title":"用户导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (24, '用户导', 0, ' ', 5, '', 1, 'system:user:import', '{"title":"用户导"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (25, '重置密码', 0, ' ', 5, '', 1, 'system:user:resetPwd', '{"title":"重置密码"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (26, '角色查询', 0, ' ', 6, '', 1, 'system:role:query', '{"title":"角色查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (27, '角色新增', 0, ' ', 6, '', 1, 'system:role:add', '{"title":"角色新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (28, '角色修改', 0, ' ', 6, '', 1, 'system:role:edit', '{"title":"角色修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (29, '角色删除', 0, ' ', 6, '', 1, 'system:role:remove', '{"title":"角色删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (30, '角色导出', 0, ' ', 6, '', 1, 'system:role:export', '{"title":"角色导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (31, '菜单查询', 0, ' ', 7, '', 1, 'system:menu:query', '{"title":"菜单查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (32, '菜单新增', 0, ' ', 7, '', 1, 'system:menu:add', '{"title":"菜单新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (33, '菜单修改', 0, ' ', 7, '', 1, 'system:menu:edit', '{"title":"菜单修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (34, '菜单删除', 0, ' ', 7, '', 1, 'system:menu:remove', '{"title":"菜单删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (35, '部门查询', 0, ' ', 8, '', 1, 'system:dept:query', '{"title":"部门查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (36, '部门新增', 0, ' ', 8, '', 1, 'system:dept:add', '{"title":"部门新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (37, '部门修改', 0, ' ', 8, '', 1, 'system:dept:edit', '{"title":"部门修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (38, '部门删除', 0, ' ', 8, '', 1, 'system:dept:remove', '{"title":"部门删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (39, '岗位查询', 0, ' ', 9, '', 1, 'system:post:query', '{"title":"岗位查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (40, '岗位新增', 0, ' ', 9, '', 1, 'system:post:add', '{"title":"岗位新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (41, '岗位修改', 0, ' ', 9, '', 1, 'system:post:edit', '{"title":"岗位修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (42, '岗位删除', 0, ' ', 9, '', 1, 'system:post:remove', '{"title":"岗位删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (43, '岗位导出', 0, ' ', 9, '', 1, 'system:post:export', '{"title":"岗位导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (44, '参数查询', 0, ' ', 10, '', 1, 'system:config:query', '{"title":"参数查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (45, '参数新增', 0, ' ', 10, '', 1, 'system:config:add', '{"title":"参数新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (46, '参数修改', 0, ' ', 10, '', 1, 'system:config:edit', '{"title":"参数修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (47, '参数删除', 0, ' ', 10, '', 1, 'system:config:remove', '{"title":"参数删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (48, '参数导出', 0, ' ', 10, '', 1, 'system:config:export', '{"title":"参数导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (49, '公告查询', 0, ' ', 11, '', 1, 'system:notice:query', '{"title":"公告查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (50, '公告新增', 0, ' ', 11, '', 1, 'system:notice:add', '{"title":"公告新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (51, '公告修改', 0, ' ', 11, '', 1, 'system:notice:edit', '{"title":"公告修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (52, '公告删除', 0, ' ', 11, '', 1, 'system:notice:remove', '{"title":"公告删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (53, '操作查询', 0, ' ', 18, '', 1, 'monitor:operlog:query', '{"title":"操作查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (54, '操作删除', 0, ' ', 18, '', 1, 'monitor:operlog:remove', '{"title":"操作删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (55, '日志导出', 0, ' ', 18, '', 1, 'monitor:operlog:export', '{"title":"日志导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (56, '登录查询', 0, ' ', 19, '', 1, 'monitor:logininfor:query', '{"title":"登录查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (57, '登录删除', 0, ' ', 19, '', 1, 'monitor:logininfor:remove', '{"title":"登录删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (58, '日志导出', 0, ' ', 19, '', 1, 'monitor:logininfor:export', '{"title":"日志导出","rank":22}', 1, '', 0, '2022-05-21 08:30:54', 1, '2023-07-22 17:02:28', 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (59, '在线查询', 0, ' ', 13, '', 1, 'monitor:online:query', '{"title":"在线查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (60, '批量强退', 0, ' ', 13, '', 1, 'monitor:online:batchLogout', '{"title":"批量强退"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (61, '单条强退', 0, ' ', 13, '', 1, 'monitor:online:forceLogout', '{"title":"单条强退"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (62, '首页', 2, '', 0, '/global', 0, '121212', '{"title":"首页","showParent":true,"rank":3}', 1, '', 1, '2023-07-24 22:36:03', 1, '2023-07-24 22:38:37', 1);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (63, '个人中心', 1, 'PersonalCenter', 2053, '/system/user/profile', 0, '434sdf', '{"title":"个人中心","showParent":true,"rank":3}', 1, '', 1, '2023-07-24 22:36:55', null, null, 1);
create table sys_notice
(
@@ -357,6 +360,123 @@ create table sys_user
comment '用户信息表';
INSERT INTO sys_user (user_id, post_id, role_id, dept_id, username, nickname, user_type, email, phone_number, sex, avatar, password, status, login_ip, login_date, is_admin, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (1, 1, 1, 4, 'admin', 'valarchie1', 0, 'agileboot@163.com', '15888888883', 0, '/profile/avatar/20230725164110_blob_6b7a989b1cdd4dd396665d2cfd2addc5.png', '$2a$10$o55UFZAtyWnDpRV6dvQe8.c/MjlFacC49ASj2usNXm9BY74SYI/uG', 1, '127.0.0.1', '2023-08-14 23:07:03', 1, null, '2022-05-21 08:30:54', 1, '2023-08-14 23:07:03', '管理员', 0);
INSERT INTO sys_user (user_id, post_id, role_id, dept_id, username, nickname, user_type, email, phone_number, sex, avatar, password, status, login_ip, login_date, is_admin, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (2, 2, 2, 5, 'ag1', 'valarchie2', 0, 'agileboot1@qq.com', '15666666666', 1, '/profile/avatar/20230725114818_avatar_b5bf400732bb43369b4df58802049b22.png', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', 1, '127.0.0.1', '2022-05-21 08:30:54', 0, null, '2022-05-21 08:30:54', null, null, '测试员1', 0);
INSERT INTO sys_user (user_id, post_id, role_id, dept_id, username, nickname, user_type, email, phone_number, sex, avatar, password, status, login_ip, login_date, is_admin, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (3, 2, 0, 5, 'ag2', 'valarchie3', 0, 'agileboot2@qq.com', '15666666667', 1, '/profile/avatar/20230725114818_avatar_b5bf400732bb43369b4df58802049b22.png', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', 1, '127.0.0.1', '2022-05-21 08:30:54', 0, null, '2022-05-21 08:30:54', null, null, '测试员2', 0);
create table collaboration_record
(
record_id bigint auto_increment comment '合作记录ID'
primary key,
brand varchar(100) not null comment '品牌',
goods varchar(100) not null comment '物品',
cooperation_platform varchar(50) null comment '合作平台',
image_return_num int default 1 not null comment '返图数量',
retained_method varchar(30) default '寄拍' not null comment '留存方式',
cooperated_method varchar(30) default '水下' not null comment '合作方式',
purchase_method varchar(30) default '拍单' not null comment '购入方式',
purchase_price decimal(10, 2) null comment '购入金额',
purchase_date date null comment '购入日期',
purchase_platform varchar(30) null comment '购入平台',
deadline date not null comment '预完成日期',
remuneration decimal(10, 2) null comment '稿费',
complete_date date null comment '完成日期',
requirements varchar(1000) null comment '拍摄要求',
remark varchar(1000) null comment '备注',
creator_id bigint null comment '创建者ID',
create_time datetime null comment '创建时间',
updater_id bigint null comment '更新者ID',
update_time datetime null comment '更新时间',
deleted tinyint(1) default 0 not null comment '逻辑删除',
index idx_collaboration_record_platform (cooperation_platform),
index idx_collaboration_record_deadline (deadline),
index idx_collaboration_record_purchase_date (purchase_date)
)
comment '合作记录表';
create table collaboration_task
(
task_id bigint auto_increment comment '笔记任务ID'
primary key,
record_id bigint not null comment '合作记录ID',
release_date date null comment '发布日期',
sort_order int default 0 not null comment '排序',
creator_id bigint null comment '创建者ID',
create_time datetime null comment '创建时间',
updater_id bigint null comment '更新者ID',
update_time datetime null comment '更新时间',
deleted tinyint(1) default 0 not null comment '逻辑删除',
index idx_collaboration_task_record_id (record_id)
)
comment '合作笔记任务表';
create table collaboration_expenditure
(
expenditure_id bigint auto_increment comment '支出ID'
primary key,
record_id bigint not null comment '合作记录ID',
spend_date date null comment '支出日期',
amount decimal(10, 2) null comment '支出金额',
purpose varchar(30) null comment '支出用途',
creator_id bigint null comment '创建者ID',
create_time datetime null comment '创建时间',
updater_id bigint null comment '更新者ID',
update_time datetime null comment '更新时间',
deleted tinyint(1) default 0 not null comment '逻辑删除',
index idx_collaboration_expenditure_record_id (record_id),
index idx_collaboration_expenditure_spend_date (spend_date)
)
comment '合作支出表';
create table collaboration_settlement
(
settlement_id bigint auto_increment comment '结款ID'
primary key,
record_id bigint not null comment '合作记录ID',
settle_date date null comment '结款日期',
method varchar(30) null comment '结款方式',
income decimal(10, 2) null comment '结款金额',
purpose varchar(30) null comment '结款用途',
creator_id bigint null comment '创建者ID',
create_time datetime null comment '创建时间',
updater_id bigint null comment '更新者ID',
update_time datetime null comment '更新时间',
deleted tinyint(1) default 0 not null comment '逻辑删除',
index idx_collaboration_settlement_record_id (record_id),
index idx_collaboration_settlement_date (settle_date),
index idx_collaboration_settlement_purpose (purpose)
)
comment '合作结款表';
create table collaboration_file
(
file_id bigint auto_increment comment '文件ID'
primary key,
record_id bigint not null comment '合作记录ID',
file_type varchar(30) not null comment '文件类型:GOODS_IMAGE/ATTACHMENT',
url varchar(500) not null comment '访问地址',
file_name varchar(300) null comment '服务端相对路径',
new_file_name varchar(200) null comment '服务端新文件名',
original_filename varchar(300) null comment '原始文件名',
sort_order int default 0 not null comment '排序',
creator_id bigint null comment '创建者ID',
create_time datetime null comment '创建时间',
updater_id bigint null comment '更新者ID',
update_time datetime null comment '更新时间',
deleted tinyint(1) default 0 not null comment '逻辑删除',
index idx_collaboration_file_record_id (record_id),
index idx_collaboration_file_type (file_type)
)
comment '合作文件表';
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (3000, '合作管理', 2, '', 0, '/collaboration', 0, '', '{"title":"合作管理","icon":"ep:connection","showParent":true,"rank":4}', 1, '合作管理目录', 0, now(), null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (3001, '合作记录', 1, 'CollaborationRecord', 3000, '/collaboration/record/index', 0, 'collaboration:record:list', '{"title":"合作记录","icon":"ep:notebook","showParent":true}', 1, '合作记录菜单', 0, now(), null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (3002, '月度统计', 1, 'CollaborationStatistics', 3000, '/collaboration/statistics/index', 0, 'collaboration:record:statistics', '{"title":"月度统计","icon":"ep:data-analysis","showParent":true}', 1, '月度统计菜单', 0, now(), null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (3003, '合作查询', 0, ' ', 3001, '', 1, 'collaboration:record:query', '{"title":"合作查询"}', 1, '', 0, now(), null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (3004, '合作新增', 0, ' ', 3001, '', 1, 'collaboration:record:add', '{"title":"合作新增"}', 1, '', 0, now(), null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (3005, '合作修改', 0, ' ', 3001, '', 1, 'collaboration:record:edit', '{"title":"合作修改"}', 1, '', 0, now(), null, null, 0);
INSERT INTO sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (3006, '合作删除', 0, ' ', 3001, '', 1, 'collaboration:record:remove', '{"title":"合作删除"}', 1, '', 0, now(), null, null, 0);
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (2, 3000);
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (2, 3001);
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (2, 3002);
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (2, 3003);
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (2, 3004);
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (2, 3005);
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (2, 3006);
-428
View File
@@ -1,428 +0,0 @@
/*
Navicat MySQL Data Transfer
Source Server : Local-Mysql
Source Server Version : 80029
Source Host : localhost:33066
Source Database : agileboot
Target Server Type : MYSQL
Target Server Version : 80029
File Encoding : 65001
Date: 2022-10-07 16:05:40
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for sys_config
-- ----------------------------
DROP TABLE IF EXISTS `sys_config`;
CREATE TABLE `sys_config` (
`config_id` int NOT NULL AUTO_INCREMENT COMMENT '参数主键',
`config_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '配置名称',
`config_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '配置键名',
`config_options` varchar(1024) NOT NULL DEFAULT '' COMMENT '可选的选项',
`config_value` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '配置值',
`is_allow_change` tinyint(1) NOT NULL COMMENT '是否允许修改',
`creator_id` bigint DEFAULT NULL COMMENT '创建者ID',
`updater_id` bigint DEFAULT NULL COMMENT '更新者ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`remark` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
PRIMARY KEY (`config_id`),
UNIQUE KEY `config_key_uniq_idx` (`config_key`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='参数配置表';
-- ----------------------------
-- Records of sys_config
-- ----------------------------
INSERT INTO `sys_config` VALUES ('1', '主框架页-默认皮肤样式名称', 'sys.index.skinName', '["skin-blue","skin-green","skin-purple","skin-red","skin-yellow"]', 'skin-blue', '1', null, null, '2022-08-28 22:12:19', '2022-05-21 08:30:55', '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow', '0');
INSERT INTO `sys_config` VALUES ('2', '用户管理-账号初始密码', 'sys.user.initPassword', '', '1234567', '1', null, null, '2022-08-28 21:54:19', '2022-05-21 08:30:55', '初始化密码 123456', '0');
INSERT INTO `sys_config` VALUES ('3', '主框架页-侧边栏主题', 'sys.index.sideTheme', '["theme-dark","theme-light"]', 'theme-dark', '1', null, null, '2022-08-28 22:12:15', '2022-08-20 08:30:55', '深色主题theme-dark,浅色主题theme-light', '0');
INSERT INTO `sys_config` VALUES ('4', '账号自助-验证码开关', 'sys.account.captchaOnOff', '["true","false"]', 'false', '0', null, null, '2022-08-28 22:03:37', '2022-05-21 08:30:55', '是否开启验证码功能(true开启,false关闭)', '0');
INSERT INTO `sys_config` VALUES ('5', '账号自助-是否开启用户注册功能', 'sys.account.registerUser', '["true","false"]', 'true', '0', null, '1', '2022-10-05 22:18:57', '2022-05-21 08:30:55', '是否开启注册用户功能(true开启,false关闭)', '0');
-- ----------------------------
-- Table structure for sys_dept
-- ----------------------------
DROP TABLE IF EXISTS `sys_dept`;
CREATE TABLE `sys_dept` (
`dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '部门id',
`parent_id` bigint NOT NULL DEFAULT '0' COMMENT '父部门id',
`ancestors` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '祖级列表',
`dept_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '部门名称',
`order_num` int NOT NULL DEFAULT '0' COMMENT '显示顺序',
`leader_id` bigint DEFAULT NULL,
`leader_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '负责人',
`phone` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '联系电话',
`email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '邮箱',
`status` smallint NOT NULL DEFAULT '0' COMMENT '部门状态(0停用 1启用)',
`creator_id` bigint DEFAULT NULL COMMENT '创建者ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`updater_id` bigint DEFAULT NULL COMMENT '更新者ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
PRIMARY KEY (`dept_id`)
) ENGINE=InnoDB AUTO_INCREMENT=203 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='部门表';
-- ----------------------------
-- Records of sys_dept
-- ----------------------------
INSERT INTO `sys_dept` VALUES ('1', '0', '0', 'AgileBoot科技', '0', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('2', '1', '0,1', '深圳总公司', '1', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('3', '1', '0,1', '长沙分公司', '2', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('4', '2', '0,1,2', '研发部门', '1', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('5', '2', '0,1,2', '市场部门', '2', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('6', '2', '0,1,2', '测试部门', '3', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('7', '2', '0,1,2', '财务部门', '4', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('8', '2', '0,1,2', '运维部门', '5', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('9', '3', '0,1,3', '市场部门', '1', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('10', '3', '0,1,3', '财务部门', '2', null, 'valarchie', '15888888888', 'valarchie@163.com', '0', null, '2022-05-21 08:30:54', null, null, '0');
-- ----------------------------
-- Table structure for sys_login_info
-- ----------------------------
DROP TABLE IF EXISTS `sys_login_info`;
CREATE TABLE `sys_login_info` (
`info_id` bigint NOT NULL AUTO_INCREMENT COMMENT '访问ID',
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户账号',
`ip_address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '登录IP地址',
`login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '登录地点',
`browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '浏览器类型',
`operation_system` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作系统',
`status` smallint NOT NULL DEFAULT '0' COMMENT '登录状态(1成功 0失败)',
`msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '提示消息',
`login_time` datetime DEFAULT NULL COMMENT '访问时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
PRIMARY KEY (`info_id`)
) ENGINE=InnoDB AUTO_INCREMENT=415 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统访问记录';
-- ----------------------------
-- Records of sys_login_info
-- ----------------------------
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`menu_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',
`parent_id` bigint NOT NULL DEFAULT '0' COMMENT '父菜单ID',
`order_num` int NOT NULL DEFAULT '0' COMMENT '显示顺序',
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '路由地址',
`component` varchar(255) DEFAULT NULL COMMENT '组件路径',
`query` varchar(255) DEFAULT NULL COMMENT '路由参数',
`is_external` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否为外链(1是 0否)',
`is_cache` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否缓存(1缓存 0不缓存)',
`menu_type` smallint NOT NULL DEFAULT '0' COMMENT '菜单类型(M=1目录 C=2菜单 F=3按钮)',
`is_visible` tinyint(1) NOT NULL DEFAULT '0' COMMENT '菜单状态(1显示 0隐藏)',
`status` smallint NOT NULL DEFAULT '0' COMMENT '菜单状态(1启用 0停用)',
`perms` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '权限标识',
`icon` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '#' COMMENT '菜单图标',
`creator_id` bigint DEFAULT NULL COMMENT '创建者ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`updater_id` bigint DEFAULT NULL COMMENT '更新者ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '备注',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2007 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='菜单权限表';
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES ('1', '系统管理', '0', '1', 'system', null, '', '0', '1', '1', '1', '1', '', 'system', '0', '2022-05-21 08:30:54', null, null, '系统管理目录', '0');
INSERT INTO `sys_menu` VALUES ('2', '系统监控', '0', '2', 'monitor', null, '', '0', '1', '1', '1', '1', '', 'monitor', '0', '2022-05-21 08:30:54', null, null, '系统监控目录', '0');
INSERT INTO `sys_menu` VALUES ('3', '系统工具', '0', '3', 'tool', null, '', '0', '1', '1', '1', '1', '', 'tool', '0', '2022-05-21 08:30:54', null, null, '系统工具目录', '0');
INSERT INTO `sys_menu` VALUES ('4', 'AgileBoot官网', '0', '4', 'https://juejin.cn/column/7159946528827080734', null, '', '1', '1', '1', '1', '1', '', 'guide', '0', '2022-05-21 08:30:54', null, null, 'Agileboot官网地址', '0');
INSERT INTO `sys_menu` VALUES ('5', '用户管理', '1', '1', 'user', 'system/user/index', '', '0', '1', '2', '1', '1', 'system:user:list', 'user', '0', '2022-05-21 08:30:54', null, null, '用户管理菜单', '0');
INSERT INTO `sys_menu` VALUES ('6', '角色管理', '1', '2', 'role', 'system/role/index', '', '0', '1', '2', '1', '1', 'system:role:list', 'peoples', '0', '2022-05-21 08:30:54', null, null, '角色管理菜单', '0');
INSERT INTO `sys_menu` VALUES ('7', '菜单管理', '1', '3', 'menu', 'system/menu/index', '', '0', '1', '2', '1', '1', 'system:menu:list', 'tree-table', '0', '2022-05-21 08:30:54', null, null, '菜单管理菜单', '0');
INSERT INTO `sys_menu` VALUES ('8', '部门管理', '1', '4', 'dept', 'system/dept/index', '', '0', '1', '2', '1', '1', 'system:dept:list', 'tree', '0', '2022-05-21 08:30:54', null, null, '部门管理菜单', '0');
INSERT INTO `sys_menu` VALUES ('9', '岗位管理', '1', '5', 'post', 'system/post/index', '', '0', '1', '2', '1', '1', 'system:post:list', 'post', '0', '2022-05-21 08:30:54', null, null, '岗位管理菜单', '0');
INSERT INTO `sys_menu` VALUES ('10', '参数设置', '1', '7', 'config', 'system/config/index', '', '0', '1', '2', '1', '1', 'system:config:list', 'edit', '0', '2022-05-21 08:30:54', null, null, '参数设置菜单', '0');
INSERT INTO `sys_menu` VALUES ('11', '通知公告', '1', '8', 'notice', 'system/notice/index', '', '0', '1', '2', '1', '1', 'system:notice:list', 'message', '0', '2022-05-21 08:30:54', null, null, '通知公告菜单', '0');
INSERT INTO `sys_menu` VALUES ('12', '日志管理', '1', '9', 'log', '', '', '0', '1', '1', '1', '1', '', 'log', '0', '2022-05-21 08:30:54', null, null, '日志管理菜单', '0');
INSERT INTO `sys_menu` VALUES ('13', '在线用户', '2', '1', 'online', 'monitor/online/index', '', '0', '1', '2', '1', '1', 'monitor:online:list', 'online', '0', '2022-05-21 08:30:54', null, null, '在线用户菜单', '0');
INSERT INTO `sys_menu` VALUES ('14', '数据监控', '2', '3', 'druid', 'monitor/druid/index', '', '0', '1', '2', '1', '1', 'monitor:druid:list', 'druid', '0', '2022-05-21 08:30:54', null, null, '数据监控菜单', '0');
INSERT INTO `sys_menu` VALUES ('15', '服务监控', '2', '4', 'server', 'monitor/server/index', '', '0', '1', '2', '1', '1', 'monitor:server:list', 'server', '0', '2022-05-21 08:30:54', null, null, '服务监控菜单', '0');
INSERT INTO `sys_menu` VALUES ('16', '缓存监控', '2', '5', 'cache', 'monitor/cache/index', '', '0', '1', '2', '1', '1', 'monitor:cache:list', 'redis', '0', '2022-05-21 08:30:54', null, null, '缓存监控菜单', '0');
INSERT INTO `sys_menu` VALUES ('17', '系统接口', '3', '3', 'swagger', 'tool/swagger/index', '', '0', '1', '2', '1', '1', 'tool:swagger:list', 'swagger', '0', '2022-05-21 08:30:54', null, null, '系统接口菜单', '0');
INSERT INTO `sys_menu` VALUES ('18', '操作日志', '12', '1', 'operlog', 'monitor/operlog/index', '', '0', '1', '2', '1', '1', 'monitor:operlog:list', 'form', '0', '2022-05-21 08:30:54', null, null, '操作日志菜单', '0');
INSERT INTO `sys_menu` VALUES ('19', '登录日志', '12', '2', 'logininfor', 'monitor/logininfor/index', '', '0', '1', '2', '1', '1', 'monitor:logininfor:list', 'logininfor', '0', '2022-05-21 08:30:54', null, null, '登录日志菜单', '0');
INSERT INTO `sys_menu` VALUES ('20', '用户查询', '5', '1', '', '', '', '0', '1', '3', '1', '1', 'system:user:query', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('21', '用户新增', '5', '2', '', '', '', '0', '1', '3', '1', '1', 'system:user:add', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('22', '用户修改', '5', '3', '', '', '', '0', '1', '3', '1', '1', 'system:user:edit', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('23', '用户删除', '5', '4', '', '', '', '0', '1', '3', '1', '1', 'system:user:remove', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('24', '用户导出', '5', '5', '', '', '', '0', '1', '3', '1', '1', 'system:user:export', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('25', '用户导入', '5', '6', '', '', '', '0', '1', '3', '1', '1', 'system:user:import', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('26', '重置密码', '5', '7', '', '', '', '0', '1', '3', '1', '1', 'system:user:resetPwd', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('27', '角色查询', '6', '1', '', '', '', '0', '1', '3', '1', '1', 'system:role:query', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('28', '角色新增', '6', '2', '', '', '', '0', '1', '3', '1', '1', 'system:role:add', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('29', '角色修改', '6', '3', '', '', '', '0', '1', '3', '1', '1', 'system:role:edit', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('30', '角色删除', '6', '4', '', '', '', '0', '1', '3', '1', '1', 'system:role:remove', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('31', '角色导出', '6', '5', '', '', '', '0', '1', '3', '1', '1', 'system:role:export', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('32', '菜单查询', '7', '1', '', '', '', '0', '1', '3', '1', '1', 'system:menu:query', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('33', '菜单新增', '7', '2', '', '', '', '0', '1', '3', '1', '1', 'system:menu:add', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('34', '菜单修改', '7', '3', '', '', '', '0', '1', '3', '1', '1', 'system:menu:edit', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('35', '菜单删除', '7', '4', '', '', '', '0', '1', '3', '1', '1', 'system:menu:remove', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('36', '部门查询', '8', '1', '', '', '', '0', '1', '3', '1', '1', 'system:dept:query', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('37', '部门新增', '8', '2', '', '', '', '0', '1', '3', '1', '1', 'system:dept:add', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('38', '部门修改', '8', '3', '', '', '', '0', '1', '3', '1', '1', 'system:dept:edit', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('39', '部门删除', '8', '4', '', '', '', '0', '1', '3', '1', '1', 'system:dept:remove', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('40', '岗位查询', '9', '1', '', '', '', '0', '1', '3', '1', '1', 'system:post:query', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('41', '岗位新增', '9', '2', '', '', '', '0', '1', '3', '1', '1', 'system:post:add', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('42', '岗位修改', '9', '3', '', '', '', '0', '1', '3', '1', '1', 'system:post:edit', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('43', '岗位删除', '9', '4', '', '', '', '0', '1', '3', '1', '1', 'system:post:remove', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('44', '岗位导出', '9', '5', '', '', '', '0', '1', '3', '1', '1', 'system:post:export', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('45', '参数查询', '10', '1', '#', '', '', '0', '1', '3', '1', '1', 'system:config:query', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('46', '参数新增', '10', '2', '#', '', '', '0', '1', '3', '1', '1', 'system:config:add', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('47', '参数修改', '10', '3', '#', '', '', '0', '1', '3', '1', '1', 'system:config:edit', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('48', '参数删除', '10', '4', '#', '', '', '0', '1', '3', '1', '1', 'system:config:remove', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('49', '参数导出', '10', '5', '#', '', '', '0', '1', '3', '1', '1', 'system:config:export', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('50', '公告查询', '11', '1', '#', '', '', '0', '1', '3', '1', '1', 'system:notice:query', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('51', '公告新增', '11', '2', '#', '', '', '0', '1', '3', '1', '1', 'system:notice:add', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('52', '公告修改', '11', '3', '#', '', '', '0', '1', '3', '1', '1', 'system:notice:edit', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('53', '公告删除', '11', '4', '#', '', '', '0', '1', '3', '1', '1', 'system:notice:remove', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('54', '操作查询', '18', '1', '#', '', '', '0', '1', '3', '1', '1', 'monitor:operlog:query', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('55', '操作删除', '18', '2', '#', '', '', '0', '1', '3', '1', '1', 'monitor:operlog:remove', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('56', '日志导出', '18', '4', '#', '', '', '0', '1', '3', '1', '1', 'monitor:operlog:export', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('57', '登录查询', '19', '1', '#', '', '', '0', '1', '3', '1', '1', 'monitor:logininfor:query', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('58', '登录删除', '19', '2', '#', '', '', '0', '1', '3', '1', '1', 'monitor:logininfor:remove', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('59', '日志导出', '19', '3', '#', '', '', '0', '1', '3', '1', '1', 'monitor:logininfor:export', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('60', '在线查询', '13', '1', '#', '', '', '0', '1', '3', '1', '1', 'monitor:online:query', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('61', '批量强退', '13', '2', '#', '', '', '0', '1', '3', '1', '1', 'monitor:online:batchLogout', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
INSERT INTO `sys_menu` VALUES ('62', '单条强退', '13', '3', '#', '', '', '0', '1', '3', '1', '1', 'monitor:online:forceLogout', '#', '0', '2022-05-21 08:30:54', null, null, '', '0');
-- ----------------------------
-- Table structure for sys_notice
-- ----------------------------
DROP TABLE IF EXISTS `sys_notice`;
CREATE TABLE `sys_notice` (
`notice_id` int NOT NULL AUTO_INCREMENT COMMENT '公告ID',
`notice_title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告标题',
`notice_type` smallint NOT NULL COMMENT '公告类型(1通知 2公告)',
`notice_content` text COMMENT '公告内容',
`status` smallint NOT NULL DEFAULT '0' COMMENT '公告状态(1正常 0关闭)',
`creator_id` bigint NOT NULL COMMENT '创建者ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`updater_id` bigint DEFAULT NULL COMMENT '更新者ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
PRIMARY KEY (`notice_id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='通知公告表';
-- ----------------------------
-- Records of sys_notice
-- ----------------------------
INSERT INTO `sys_notice` VALUES ('1', '温馨提醒:2018-07-01 AgileBoot新版本发布啦', '2', '新版本内容~~~~~~~~~~', '1', '1', '2022-05-21 08:30:55', '1', '2022-08-29 20:12:37', '管理员', '0');
INSERT INTO `sys_notice` VALUES ('2', '维护通知:2018-07-01 AgileBoot系统凌晨维护', '1', '维护内容', '1', '1', '2022-05-21 08:30:55', null, null, '管理员', '0');
-- ----------------------------
-- Table structure for sys_operation_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_operation_log`;
CREATE TABLE `sys_operation_log` (
`operation_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',
`business_type` smallint NOT NULL DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
`request_method` smallint NOT NULL DEFAULT '0' COMMENT '请求方式',
`request_module` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求模块',
`request_url` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求URL',
`called_method` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '调用方法',
`operator_type` smallint NOT NULL DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)',
`user_id` bigint DEFAULT '0' COMMENT '用户ID',
`username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '操作人员',
`operator_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '操作人员ip',
`operator_location` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '操作地点',
`dept_id` bigint DEFAULT '0' COMMENT '部门ID',
`dept_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '部门名称',
`operation_param` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '请求参数',
`operation_result` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '返回参数',
`status` smallint NOT NULL DEFAULT '1' COMMENT '操作状态(1正常 0异常)',
`error_stack` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '错误消息',
`operation_time` datetime NOT NULL COMMENT '操作时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
PRIMARY KEY (`operation_id`)
) ENGINE=InnoDB AUTO_INCREMENT=379 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操作日志记录';
-- ----------------------------
-- Records of sys_operation_log
-- ----------------------------
-- ----------------------------
-- Table structure for sys_post
-- ----------------------------
DROP TABLE IF EXISTS `sys_post`;
CREATE TABLE `sys_post` (
`post_id` bigint NOT NULL AUTO_INCREMENT COMMENT '岗位ID',
`post_code` varchar(64) NOT NULL COMMENT '岗位编码',
`post_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位名称',
`post_sort` int NOT NULL COMMENT '显示顺序',
`status` smallint NOT NULL COMMENT '状态(1正常 0停用)',
`remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
`creator_id` bigint DEFAULT NULL,
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`updater_id` bigint DEFAULT NULL,
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除',
PRIMARY KEY (`post_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='岗位信息表';
-- ----------------------------
-- Records of sys_post
-- ----------------------------
INSERT INTO `sys_post` VALUES ('1', 'ceo', '董事长', '1', '1', '', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_post` VALUES ('2', 'se', '项目经理', '2', '1', '', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_post` VALUES ('3', 'hr', '人力资源', '3', '1', '', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_post` VALUES ('4', 'user', '普通员工', '5', '0', '', null, '2022-05-21 08:30:54', null, null, '0');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',
`role_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色权限字符串',
`role_sort` int NOT NULL COMMENT '显示顺序',
`data_scope` smallint DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3: 本部门数据权限 4: 本部门及以下数据权限 5: 本人权限)',
`dept_id_set` varchar(1024) DEFAULT '' COMMENT '角色所拥有的部门数据权限',
`status` smallint NOT NULL COMMENT '角色状态(1正常 0停用)',
`creator_id` bigint DEFAULT NULL COMMENT '创建者ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`updater_id` bigint DEFAULT NULL COMMENT '更新者ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=111 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色信息表';
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', '超级管理员', 'admin', '1', '1', '', '1', null, '2022-05-21 08:30:54', null, null, '超级管理员', '0');
INSERT INTO `sys_role` VALUES ('2', '普通角色', 'common', '3', '2', '', '1', null, '2022-05-21 08:30:54', null, null, '普通角色', '0');
INSERT INTO `sys_role` VALUES ('3', '闲置角色', 'unused', '4', '2', '', '0', null, '2022-05-21 08:30:54', null, null, '未使用的角色', '0');
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`role_id` bigint NOT NULL COMMENT '角色ID',
`menu_id` bigint NOT NULL COMMENT '菜单ID',
PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色和菜单关联表';
-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES ('2', '1');
INSERT INTO `sys_role_menu` VALUES ('2', '2');
INSERT INTO `sys_role_menu` VALUES ('2', '3');
INSERT INTO `sys_role_menu` VALUES ('2', '4');
INSERT INTO `sys_role_menu` VALUES ('2', '5');
INSERT INTO `sys_role_menu` VALUES ('2', '6');
INSERT INTO `sys_role_menu` VALUES ('2', '7');
INSERT INTO `sys_role_menu` VALUES ('2', '8');
INSERT INTO `sys_role_menu` VALUES ('2', '9');
INSERT INTO `sys_role_menu` VALUES ('2', '10');
INSERT INTO `sys_role_menu` VALUES ('2', '11');
INSERT INTO `sys_role_menu` VALUES ('2', '12');
INSERT INTO `sys_role_menu` VALUES ('2', '13');
INSERT INTO `sys_role_menu` VALUES ('2', '14');
INSERT INTO `sys_role_menu` VALUES ('2', '15');
INSERT INTO `sys_role_menu` VALUES ('2', '16');
INSERT INTO `sys_role_menu` VALUES ('2', '17');
INSERT INTO `sys_role_menu` VALUES ('2', '18');
INSERT INTO `sys_role_menu` VALUES ('2', '19');
INSERT INTO `sys_role_menu` VALUES ('2', '20');
INSERT INTO `sys_role_menu` VALUES ('2', '21');
INSERT INTO `sys_role_menu` VALUES ('2', '22');
INSERT INTO `sys_role_menu` VALUES ('2', '23');
INSERT INTO `sys_role_menu` VALUES ('2', '24');
INSERT INTO `sys_role_menu` VALUES ('2', '25');
INSERT INTO `sys_role_menu` VALUES ('2', '26');
INSERT INTO `sys_role_menu` VALUES ('2', '27');
INSERT INTO `sys_role_menu` VALUES ('2', '28');
INSERT INTO `sys_role_menu` VALUES ('2', '29');
INSERT INTO `sys_role_menu` VALUES ('2', '30');
INSERT INTO `sys_role_menu` VALUES ('2', '31');
INSERT INTO `sys_role_menu` VALUES ('2', '32');
INSERT INTO `sys_role_menu` VALUES ('2', '33');
INSERT INTO `sys_role_menu` VALUES ('2', '34');
INSERT INTO `sys_role_menu` VALUES ('2', '35');
INSERT INTO `sys_role_menu` VALUES ('2', '36');
INSERT INTO `sys_role_menu` VALUES ('2', '37');
INSERT INTO `sys_role_menu` VALUES ('2', '38');
INSERT INTO `sys_role_menu` VALUES ('2', '39');
INSERT INTO `sys_role_menu` VALUES ('2', '40');
INSERT INTO `sys_role_menu` VALUES ('2', '41');
INSERT INTO `sys_role_menu` VALUES ('2', '42');
INSERT INTO `sys_role_menu` VALUES ('2', '43');
INSERT INTO `sys_role_menu` VALUES ('2', '44');
INSERT INTO `sys_role_menu` VALUES ('2', '45');
INSERT INTO `sys_role_menu` VALUES ('2', '46');
INSERT INTO `sys_role_menu` VALUES ('2', '47');
INSERT INTO `sys_role_menu` VALUES ('2', '48');
INSERT INTO `sys_role_menu` VALUES ('2', '49');
INSERT INTO `sys_role_menu` VALUES ('2', '50');
INSERT INTO `sys_role_menu` VALUES ('2', '51');
INSERT INTO `sys_role_menu` VALUES ('2', '52');
INSERT INTO `sys_role_menu` VALUES ('2', '53');
INSERT INTO `sys_role_menu` VALUES ('2', '54');
INSERT INTO `sys_role_menu` VALUES ('2', '55');
INSERT INTO `sys_role_menu` VALUES ('2', '56');
INSERT INTO `sys_role_menu` VALUES ('2', '57');
INSERT INTO `sys_role_menu` VALUES ('2', '58');
INSERT INTO `sys_role_menu` VALUES ('2', '59');
INSERT INTO `sys_role_menu` VALUES ('2', '60');
INSERT INTO `sys_role_menu` VALUES ('2', '61');
-- roleId = 2的权限 特地少一个 方便测试
INSERT INTO `sys_role_menu` VALUES ('3', '1');
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`post_id` bigint DEFAULT NULL COMMENT '职位id',
`role_id` bigint DEFAULT NULL COMMENT '角色id',
`dept_id` bigint DEFAULT NULL COMMENT '部门ID',
`username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号',
`nick_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称',
`user_type` smallint DEFAULT '0' COMMENT '用户类型(00系统用户)',
`email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户邮箱',
`phone_number` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '手机号码',
`sex` smallint DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '头像地址',
`password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '密码',
`status` smallint NOT NULL DEFAULT '0' COMMENT '帐号状态(1正常 2停用 3冻结)',
`login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
`is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT '超级管理员标志(1是,0否)',
`creator_id` bigint DEFAULT NULL COMMENT '更新者ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`updater_id` bigint DEFAULT NULL COMMENT '更新者ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=109 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户信息表';
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', '1', '1', '4', 'admin', 'valarchie1', '0', 'agileboot@163.com', '15888888889', '0', '', '$2a$10$rb1wRoEIkLbIknREEN1LH.FGs4g0oOS5t6l5LQ793nRaFO.SPHDHy', '1', '127.0.0.1', '2022-10-06 17:00:06', 1, null, '2022-05-21 08:30:54', '1', '2022-10-06 17:00:06', '管理员', '0');
INSERT INTO `sys_user` VALUES ('2', '2', '2', '5', 'ag1', 'valarchie2', '0', 'agileboot1@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '1', '127.0.0.1', '2022-05-21 08:30:54', 0, null, '2022-05-21 08:30:54', null, null, '测试员1', '0');
INSERT INTO `sys_user` VALUES ('3', '2', '0', '5', 'ag2', 'valarchie3', '0', 'agileboot2@qq.com', '15666666667', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '1', '127.0.0.1', '2022-05-21 08:30:54', 0, null, '2022-05-21 08:30:54', null, null, '测试员2', '0');
+155
View File
@@ -0,0 +1,155 @@
# 后端业务功能开发规范
本文是本项目后端新增或修改业务功能时的权威规范。开发后台管理端业务模块时,必须优先遵循本项目现有架构,而不是套用通用 Spring Boot 三层模板。
## 架构原则
后端业务代码按 `Controller -> ApplicationService -> Model -> db Service/Mapper` 组织。
优先参考这些现有模块:
- `backend/agileboot-domain/src/main/java/com/agileboot/domain/system/post`
- `backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user`
- `backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role`
- `backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice`
- `backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system`
不要让 Controller 直接调用 Mapper,不要把业务规则写在 Controller,也不要直接把 Entity 或 DO 返回给前端。
## 开发准备
开发新后端业务功能前,应先确认需求所属领域、涉及的数据表、接口范围、权限标识和相似模块。本文是后端业务功能开发的统一规范。
开始编码前必须先做一次相似模块调研,确认目标功能最接近 `post``user``role``notice` 还是其他模块,再按相似模块的命名、包路径、注解、分页、异常、权限、日志和测试风格实现。
## 目录结构
后台管理端入口放在 `agileboot-admin`
```text
backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/<area>/
└── XxxController.java
```
业务代码放在 `agileboot-domain`
```text
backend/agileboot-domain/src/main/java/com/agileboot/domain/<area>/xxx/
├── XxxApplicationService.java
├── command/
│ ├── AddXxxCommand.java
│ └── UpdateXxxCommand.java
├── query/
│ └── XxxQuery.java
├── dto/
│ ├── XxxDTO.java
│ └── XxxDetailDTO.java
├── model/
│ ├── XxxModel.java
│ └── XxxModelFactory.java
└── db/
├── XxxEntity.java
├── XxxDO.java
├── XxxMapper.java
├── XxxService.java
└── XxxServiceImpl.java
```
`XxxDO` 是可选文件。只有当查询结果包含 join、聚合、报表字段或其他不完全匹配 Entity 的字段时才创建。
如果业务属于已有领域,例如 `system`,放到现有领域下;如果是独立业务领域,创建新的 `<area>` 包名,例如 `business`
## 各层职责
Controller 只负责 HTTP 层:
- 声明 `@RestController``@RequestMapping``@Validated`、Swagger 注解。
- 接收 `Command``Query`、路径参数和请求参数。
- 使用 `@PreAuthorize("@permission.has('xxx:yyy:zzz')")` 做权限控制。
- 对新增、修改、删除、导出等操作使用 `@AccessLog`
- 调用 `XxxApplicationService`
- 使用 `ResponseDTO.ok(...)` 返回结果。
ApplicationService 负责编排业务用例:
- 分页查询、详情查询、导出查询、新增、修改、删除。
- 创建或加载 `XxxModel`
- 调用领域模型完成业务校验和状态变化。
- 调用 `db` 包中的 Service 完成查询和持久化。
- 将 Entity 或 DO 转换为 DTO。
- 分页结果使用 `PageDTO<T>`
Model 承载业务规则:
-`AddXxxCommand``UpdateXxxCommand` 加载字段。
- 封装唯一性校验、状态校验、是否允许删除、业务状态流转等规则。
- 需要数据库判断时,通过构造注入的 `XxxService` 查询。
- 业务失败时抛出 `ApiException`,不要返回布尔值让上层解释。
ModelFactory 负责创建和加载模型:
- `create()` 返回带有依赖的空模型。
- `loadById(id)` 从数据库加载 Entity,并包装为 Model。
- 如果未找到必要数据,按项目现有异常风格处理。
db 包只负责持久化:
- `XxxEntity` 映射数据库表。
- `XxxDO` 承载复杂查询结果,例如关联表字段、统计字段、报表字段。
- `XxxMapper` 继承 MyBatis Plus `BaseMapper<XxxEntity>`
- `XxxService` 继承 `IService<XxxEntity>`,声明复用型查询方法。
- `XxxServiceImpl` 继承 `ServiceImpl<XxxMapper, XxxEntity>`,实现唯一性检查、关联检查、复杂查询等。
- 复杂 SQL 放到 mapper XML,简单条件优先使用 MyBatis Plus wrapper。
`XxxDO` 属于数据库查询结果对象,不是前端响应对象,也不是请求对象。Mapper 或 Service 可以返回 `XxxDO`,但 ApplicationService 必须转换为 `XxxDTO` 后再交给 Controller 返回。
Command、Query、DTO 的边界:
- `Command` 用于新增、修改等写请求,不要直接接收 Entity。
- `Query` 用于列表、搜索、分页条件,并提供 `toPage()``toQueryWrapper()` 等项目已有风格的方法。
- `DTO` 用于响应前端,不要直接暴露 Entity 或 DO。
- 批量删除优先使用 `BulkOperationCommand<Long>`
## 开发流程
1. 检索并阅读一个最相似的现有模块,确认命名、包路径、注解、分页、异常、测试风格。
2. 确认数据库表结构,保持主键、软删除、审计字段、字段命名与现有表一致。
3. 创建 `db` 包:`Entity``Mapper``Service``ServiceImpl`;如查询结果包含关联表字段、统计字段或报表字段,再增加 `XxxDO`
4. 创建 API 契约:`AddXxxCommand``UpdateXxxCommand``XxxQuery``XxxDTO`,需要详情时添加 `XxxDetailDTO`
5. 创建 `XxxModel``XxxModelFactory`,把业务校验放进 Model。
6. 创建 `XxxApplicationService`,编排查询、新增、修改、删除、导出等用例。
7. 创建 `XxxController`,加路由、权限、操作日志和统一响应。
8. 如功能出现在后台菜单,补充权限标识、菜单 SQL 或清楚说明需要配置的权限码。
9. 增加聚焦测试,至少覆盖核心业务校验和主要用例。
10. 运行相关 Maven 测试或编译检查;如果不能运行,说明原因。
开发过程中如果发现本文未覆盖的模式,优先参考现有模块,而不是引入新的架构风格。确实需要新增模式时,应先更新本文,再按新规范实现。
## 权限、日志和错误
权限码必须和前端菜单或按钮权限保持一致,格式参考现有系统功能:
```java
@PreAuthorize("@permission.has('system:post:add')")
```
操作日志按业务动作选择类型:
```java
@AccessLog(title = "业务名称", businessType = BusinessTypeEnum.ADD)
@AccessLog(title = "业务名称", businessType = BusinessTypeEnum.MODIFY)
@AccessLog(title = "业务名称", businessType = BusinessTypeEnum.DELETE)
@AccessLog(title = "业务名称", businessType = BusinessTypeEnum.EXPORT)
```
业务错误使用 `ApiException``ErrorCode.Business`。新增业务错误时,按现有错误码结构补充枚举或常量,不要在 Controller 中拼接错误响应。
## 验收清单
- 新代码目录结构与相似模块一致。
- Controller 只有 HTTP 编排逻辑。
- 业务校验位于 Model 或 ApplicationService,不散落在前端或 Controller。
- 查询返回 DTO,分页返回 `PageDTO<T>`
- 写请求使用 Command。
- 复杂查询结果对象使用 DO,并在 ApplicationService 转换为 DTO。
- 权限、日志、异常、测试和现有项目风格一致。
+275
View File
@@ -0,0 +1,275 @@
# Clean Code 契约
本文约束新增和修改代码时的代码质量形态。它只关注函数设计、命名、控制结构、错误处理、注释、副作用、重复和可读性,不替代前后端各自的架构开发规范。
## 1. 函数设计
- 新增或修改的函数默认不超过 20 行,不含空行和注释。
- 业务编排函数最多不超过 30 行;超过时必须拆分。
- 函数参数不超过 3 个;超过 3 个必须改为对象参数。
- 圈复杂度 <= 5。
- 嵌套深度 <= 2。
- 必须优先使用 guard clause 提前返回,避免主流程被多层 `if` 包裹。
- 一个函数只做一件事,不混合校验、转换、查询、保存、渲染等多个职责。
Bad:
```ts
function submit(user, form, config, notify) {
if (user) {
if (form.valid) {
if (config.enabled) {
save(user, form);
notify.success();
}
}
}
}
```
Good:
```ts
function submit(options: SubmitOptions) {
if (!options.user) return;
if (!options.form.valid) return;
if (!options.config.enabled) return;
save(options.user, options.form);
options.notify.success();
}
```
## 2. 命名
- 布尔变量必须以 `is``has``can``should` 开头。
- 执行动作的函数使用动词开头,例如 `getUser``validateEmail``saveRole`
- 事件处理函数使用 `handle``on` 前缀,例如 `handleSubmit``onDialogClose`
- 有副作用的函数名必须体现动作,例如 `save``update``delete``load``fetch``sync`
- 避免无意义缩写。
- 避免单字母变量,循环索引 `i` 除外。
Bad:
```ts
const visible = user.status === "enabled";
function userData() {
return api.getUser();
}
```
Good:
```ts
const isVisible = user.status === "enabled";
function getUserData() {
return api.getUser();
}
```
## 3. 控制结构
- 禁止深层 `if/else`
- 连续分支超过 3 个时,优先使用对象映射、`switch`、策略方法或枚举方法。
- 简单数据转换优先使用 `map``filter`
- 不为了“函数式”强行使用难读的 `reduce`
- 当需要提前退出、复杂流程控制或显式循环更清晰时,允许使用 `for` / `for...of`
- 不允许在 `switch` 或长 `if/else` 中堆大量业务逻辑,复杂分支必须拆成独立函数。
Bad:
```ts
function getStatusLabel(status: string) {
if (status === "enabled") return "启用";
if (status === "disabled") return "禁用";
if (status === "pending") return "待处理";
if (status === "locked") return "锁定";
return "未知";
}
```
Good:
```ts
const statusLabels: Record<string, string> = {
enabled: "启用",
disabled: "禁用",
pending: "待处理",
locked: "锁定"
};
function getStatusLabel(status: string) {
return statusLabels[status] ?? "未知";
}
```
## 4. 错误处理
- 禁止吞异常。
- 禁止空 `catch`
- `catch` 后必须处理、记录、转换或重新抛出异常。
- 外部调用必须有明确错误处理,包括网络、数据库、文件、缓存、第三方服务。
- 不允许只打印错误但不中断、不反馈、不恢复状态。
- 不允许把异常转换成无意义的 `null``false` 或空数组,除非调用方能明确区分该状态。
Bad:
```ts
async function loadUser() {
try {
return await api.getUser();
} catch (error) {
console.log(error);
}
}
```
Good:
```ts
async function loadUser() {
try {
return await api.getUser();
} catch (error) {
showErrorMessage("用户信息加载失败");
throw error;
}
}
```
## 5. 注释
- 禁止废话注释,例如 `i++ // 增加 i`
- 代码能清楚表达“做了什么”时,不写解释性注释。
- 复杂算法、特殊兼容、业务规则例外必须说明“为什么这么做”。
- 临时方案必须标明原因和后续处理方式,避免无上下文的 `TODO`
- 注释必须随代码更新,禁止保留过期注释。
Bad:
```ts
// 遍历用户
users.forEach(user => {
// 设置名称
user.name = user.name.trim();
});
```
Good:
```ts
// 后端历史数据可能包含首尾空格,提交前统一清理,避免唯一性校验误判。
const normalizedUsers = users.map(user => ({
...user,
name: user.name.trim()
}));
```
## 6. 副作用
- 优先编写纯函数:相同输入应产生相同输出。
- 数据转换、格式化、校验逻辑应尽量保持无副作用。
- 修改外部状态的函数必须在命名中体现副作用。
- 不在工具函数中偷偷修改入参、全局状态、缓存或组件状态。
- 修改入参前必须显式说明原因;默认应返回新对象或新数组。
Bad:
```ts
function normalizeUser(user: User) {
user.name = user.name.trim();
return user;
}
```
Good:
```ts
function normalizeUser(user: User) {
return {
...user,
name: user.name.trim()
};
}
```
## 7. 重复与抽象
- 禁止复制粘贴相同业务逻辑。
- 出现第 2 次重复时可以接受局部重复。
- 出现第 3 次重复时必须抽取公共函数、组件、常量或配置。
- 不为了“预防未来变化”提前抽象。
- 抽象必须基于已经出现的重复或明确稳定的业务概念。
- 公共函数必须有清晰输入输出,不能依赖隐式上下文。
Bad:
```ts
const userStatus = row.status === 1 ? "启用" : "禁用";
const roleStatus = role.status === 1 ? "启用" : "禁用";
const postStatus = post.status === 1 ? "启用" : "禁用";
```
Good:
```ts
function getEnabledStatusLabel(status: number) {
return status === 1 ? "启用" : "禁用";
}
const userStatus = getEnabledStatusLabel(row.status);
const roleStatus = getEnabledStatusLabel(role.status);
const postStatus = getEnabledStatusLabel(post.status);
```
## 8. 可读性
- 优先让主流程从上到下阅读。
- 变量声明尽量靠近使用位置。
- 避免长链式调用;链式调用过长时拆成中间变量。
- 避免布尔表达式过长;复杂条件必须提取为语义明确的变量或函数。
- 避免魔法值;业务含义明确的数字、字符串应提取为常量。
- 不写“聪明但难懂”的代码。
Bad:
```ts
if ((user.age > 18 && user.status === 1 && !user.deleted) || user.role === "admin") {
allowAccess();
}
```
Good:
```ts
const isActiveAdult = user.age > 18 && user.status === 1 && !user.deleted;
const isAdmin = user.role === "admin";
if (isActiveAdult || isAdmin) {
allowAccess();
}
```
## 9. 自动化检查
完成代码后必须运行与改动相关的检查。
建议静态检查覆盖:
- 函数长度
- 参数数量
- 圈复杂度
- 嵌套深度
-`catch`
- 未使用变量
- 重复分支
- 基础命名规则
## 10. 例外规则
- 如果确实需要突破本契约,必须说明原因。
- 例外只能针对具体代码点,不能作为整个模块放宽标准的理由。
- 为了满足行数限制而机械拆分、降低可读性的改动不接受。
- 可读性优先于形式主义,但必须能解释为什么更清晰。
+198
View File
@@ -0,0 +1,198 @@
# Web 前端业务功能开发规范
本文是 `frontend/web` 新增或修改前端业务功能时的权威规范。当前 Web 项目基于 Vue 3、Vite、Element Plus、Pure Admin、Pinia、Vue Router 和项目自有 `@/utils/http` 封装。开发时必须优先复用现有目录结构、组件和工具链。
本文只覆盖 `frontend/web`,不覆盖 `frontend/app`
## 架构原则
前端业务模块按“页面结构 + API 类型封装 + 业务 hook + 表单/弹窗 + 页面私有组件”组织。
优先参考这些现有模块:
- `frontend/web/src/views/system/post`
- `frontend/web/src/views/system/user`
- `frontend/web/src/views/system/role`
- `frontend/web/src/views/system/notice`
- `frontend/web/src/views/login`
- `frontend/web/src/api/system/post.ts`
不要绕过 `@/utils/http` 直接使用 Axios。不要在 `index.vue` 中堆积复杂列表逻辑。不要硬编码字典状态文本、值或标签样式。
## 开发准备
开发 `frontend/web` 业务功能前,应先确认页面所属业务域、后端接口、菜单路由、权限标识、字典依赖和相似页面。本文是 Web 前端业务功能开发的统一规范。
开始编码前必须先做一次相似模块调研,确认目标功能最接近 `system/post``system/user``system/role``system/notice``login` 还是其他模块,再按相似模块的页面结构、API 类型、hook、表单、字典、表格、分页、排序、删除、导出、权限和组件命名风格实现。
## 目录结构
业务页面放在 `frontend/web/src/views/<area>/<module>/`
```text
frontend/web/src/views/<area>/<module>/
├── index.vue
├── form.vue 或 xxx-form-modal.vue
├── components/
│ ├── private-panel.vue
│ └── private-fragment.vue
└── utils/
├── hook.tsx
├── rule.ts
└── types.ts
```
API 放在 `frontend/web/src/api/<area>/<module>.ts`
```text
frontend/web/src/api/<area>/<module>.ts
```
`components/``rule.ts``types.ts` 按需创建,不要求每个模块都存在。
## 页面私有组件
只被某个页面或模块使用的组件,放在该模块目录下的 `components/`。现有示例是 `frontend/web/src/views/login/components`
页面私有组件适合承载:
- 页面内局部展示组件。
- 局部表单片段。
- 局部抽屉、局部面板、局部弹窗。
- 只服务当前页面流程的交互单元。
页面私有组件不应承载整个业务模块的主流程。主列表、主表单、主弹窗仍按现有模块风格放在 `index.vue``form.vue``xxx-form-modal.vue`
如果组件开始被第二个业务模块复用,应提升到 `frontend/web/src/components`,不要继续放在某个页面私有目录中。只有在明确属于某个业务域且多个同域模块复用时,才考虑创建 `views/<area>/components`;当前项目还没有这个模式,新增前应谨慎。
页面私有组件的判断标准是“是否只服务当前页面或当前模块”。如果组件包含通用交互、通用展示、通用上传、通用选择器等能力,应优先设计为公共组件,而不是藏在某个业务页面目录下。
## 各文件职责
`index.vue` 负责页面结构:
- 搜索表单。
- 表格和表格插槽。
- 工具栏按钮。
- 弹窗、抽屉、页面私有组件的挂载。
- `defineOptions({ name })`,组件 name 应与菜单表中的 `router_name` 保持一致。
`utils/hook.tsx` 负责列表业务状态和行为:
- 查询参数、分页、排序、loading。
- 表格列定义。
- 列表查询、搜索、重置。
- 删除、批量删除、导出。
- 字典值渲染,例如 `el-tag`
`form.vue``xxx-form-modal.vue` 负责新增/编辑:
- 接收 `v-model` 控制显示。
- 接收 `type``row` 等必要 props。
- 维护表单数据和校验状态。
- 调用新增/修改 API。
- 成功后关闭并向父组件发出 `success` 事件。
`utils/rule.ts` 负责复杂或可复用的表单校验规则。简单模块也可以把规则放在表单组件内,保持与现有代码一致。
`utils/types.ts` 只放页面私有类型。跨 API 复用的请求/响应类型优先放在对应 `src/api/<area>/<module>.ts`
## API 规范
每个业务模块在 `src/api/<area>/<module>.ts` 中封装后端接口和 TypeScript 类型。
接口调用使用项目 HTTP 封装:
```ts
http.request<ResponseData<T>>("get", "/path", { params });
http.request<ResponseData<void>>("post", "/path", { data });
```
文件下载使用:
```ts
http.download("/path/excel", fileName, { params });
```
类型命名优先遵循现有风格:
- `XxxListCommand`:列表查询参数。
- `XxxPageResponse`:列表响应行。
- `AddXxxCommand`:新增请求。
- `UpdateXxxCommand`:修改请求。
删除接口如果后端接收 `ids` 查询参数,按现有写法把数组转成字符串,避免 Axios 序列化为 `ids[0]``ids[1]`
## 列表、分页和排序
列表页优先使用 `PureTableBar + pure-table`
分页使用 `PaginationProps`,并通过 `CommonUtils.fillPaginationParams()` 填充查询参数。
排序使用 Element Plus `Sort`,并通过 `CommonUtils.fillSortParams()` 传给后端。
时间范围查询使用 `beginTime``endTime`,优先复用现有 computed 写法处理 `el-date-picker` 的双向绑定。
批量操作必须维护 `multipleSelection`,并在空选择时给出提示。
## 字典和状态
字典来自登录配置并缓存在用户 store 中。
下拉选项使用:
```ts
useUserStoreHook().dictionaryList["common.status"]
```
表格状态渲染使用:
```ts
useUserStoreHook().dictionaryMap["common.status"]
```
不要在页面中硬编码状态 label、value、`el-tag` 类型。字典不存在或可能为空时,新增代码应避免直接访问导致运行时报错。
## 交互和组件
按钮图标优先使用 `useRenderIcon()` 和现有 iconify 图标。
新增/编辑弹窗优先复用 `VDialog` 或相似模块的弹窗写法。
单条删除使用 `el-popconfirm` 或模块现有风格。批量删除使用 `ElMessageBox.confirm` 时,必须处理取消分支并清理选择状态。
操作成功后刷新列表。弹窗通过 `v-model``success` 事件与父组件通信。
跨业务通用能力优先放在 `src/components``src/utils`;页面私有能力保持在页面目录内。
## 权限、路由和菜单
后台业务菜单主要依赖后端菜单和动态路由。新页面的组件 name 必须与菜单表中的 `router_name` 保持一致。
不要随意新增静态路由。只有登录页、重定向、个人中心等固定页面才参考 `src/router/modules` 中的静态路由模式。
如需要按钮权限,优先复用项目已有的 `ReAuth` 或 auth directive 模式,不要自行实现一套权限判断。
## 验证清单
前端改动后优先运行:
```bash
pnpm --dir frontend/web typecheck
pnpm --dir frontend/web lint
```
如果依赖未安装或环境不允许运行,说明原因。
如果只修改文档,不需要运行前端 typecheck 或 lint;如果修改了 TypeScript、Vue、样式或路由/API 文件,应优先运行上述检查。
业务页面至少检查:
- 列表加载。
- 搜索和重置。
- 分页和排序。
- 新增和编辑。
- 单条删除和批量删除。
- 导出。
- 字典下拉和表格状态展示。
- 弹窗关闭、成功回调和列表刷新。
+1 -1
View File
@@ -1,7 +1,7 @@
module.exports = {
tabWidth: 2,
bracketSpacing: true,
singleQuote: false,
arrowParens: "avoid",
trailingComma: "none"
};
-8
View File
@@ -1,8 +0,0 @@
dist/
deploy_versions/
.temp/
.rn_temp/
node_modules/
.DS_Store
.swc
*.local
+29
View File
@@ -204,6 +204,9 @@ importers:
nprogress:
specifier: ^0.2.0
version: 0.2.0
path:
specifier: ^0.12.7
version: 0.12.7
pinia:
specifier: ^2.1.4
version: 2.3.1(typescript@5.0.4)(vue@3.5.34(typescript@5.0.4))
@@ -3995,6 +3998,9 @@ packages:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
inherits@2.0.3:
resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==}
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@@ -4774,6 +4780,9 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
path@0.12.7:
resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
@@ -5350,6 +5359,10 @@ packages:
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
promise-polyfill@7.1.2:
resolution: {integrity: sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ==}
@@ -6216,6 +6229,9 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
util@0.10.4:
resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==}
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@@ -10917,6 +10933,8 @@ snapshots:
once: 1.4.0
wrappy: 1.0.2
inherits@2.0.3: {}
inherits@2.0.4: {}
ini@1.3.8: {}
@@ -11649,6 +11667,11 @@ snapshots:
path-type@4.0.0: {}
path@0.12.7:
dependencies:
process: 0.11.10
util: 0.10.4
pathe@2.0.3:
optional: true
@@ -12160,6 +12183,8 @@ snapshots:
process-nextick-args@2.0.1: {}
process@0.11.10: {}
promise-polyfill@7.1.2: {}
property-expr@2.0.6: {}
@@ -13164,6 +13189,10 @@ snapshots:
util-deprecate@1.0.2: {}
util@0.10.4:
dependencies:
inherits: 2.0.3
uuid@8.3.2: {}
validate-html-nesting@1.2.4: {}
+10 -1
View File
@@ -1,4 +1,13 @@
packages:
- "web"
- "app"
allowBuilds:
'@parcel/watcher': set this to true or false
'@swc/core': set this to true or false
'@tarojs/binding': set this to true or false
'@tarojs/cli': set this to true or false
core-js: set this to true or false
core-js-pure: set this to true or false
esbuild: set this to true or false
typeit: set this to true or false
vue-demi: set this to true or false
-24
View File
@@ -1,24 +0,0 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.eslintcache
report.html
yarn.lock
npm-debug.log*
.pnpm-error.log*
.pnpm-debug.log
tests/**/coverage/
# 本机调试debug配置文件
.vscode/launch.json
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
tsconfig.tsbuildinfo
-31
View File
@@ -1,31 +0,0 @@
{
"recommendations": [
"akamud.vscode-theme-onedark",
"antfu.iconify",
"bradlc.vscode-tailwindcss",
"christian-kohler.npm-intellisense",
"christian-kohler.path-intellisense",
"Codeium.codeium",
"csstools.postcss",
"DavidAnson.vscode-markdownlint",
"dbaeumer.vscode-eslint",
"donjayamanne.githistory",
"dsznajder.es7-react-js-snippets",
"eamodio.gitlens",
"ecmel.vscode-html-css",
"esbenp.prettier-vscode",
"genieai.chatgpt-vscode",
"hollowtree.vue-snippets",
"lokalise.i18n-ally",
"mhutchie.git-graph",
"mikestead.dotenv",
"pmneo.tsimporter",
"streetsidesoftware.code-spell-checker",
"stylelint.vscode-stylelint",
"syler.sass-indented",
"sysoev.language-stylus",
"vscode-icons-team.vscode-icons",
"Vue.volar",
"xabikos.JavaScriptSnippets"
]
}
-22
View File
@@ -1,22 +0,0 @@
{
"Vue3.0快速生成模板": {
"scope": "vue",
"prefix": "Vue3.0",
"body": [
"<template>",
"\t<div>test</div>",
"</template>\n",
"<script lang='ts'>",
"export default {",
"\tsetup() {",
"\t\treturn {}",
"\t}",
"}",
"</script>\n",
"<style lang='scss' scoped>\n",
"</style>",
"$2"
],
"description": "Vue3.0"
}
}
-17
View File
@@ -1,17 +0,0 @@
{
"Vue3.2+快速生成模板": {
"scope": "vue",
"prefix": "Vue3.2+",
"body": [
"<script setup lang='ts'>",
"</script>\n",
"<template>",
"\t<div>test</div>",
"</template>\n",
"<style lang='scss' scoped>\n",
"</style>",
"$2"
],
"description": "Vue3.2+"
}
}
-20
View File
@@ -1,20 +0,0 @@
{
"Vue3.3+defineOptions快速生成模板": {
"scope": "vue",
"prefix": "Vue3.3+",
"body": [
"<script setup lang='ts'>",
"defineOptions({",
"\tname: ''",
"})",
"</script>\n",
"<template>",
"\t<div>test</div>",
"</template>\n",
"<style lang='scss' scoped>\n",
"</style>",
"$2"
],
"description": "Vue3.3+defineOptions快速生成模板"
}
}
+1
View File
@@ -41,6 +41,7 @@
"jsencrypt": "^3.3.2",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"path": "^0.12.7",
"pinia": "^2.1.4",
"pinyin-pro": "^3.15.2",
"cropperjs": "^1.5.13",
@@ -0,0 +1,206 @@
import { http } from "@/utils/http";
export type SettlementStatusValue =
| "NONE"
| "SETTLED"
| "UNSETTLED"
| "PARTIAL";
export type CollaborationFileType = "GOODS_IMAGE" | "ATTACHMENT";
export interface SettlementStatusDTO {
status: SettlementStatusValue;
label: string;
}
export interface CollaborationRecordListCommand extends BasePageQuery {
brand?: string;
goods?: string;
cooperationPlatform?: string;
purchaseBeginTime?: string;
purchaseEndTime?: string;
}
export interface CollaborationTaskCommand {
releaseDate?: string;
}
export interface CollaborationExpenditureCommand {
spendDate?: string;
amount?: number;
purpose?: string;
}
export interface CollaborationSettlementCommand {
settleDate?: string;
method?: string;
income?: number;
purpose?: string;
}
export interface CollaborationFileCommand {
fileType: CollaborationFileType;
url: string;
fileName?: string;
newFileName?: string;
originalFilename?: string;
}
export interface AddCollaborationRecordCommand {
brand: string;
goods: string;
cooperationPlatform?: string;
imageReturnNum: number;
retainedMethod?: string;
cooperatedMethod?: string;
purchaseMethod?: string;
purchasePrice?: number;
purchaseDate?: string;
purchasePlatform?: string;
deadline?: string;
remuneration?: number;
completeDate?: string;
requirements?: string;
remark?: string;
tasks: CollaborationTaskCommand[];
expenditures: CollaborationExpenditureCommand[];
settlements: CollaborationSettlementCommand[];
files: CollaborationFileCommand[];
}
export interface UpdateCollaborationRecordCommand
extends AddCollaborationRecordCommand {
recordId: number;
}
export interface CollaborationRecordPageResponse {
recordId: number;
brand: string;
goods: string;
cooperationPlatform?: string;
imageReturnNum: number;
retainedMethod?: string;
cooperatedMethod?: string;
purchaseMethod?: string;
purchasePrice?: number;
purchaseDate?: string;
purchasePlatform?: string;
deadline?: string;
remuneration?: number;
completeDate?: string;
requirements?: string;
remark?: string;
tasksNum: number;
completedTasksNum: number;
purchaseSettlementStatus: SettlementStatusDTO;
deliverySettlementStatus: SettlementStatusDTO;
remunerationSettlementStatus: SettlementStatusDTO;
createTime: string;
}
export interface CollaborationRecordDetailResponse
extends CollaborationRecordPageResponse {
tasks: Array<
CollaborationTaskCommand & { taskId?: number; sortOrder?: number }
>;
expenditures: Array<
CollaborationExpenditureCommand & { expenditureId?: number }
>;
settlements: Array<
CollaborationSettlementCommand & { settlementId?: number }
>;
files: Array<
CollaborationFileCommand & { fileId?: number; sortOrder?: number }
>;
}
export interface CollaborationOptionResponse {
type: string;
label: string;
values: string[];
}
export interface CollaborationMonthlyStatisticsResponse {
month: number;
purchasePrice: number;
expenditureAmount: number;
settledRemuneration: number;
settledTotal: number;
}
export interface UploadResponse {
url: string;
fileName: string;
newFileName: string;
originalFilename: string;
}
export const getCollaborationRecordListApi = (
params: CollaborationRecordListCommand
) => {
return http.request<ResponseData<PageDTO<CollaborationRecordPageResponse>>>(
"get",
"/collaboration/record/list",
{ params }
);
};
export const getCollaborationRecordInfoApi = (recordId: number) => {
return http.request<ResponseData<CollaborationRecordDetailResponse>>(
"get",
`/collaboration/record/${recordId}`
);
};
export const addCollaborationRecordApi = (
data: AddCollaborationRecordCommand
) => {
return http.request<ResponseData<void>>("post", "/collaboration/record", {
data
});
};
export const updateCollaborationRecordApi = (
data: UpdateCollaborationRecordCommand
) => {
return http.request<ResponseData<void>>("put", "/collaboration/record", {
data
});
};
export const deleteCollaborationRecordApi = (data: Array<number>) => {
return http.request<ResponseData<void>>("delete", "/collaboration/record", {
params: {
ids: data.toString()
}
});
};
export const getCollaborationOptionsApi = () => {
return http.request<ResponseData<CollaborationOptionResponse[]>>(
"get",
"/collaboration/record/options"
);
};
export const getCollaborationMonthlyStatisticsApi = (year: number) => {
return http.request<ResponseData<CollaborationMonthlyStatisticsResponse[]>>(
"get",
"/collaboration/record/monthly-statistics",
{
params: { year }
}
);
};
export const uploadCollaborationFileApi = (data: FormData) => {
return http.request<ResponseData<UploadResponse>>(
"post",
"/file/upload",
{ data },
{
headers: {
"Content-Type": "multipart/form-data"
}
}
);
};
@@ -89,6 +89,7 @@ function handleClose(
:key="index"
v-bind="options"
v-model="options.visible"
:align-center="options.alignCenter ?? true"
:fullscreen="fullscreen ? true : options?.fullscreen ? true : false"
@close="handleClose(options, index)"
@opened="eventsCallBack('open', options, index)"
@@ -40,7 +40,7 @@
class="header-btn"
/>
<el-button
:icon="Close"
:icon="closeIcon"
link
@click="handleCloseClick"
class="header-btn"
@@ -81,7 +81,8 @@
import { computed, ref } from "vue";
import { ElDialog, ElButton, ElScrollbar } from "element-plus";
import { DialogEmits, DialogProps } from "./dialog";
import { Close } from "@element-plus/icons-vue";
import Close from "@iconify-icons/ep/close";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import FullScreenMaximize from "@/assets/svg/FullScreenMaximize.svg?component";
import FullScreenMinimize from "@/assets/svg/FullScreenMinimize.svg?component";
@@ -96,6 +97,7 @@ const props = withDefaults(defineProps<DialogProps>(), {
loading: false
});
const emits = defineEmits<DialogEmits>();
const closeIcon = useRenderIcon(Close);
const visible = computed<boolean>({
get: () => {
@@ -107,7 +109,6 @@ const visible = computed<boolean>({
const fullScreenState = ref(!!props.initFullScreen);
const fullScreen = computed<boolean>({
get: () => {
console.log("fullScreen getter", props.fullScreen, fullScreenState.value);
// 非受控模式,状态完全由组件内部控制
if (props.fullScreen === undefined) {
return fullScreenState.value;
@@ -117,7 +118,6 @@ const fullScreen = computed<boolean>({
},
set: v => {
fullScreenState.value = v;
console.log("fullScreen setter", v, props.fullScreen);
// 受控模式,将状态更新到父组件
if (props.fullScreen !== undefined) {
emits("update:fullScreen", v);
@@ -1,4 +1,5 @@
<script setup lang="ts">
import path from "path";
import { getConfig } from "@/config";
import { menuType } from "../../types";
import extraIcon from "./extraIcon.vue";
@@ -174,10 +175,7 @@ function resolvePath(routePath) {
return routePath || props.basePath;
} else {
// 使用path.posix.resolve替代path.resolve 避免windows环境下使用electron出现盘符问题
const segments = `${props.basePath}/${routePath}`
.split("/")
.filter(Boolean);
return `/${segments.join("/")}`;
return path.posix.resolve(props.basePath, routePath);
}
}
</script>
+27
View File
@@ -69,11 +69,38 @@
}
}
.el-overlay-dialog:has(.pure-dialog.el-dialog:not(.is-fullscreen)) {
box-sizing: border-box;
display: flex !important;
align-items: center;
justify-content: center;
padding: 24px 0;
overflow: hidden;
}
.pure-dialog {
&.el-dialog:not(.is-fullscreen) {
display: flex;
flex-direction: column;
max-height: min(88vh, calc(100vh - 48px));
margin: auto !important;
}
.pure-dialog-svg {
color: var(--el-color-info);
}
.el-dialog__header,
.el-dialog__footer {
flex: none;
}
.el-dialog__body {
flex: 1;
min-height: 0;
overflow-y: auto;
}
.el-dialog__headerbtn {
top: 20px;
right: 14px;
@@ -0,0 +1,238 @@
<script setup lang="ts">
import { h, ref } from "vue";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { addDialog } from "@/components/ReDialog";
import { useCollaborationRecordHook } from "./utils/hook";
import { CollaborationRecordPageResponse } from "@/api/collaboration/record";
import RecordFormModal from "./record-form-modal.vue";
import AddFill from "@iconify-icons/ri/add-circle-line";
import Delete from "@iconify-icons/ep/delete";
import EditPen from "@iconify-icons/ep/edit-pen";
import Refresh from "@iconify-icons/ep/refresh";
import Search from "@iconify-icons/ep/search";
defineOptions({
name: "CollaborationRecord"
});
const tableRef = ref();
const searchFormRef = ref();
const {
searchFormParams,
pageLoading,
columns,
dataList,
pagination,
defaultSort,
deadlineRange,
purchaseRange,
optionMap,
multipleSelection,
onSearch,
onSortChanged,
getRecordList,
resetForm,
handleDelete,
handleBulkDelete
} = useCollaborationRecordHook();
const recordFormRef = ref();
function openDialog(
type: "add" | "update",
row?: CollaborationRecordPageResponse
) {
addDialog({
title: type === "add" ? "新增合作记录" : "编辑合作记录",
props: {
type,
row,
optionMap
},
width: "1180px",
top: "6vh",
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(RecordFormModal as any, { ref: recordFormRef }),
beforeSure: async done => {
const isSuccess = await recordFormRef.value.handleConfirm();
if (isSuccess) {
done();
onSearch(tableRef);
}
}
});
}
</script>
<template>
<div class="main">
<el-form
ref="searchFormRef"
:inline="true"
:model="searchFormParams"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
>
<el-form-item label="品牌" prop="brand">
<el-input
v-model="searchFormParams.brand"
placeholder="请输入品牌"
clearable
class="!w-[180px]"
/>
</el-form-item>
<el-form-item label="物品" prop="goods">
<el-input
v-model="searchFormParams.goods"
placeholder="请输入物品"
clearable
class="!w-[180px]"
/>
</el-form-item>
<el-form-item label="合作平台" prop="cooperationPlatform">
<el-select
v-model="searchFormParams.cooperationPlatform"
placeholder="请选择合作平台"
clearable
class="!w-[160px]"
>
<el-option
v-for="item in optionMap.cooperationPlatform"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="预完成日期">
<el-date-picker
v-model="deadlineRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
class="!w-[240px]"
/>
</el-form-item>
<el-form-item label="购入日期">
<el-date-picker
v-model="purchaseRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
class="!w-[240px]"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon(Search)"
:loading="pageLoading"
@click="onSearch(tableRef)"
>
搜索
</el-button>
<el-button
:icon="useRenderIcon(Refresh)"
@click="resetForm(searchFormRef, tableRef)"
>
重置
</el-button>
</el-form-item>
</el-form>
<PureTableBar
title="合作记录"
:columns="columns"
@refresh="onSearch(tableRef)"
>
<template #buttons>
<el-button
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog('add')"
>
新增合作
</el-button>
<el-button
type="danger"
:icon="useRenderIcon(Delete)"
@click="handleBulkDelete(tableRef)"
>
批量删除
</el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<pure-table
border
ref="tableRef"
align-whole="center"
showOverflowTooltip
table-layout="auto"
:loading="pageLoading"
:size="size"
adaptive
:data="dataList"
:columns="dynamicColumns"
:default-sort="defaultSort"
:pagination="pagination"
:paginationSmall="size === 'small'"
:header-cell-style="{
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)'
}"
@page-size-change="getRecordList"
@page-current-change="getRecordList"
@sort-change="onSortChanged"
@selection-change="
rows => (multipleSelection = rows.map(item => item.recordId))
"
>
<template #operation="{ row }">
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(EditPen)"
@click="openDialog('update', row)"
>
编辑
</el-button>
<el-popconfirm
:title="`是否确认删除编号为${row.recordId}的合作记录`"
@confirm="handleDelete(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
type="danger"
:size="size"
:icon="useRenderIcon(Delete)"
>
删除
</el-button>
</template>
</el-popconfirm>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>
<style scoped lang="scss">
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
</style>
@@ -0,0 +1,558 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from "vue";
import { ElMessage, FormInstance, FormRules } from "element-plus";
import type {
UploadFile,
UploadRequestOptions,
UploadUserFile
} from "element-plus";
import Plus from "@iconify-icons/ep/plus";
import {
AddCollaborationRecordCommand,
CollaborationFileCommand,
CollaborationFileType,
CollaborationRecordPageResponse,
UpdateCollaborationRecordCommand,
addCollaborationRecordApi,
getCollaborationRecordInfoApi,
updateCollaborationRecordApi,
uploadCollaborationFileApi
} from "@/api/collaboration/record";
interface Props {
type: "add" | "update";
row?: CollaborationRecordPageResponse;
optionMap: Record<string, string[]>;
}
const props = defineProps<Props>();
const defaultFormData = (): AddCollaborationRecordCommand &
Partial<UpdateCollaborationRecordCommand> => ({
recordId: 0,
brand: "",
goods: "",
cooperationPlatform: "小红书",
imageReturnNum: 1,
retainedMethod: "寄拍",
cooperatedMethod: "水下",
purchaseMethod: "拍单",
purchasePrice: undefined,
purchaseDate: "",
purchasePlatform: "",
deadline: "",
remuneration: undefined,
completeDate: "",
requirements: "",
remark: "",
tasks: [{ releaseDate: "" }],
expenditures: [],
settlements: [],
files: []
});
const formData = reactive(defaultFormData());
const formRef = ref<FormInstance>();
const previewImageUrl = ref("");
const isImagePreviewVisible = ref(false);
const rules: FormRules = {
brand: [{ required: true, message: "品牌不能为空" }],
goods: [{ required: true, message: "物品不能为空" }],
cooperationPlatform: [{ required: true, message: "合作平台不能为空" }],
imageReturnNum: [{ required: true, message: "返图数量不能为空" }],
deadline: [{ required: true, message: "预完成日期不能为空" }]
};
const goodsImages = computed(() =>
formData.files.filter(item => item.fileType === "GOODS_IMAGE")
);
const attachments = computed(() =>
formData.files.filter(item => item.fileType === "ATTACHMENT")
);
const goodsImageUploadFiles = computed<UploadUserFile[]>(() =>
goodsImages.value.map(file => ({
name: getFileName(file),
url: getFileUrl(file)
}))
);
const attachmentUploadFiles = computed<UploadUserFile[]>(() =>
attachments.value.map(file => ({
name: getFileName(file),
url: getFileUrl(file)
}))
);
async function handleOpened() {
resetFormData();
if (props.type === "update" && props.row?.recordId) {
await loadDetail(props.row.recordId);
}
}
function resetFormData() {
Object.assign(formData, defaultFormData());
formRef.value?.clearValidate();
}
async function loadDetail(recordId: number) {
const { data } = await getCollaborationRecordInfoApi(recordId);
Object.assign(formData, {
...defaultFormData(),
...data,
tasks: data.tasks.length ? data.tasks : [{ releaseDate: "" }],
expenditures: data.expenditures,
settlements: data.settlements,
files: data.files
});
}
function addTask() {
formData.tasks.push({ releaseDate: "" });
}
function removeTask(index: number) {
formData.tasks.splice(index, 1);
}
function addExpenditure() {
formData.expenditures.push({
spendDate: "",
amount: undefined,
purpose: ""
});
}
function removeExpenditure(index: number) {
formData.expenditures.splice(index, 1);
}
function addSettlement() {
formData.settlements.push({
settleDate: "",
method: "",
income: undefined,
purpose: ""
});
}
function removeSettlement(index: number) {
formData.settlements.splice(index, 1);
}
async function handleUpload(
option: UploadRequestOptions,
fileType: CollaborationFileType
) {
const data = new FormData();
data.append("file", option.file);
const response = await uploadCollaborationFileApi(data);
formData.files.push(toFileCommand(response.data, fileType));
option.onSuccess(response.data);
}
function toFileCommand(file, fileType: CollaborationFileType) {
return {
fileType,
url: file.url,
fileName: file.fileName,
newFileName: file.newFileName,
originalFilename: file.originalFilename
};
}
function removeFile(file: CollaborationFileCommand) {
const index = formData.files.indexOf(file);
if (index >= 0) {
formData.files.splice(index, 1);
}
}
function handlePreviewImage(file: UploadFile) {
if (!file.url) return;
previewImageUrl.value = file.url;
isImagePreviewVisible.value = true;
}
function handleRemoveGoodsImage(file: UploadFile) {
const target = goodsImages.value.find(item => getFileUrl(item) === file.url);
if (target) {
removeFile(target);
}
}
function handlePreviewAttachment(file: UploadFile) {
if (!file.url) {
ElMessage.warning("文件地址不存在");
return;
}
window.open(file.url, "_blank", "noopener,noreferrer");
}
function handleRemoveAttachment(file: UploadFile) {
const target = attachments.value.find(item => getFileUrl(item) === file.url);
if (target) {
removeFile(target);
}
}
function getFileName(file: CollaborationFileCommand) {
return file.originalFilename || file.newFileName || "未命名文件";
}
function getFileUrl(file: CollaborationFileCommand) {
if (file.url) return file.url;
if (!file.fileName) return "";
return `${import.meta.env.VITE_APP_BASE_API}${file.fileName}`;
}
async function handleConfirm() {
const isValid = await formRef.value?.validate().catch(() => false);
if (!isValid) return false;
return submitForm();
}
async function submitForm() {
try {
if (props.type === "add") {
await addCollaborationRecordApi(formData);
} else {
await updateCollaborationRecordApi(
formData as UpdateCollaborationRecordCommand
);
}
ElMessage.success("提交成功");
return true;
} catch (e) {
ElMessage.error((e as Error)?.message || "提交失败");
return false;
}
}
onMounted(handleOpened);
defineExpose({ handleConfirm });
</script>
<template>
<el-form
ref="formRef"
class="record-form"
:model="formData"
:rules="rules"
label-width="112px"
>
<el-tabs>
<el-tab-pane label="基本信息">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item prop="brand" label="品牌" required>
<el-input v-model="formData.brand" placeholder="请输入品牌" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="goods" label="物品" required>
<el-input v-model="formData.goods" placeholder="请输入物品" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="cooperationPlatform" label="合作平台" required>
<el-select v-model="formData.cooperationPlatform" clearable>
<el-option
v-for="item in optionMap.cooperationPlatform"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="imageReturnNum" label="返图数量" required>
<el-input-number
:min="1"
controls-position="right"
v-model="formData.imageReturnNum"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="留存方式">
<el-select v-model="formData.retainedMethod">
<el-option
v-for="item in optionMap.retainedMethod"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合作方式">
<el-select v-model="formData.cooperatedMethod">
<el-option
v-for="item in optionMap.cooperatedMethod"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="购入方式">
<el-select v-model="formData.purchaseMethod">
<el-option
v-for="item in optionMap.purchaseMethod"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="购入金额">
<el-input-number
:min="0"
controls-position="right"
v-model="formData.purchasePrice"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="购入平台">
<el-select v-model="formData.purchasePlatform" clearable>
<el-option
v-for="item in optionMap.purchasePlatform"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="购入日期">
<el-date-picker
v-model="formData.purchaseDate"
value-format="YYYY-MM-DD"
type="date"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="deadline" label="预完成日期" required>
<el-date-picker
v-model="formData.deadline"
value-format="YYYY-MM-DD"
type="date"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="稿费">
<el-input-number
:min="0"
controls-position="right"
v-model="formData.remuneration"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="物品图片">
<el-upload
multiple
accept="image/*"
class="goods-image-upload"
list-type="picture-card"
:file-list="goodsImageUploadFiles"
:http-request="option => handleUpload(option, 'GOODS_IMAGE')"
:on-preview="handlePreviewImage"
:on-remove="handleRemoveGoodsImage"
>
<IconifyIconOffline class="upload-plus" :icon="Plus" />
</el-upload>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="附件">
<el-upload
multiple
class="attachment-upload"
:file-list="attachmentUploadFiles"
:http-request="option => handleUpload(option, 'ATTACHMENT')"
:on-preview="handlePreviewAttachment"
:on-remove="handleRemoveAttachment"
>
<el-button>上传附件</el-button>
</el-upload>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="拍摄要求">
<el-input
type="textarea"
:rows="3"
v-model="formData.requirements"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注">
<el-input type="textarea" :rows="3" v-model="formData.remark" />
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="笔记任务">
<el-button type="primary" plain @click="addTask">添加笔记</el-button>
<div
v-for="(item, index) in formData.tasks"
:key="index"
class="line-item"
>
<el-date-picker
v-model="item.releaseDate"
value-format="YYYY-MM-DD"
type="date"
placeholder="发布日期"
/>
<el-button type="danger" link @click="removeTask(index)"
>删除</el-button
>
</div>
</el-tab-pane>
<el-tab-pane label="支出信息">
<el-button type="primary" plain @click="addExpenditure"
>添加支出</el-button
>
<div
v-for="(item, index) in formData.expenditures"
:key="index"
class="line-item"
>
<el-date-picker
v-model="item.spendDate"
value-format="YYYY-MM-DD"
type="date"
placeholder="支出日期"
/>
<el-input-number
:min="0"
controls-position="right"
v-model="item.amount"
placeholder="金额"
/>
<el-select v-model="item.purpose" placeholder="用途" clearable>
<el-option
v-for="option in optionMap.expenditurePurpose"
:key="option"
:label="option"
:value="option"
/>
</el-select>
<el-button type="danger" link @click="removeExpenditure(index)"
>删除</el-button
>
</div>
</el-tab-pane>
<el-tab-pane label="结款信息">
<el-button type="primary" plain @click="addSettlement"
>添加结款</el-button
>
<div
v-for="(item, index) in formData.settlements"
:key="index"
class="line-item"
>
<el-date-picker
v-model="item.settleDate"
value-format="YYYY-MM-DD"
type="date"
placeholder="结款日期"
/>
<el-select v-model="item.method" placeholder="方式" clearable>
<el-option
v-for="option in optionMap.settlementMethod"
:key="option"
:label="option"
:value="option"
/>
</el-select>
<el-input-number
:min="0"
controls-position="right"
v-model="item.income"
placeholder="金额"
/>
<el-select v-model="item.purpose" placeholder="用途" clearable>
<el-option
v-for="option in optionMap.settlementPurpose"
:key="option"
:label="option"
:value="option"
/>
</el-select>
<el-button type="danger" link @click="removeSettlement(index)"
>删除</el-button
>
</div>
</el-tab-pane>
</el-tabs>
</el-form>
<el-dialog v-model="isImagePreviewVisible" append-to-body>
<img class="preview-image" :src="previewImageUrl" alt="" />
</el-dialog>
</template>
<style scoped lang="scss">
.line-item {
display: flex;
gap: 12px;
align-items: center;
margin-top: 12px;
}
.record-form {
:deep(.el-row .el-select),
:deep(.el-row .el-date-editor.el-input),
:deep(.el-row .el-input-number) {
width: 100%;
}
:deep(.el-input-number .el-input__inner) {
text-align: left;
}
}
.goods-image-upload {
:deep(.el-upload--picture-card),
:deep(.el-upload-list--picture-card .el-upload-list__item) {
width: 96px;
height: 96px;
line-height: 96px;
}
}
.upload-plus {
font-size: 24px;
color: var(--el-text-color-placeholder);
}
.attachment-upload {
width: 100%;
max-width: 100%;
}
.preview-image {
display: block;
max-width: 100%;
margin: 0 auto;
}
</style>
@@ -0,0 +1,241 @@
import dayjs from "dayjs";
import { message } from "@/utils/message";
import { ElMessageBox, Sort } from "element-plus";
import { computed, onMounted, reactive, ref, toRaw } from "vue";
import { CommonUtils } from "@/utils/common";
import { PaginationProps } from "@pureadmin/table";
import {
CollaborationOptionResponse,
CollaborationRecordListCommand,
CollaborationRecordPageResponse,
deleteCollaborationRecordApi,
getCollaborationOptionsApi,
getCollaborationRecordListApi
} from "@/api/collaboration/record";
const statusTypeMap = {
NONE: "info",
SETTLED: "success",
UNSETTLED: "danger",
PARTIAL: "warning"
};
export function useCollaborationRecordHook() {
const defaultSort: Sort = {
prop: "deadline",
order: "descending"
};
const pagination: PaginationProps = {
total: 0,
pageSize: 10,
currentPage: 1,
background: true
};
const searchFormParams = reactive<CollaborationRecordListCommand>({
brand: "",
goods: "",
cooperationPlatform: undefined
});
const deadlineRange = computed<[string, string] | null>({
get: () => getRange(searchFormParams.beginTime, searchFormParams.endTime),
set: v => fillRange(v, "beginTime", "endTime")
});
const purchaseRange = computed<[string, string] | null>({
get: () =>
getRange(
searchFormParams.purchaseBeginTime,
searchFormParams.purchaseEndTime
),
set: v => fillRange(v, "purchaseBeginTime", "purchaseEndTime")
});
const dataList = ref<CollaborationRecordPageResponse[]>([]);
const optionMap = ref<Record<string, string[]>>({});
const pageLoading = ref(true);
const multipleSelection = ref<number[]>([]);
const sortState = ref<Sort>(defaultSort);
const columns: TableColumnList = [
{ type: "selection", align: "left" },
{ label: "品牌", prop: "brand", minWidth: 120 },
{ label: "物品", prop: "goods", minWidth: 120 },
{ label: "合作平台", prop: "cooperationPlatform", minWidth: 110 },
{ label: "留存方式", prop: "retainedMethod", minWidth: 100 },
{ label: "购入方式", prop: "purchaseMethod", minWidth: 100 },
{
label: "预完成日期",
prop: "deadline",
minWidth: 130,
sortable: "custom",
formatter: ({ deadline }) => formatDate(deadline)
},
{
label: "任务进度",
minWidth: 130,
cellRenderer: ({ row }) => `${row.completedTasksNum}/${row.tasksNum}`
},
{
label: "拍单费用",
minWidth: 110,
cellRenderer: ({ row, props }) =>
renderStatus(row.purchaseSettlementStatus, props.size)
},
{
label: "快递费用",
minWidth: 110,
cellRenderer: ({ row, props }) =>
renderStatus(row.deliverySettlementStatus, props.size)
},
{
label: "稿费",
minWidth: 110,
cellRenderer: ({ row, props }) =>
renderStatus(row.remunerationSettlementStatus, props.size)
},
{
label: "创建时间",
prop: "createTime",
minWidth: 160,
sortable: "custom",
formatter: ({ createTime }) => formatDateTime(createTime)
},
{ label: "操作", fixed: "right", width: 140, slot: "operation" }
];
function getRange(start?: string, end?: string) {
if (!start || !end) return null;
return [start, end] as [string, string];
}
function fillRange(v, startKey: string, endKey: string) {
searchFormParams[startKey] = v?.length === 2 ? v[0] : undefined;
searchFormParams[endKey] = v?.length === 2 ? v[1] : undefined;
}
function formatDate(value?: string) {
return value ? dayjs(value).format("YYYY-MM-DD") : "";
}
function formatDateTime(value?: string) {
return value ? dayjs(value).format("YYYY-MM-DD HH:mm:ss") : "";
}
function renderStatus(status, size) {
if (!status) return "";
return (
<el-tag size={size} type={statusTypeMap[status.status]} effect="plain">
{status.label}
</el-tag>
);
}
function onSortChanged(sort: Sort) {
sortState.value = sort;
pagination.currentPage = 1;
getRecordList();
}
async function onSearch(tableRef) {
tableRef.getTableRef().sort("deadline", "descending");
}
function resetForm(formEl, tableRef) {
if (!formEl) return;
formEl.resetFields();
fillRange(null, "beginTime", "endTime");
fillRange(null, "purchaseBeginTime", "purchaseEndTime");
onSearch(tableRef);
}
async function getRecordList() {
pageLoading.value = true;
CommonUtils.fillSortParams(searchFormParams, sortState.value);
CommonUtils.fillPaginationParams(searchFormParams, pagination);
const { data } = await getCollaborationRecordListApi(
toRaw(searchFormParams)
).finally(() => {
pageLoading.value = false;
});
dataList.value = data.rows;
pagination.total = data.total;
}
async function getOptions() {
const { data } = await getCollaborationOptionsApi();
optionMap.value = data.reduce(
(result, item: CollaborationOptionResponse) => {
result[item.type] = item.values;
return result;
},
{}
);
}
async function handleDelete(row: CollaborationRecordPageResponse) {
await deleteCollaborationRecordApi([row.recordId]);
message(`您删除了编号为${row.recordId}的合作记录`, { type: "success" });
getRecordList();
}
async function handleBulkDelete(tableRef) {
if (multipleSelection.value.length === 0) {
message("请选择需要删除的数据", { type: "warning" });
return;
}
confirmBulkDelete(tableRef);
}
function confirmBulkDelete(tableRef) {
ElMessageBox.confirm(
`确认删除编号为[ ${multipleSelection.value} ]的合作记录吗?`,
"系统提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
draggable: true
}
)
.then(deleteSelectedRecords)
.catch(() => {
message("取消删除", { type: "info" });
tableRef.getTableRef().clearSelection();
});
}
async function deleteSelectedRecords() {
await deleteCollaborationRecordApi(multipleSelection.value);
message(`您删除了编号为[ ${multipleSelection.value} ]的合作记录`, {
type: "success"
});
getRecordList();
}
onMounted(() => {
getOptions();
getRecordList();
});
return {
searchFormParams,
pageLoading,
columns,
dataList,
pagination,
defaultSort,
deadlineRange,
purchaseRange,
optionMap,
multipleSelection,
onSearch,
onSortChanged,
getRecordList,
resetForm,
handleDelete,
handleBulkDelete
};
}
@@ -0,0 +1,48 @@
<script setup lang="ts">
import { useCollaborationStatisticsHook } from "./utils/hook";
defineOptions({
name: "CollaborationStatistics"
});
const { chartRef, selectedYear, yearOptions, getStatistics } =
useCollaborationStatisticsHook();
</script>
<template>
<div class="main">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span>月度统计</span>
<el-select
v-model="selectedYear"
class="!w-[140px]"
@change="getStatistics"
>
<el-option
v-for="year in yearOptions"
:key="year"
:label="`${year}年`"
:value="year"
/>
</el-select>
</div>
</template>
<div ref="chartRef" class="chart" />
</el-card>
</div>
</template>
<style scoped lang="scss">
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.chart {
width: 100%;
height: 600px;
}
</style>
@@ -0,0 +1,84 @@
import { onMounted, ref } from "vue";
import * as echarts from "echarts";
import { getCollaborationMonthlyStatisticsApi } from "@/api/collaboration/record";
export function useCollaborationStatisticsHook() {
const currentYear = new Date().getFullYear();
const selectedYear = ref(currentYear);
const yearOptions = Array.from(
{ length: currentYear - 2012 },
(_, index) => currentYear - index
);
const chartRef = ref<HTMLElement>();
let chart: echarts.ECharts | undefined;
async function getStatistics() {
const { data } = await getCollaborationMonthlyStatisticsApi(
selectedYear.value
);
renderChart(data);
}
function renderChart(data) {
chart?.dispose();
chart = echarts.init(chartRef.value);
chart.setOption({
tooltip: { trigger: "axis", axisPointer: { type: "shadow" } },
legend: {
data: ["拍单费用", "支出费用", "已结稿费", "已结总费用"],
top: "2%",
right: "0"
},
grid: { left: "0%", right: "0%", bottom: "0%", containLabel: true },
xAxis: { type: "category", data: data.map(item => `${item.month}`) },
yAxis: { type: "value" },
series: [
buildSeries(
"拍单费用",
data.map(item => item.purchasePrice),
"#ffbe00"
),
buildSeries(
"支出费用",
data.map(item => item.expenditureAmount),
"#f56c6c"
),
buildSeries(
"已结稿费",
data.map(item => item.settledRemuneration),
"#409eff"
),
buildSeries(
"已结总费用",
data.map(item => item.settledTotal),
"#67c23a"
)
]
});
}
function buildSeries(name: string, data: number[], color: string) {
return {
name,
data,
type: "bar",
color
};
}
function resizeChart() {
chart?.resize({ width: "auto" });
}
onMounted(() => {
getStatistics();
window.addEventListener("resize", resizeChart);
});
return {
chartRef,
selectedYear,
yearOptions,
getStatistics
};
}
+58 -15
View File
@@ -1,8 +1,9 @@
<script setup lang="ts">
import { ref } from "vue";
import { h, ref } from "vue";
import { usePostHook } from "./utils/hook";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { addDialog } from "@/components/ReDialog";
import Delete from "@iconify-icons/ep/delete";
import Search from "@iconify-icons/ep/search";
@@ -12,8 +13,15 @@ import { useUserStoreHook } from "@/store/modules/user";
import { CommonUtils } from "@/utils/common";
import PostFormModal from "@/views/system/post/post-form-modal.vue";
import EditPen from "@iconify-icons/ep/edit-pen";
import { PostPageResponse } from "@/api/system/post";
import {
AddPostCommand,
PostPageResponse,
UpdatePostCommand,
addPostApi,
updatePostApi
} from "@/api/system/post";
import AddFill from "@iconify-icons/ri/add-circle-line";
import { ElMessage } from "element-plus";
/** 组件name最好和菜单表中的router_name一致 */
defineOptions({
@@ -43,13 +51,55 @@ const {
handleBulkDelete
} = usePostHook();
const opType = ref<"add" | "update">("add");
const modalVisible = ref(false);
const opRow = ref<PostPageResponse>();
const postFormRef = ref();
function getPostFormData(row?: PostPageResponse) {
return {
postId: row?.postId ?? 0,
postCode: row?.postCode ?? "",
postName: row?.postName ?? "",
postSort: row?.postSort ?? 1,
remark: row?.remark ?? "",
status: row?.status?.toString() ?? ""
};
}
async function submitPostForm(
type: "add" | "update",
formData: AddPostCommand & Partial<UpdatePostCommand>,
done: () => void
) {
if (type === "add") {
await addPostApi(formData);
} else {
await updatePostApi(formData as UpdatePostCommand);
}
ElMessage.success("提交成功");
done();
onSearch(tableRef);
}
function openDialog(type: "add" | "update", row?: PostPageResponse) {
opType.value = type;
opRow.value = row;
modalVisible.value = true;
const formInline = getPostFormData(row);
addDialog({
title: type === "add" ? "新增岗位" : "更新岗位",
props: { formInline },
width: "40%",
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(PostFormModal, { ref: postFormRef }),
beforeSure: (done, { options }) => {
const formRuleRef = postFormRef.value.getFormRuleRef();
const formData = options.props.formInline as AddPostCommand &
Partial<UpdatePostCommand>;
formRuleRef.validate(valid => {
if (valid) {
submitPostForm(type, formData, () => done());
}
});
}
});
}
</script>
@@ -205,13 +255,6 @@ function openDialog(type: "add" | "update", row?: PostPageResponse) {
</pure-table>
</template>
</PureTableBar>
<post-form-modal
v-model="modalVisible"
:type="opType"
:row="opRow"
@success="onSearch"
/>
</div>
</template>
@@ -1,43 +1,24 @@
<script setup lang="ts">
import VDialog from "@/components/VDialog/VDialog.vue";
import { computed, reactive, ref } from "vue";
import {
AddPostCommand,
PostPageResponse,
UpdatePostCommand,
addPostApi,
updatePostApi
} from "@/api/system/post";
import { ref } from "vue";
import { AddPostCommand, UpdatePostCommand } from "@/api/system/post";
import { useUserStoreHook } from "@/store/modules/user";
import { ElMessage, FormInstance, FormRules } from "element-plus";
import { FormInstance, FormRules } from "element-plus";
interface Props {
type: "add" | "update";
modelValue: boolean;
row?: PostPageResponse;
formInline: AddPostCommand & Partial<UpdatePostCommand>;
}
const props = defineProps<Props>();
const emits = defineEmits<{
(e: "update:modelValue", v: boolean): void;
(e: "success"): void;
}>();
const visible = computed({
get: () => props.modelValue,
set(v) {
emits("update:modelValue", v);
}
});
const formData = reactive<AddPostCommand | UpdatePostCommand>({
postId: 0,
postCode: "",
postName: "",
postSort: 1,
remark: "",
status: ""
const props = withDefaults(defineProps<Props>(), {
formInline: () => ({
postId: 0,
postCode: "",
postName: "",
postSort: 1,
remark: "",
status: ""
})
});
const formData = ref(props.formInline);
const statusList = useUserStoreHook().dictionaryMap["common.status"];
@@ -62,70 +43,37 @@ const rules: FormRules = {
]
};
const formRef = ref<FormInstance>();
function handleOpened() {
if (props.row) {
Object.assign(formData, props.row);
} else {
formRef.value?.resetFields();
}
function getFormRuleRef() {
return formRef.value;
}
const loading = ref(false);
async function handleConfirm() {
try {
loading.value = true;
if (props.type === "add") {
await addPostApi(formData);
} else if (props.type === "update") {
await updatePostApi(formData as UpdatePostCommand);
}
ElMessage.info("提交成功");
visible.value = false;
emits("success");
} catch (e) {
console.error(e);
ElMessage.error((e as Error)?.message || "提交失败");
} finally {
loading.value = false;
}
}
defineExpose({ getFormRuleRef });
</script>
<template>
<v-dialog
show-full-screen
:fixed-body-height="false"
use-body-scrolling
:title="type === 'add' ? '新增岗位' : '更新岗位'"
v-model="visible"
:loading="loading"
@confirm="handleConfirm"
@cancel="visible = false"
@opened="handleOpened"
>
<el-form :model="formData" label-width="120px" :rules="rules" ref="formRef">
<el-form-item prop="postName" label="岗位名称" required inline-message>
<el-input v-model="formData.postName" />
</el-form-item>
<el-form-item prop="postCode" label="岗位编码" required>
<el-input v-model="formData.postCode" />
</el-form-item>
<el-form-item prop="postSort" label="岗位顺序" required>
<el-input-number :min="1" v-model="formData.postSort" />
</el-form-item>
<el-form-item prop="status" label="岗位状态">
<el-radio-group v-model="formData.status">
<el-radio
v-for="item in Object.keys(statusList)"
:key="item"
:label="statusList[item].value"
>{{ statusList[item].label }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item prop="remark" label="备注" style="margin-bottom: 0">
<el-input type="textarea" v-model="formData.remark" />
</el-form-item>
</el-form>
</v-dialog>
<el-form :model="formData" label-width="120px" :rules="rules" ref="formRef">
<el-form-item prop="postName" label="岗位名称" required inline-message>
<el-input v-model="formData.postName" />
</el-form-item>
<el-form-item prop="postCode" label="岗位编码" required>
<el-input v-model="formData.postCode" />
</el-form-item>
<el-form-item prop="postSort" label="岗位顺序" required>
<el-input-number :min="1" v-model="formData.postSort" />
</el-form-item>
<el-form-item prop="status" label="岗位状态">
<el-radio-group v-model="formData.status">
<el-radio
v-for="item in Object.keys(statusList)"
:key="item"
:label="statusList[item].value"
>{{ statusList[item].label }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item prop="remark" label="备注" style="margin-bottom: 0">
<el-input type="textarea" v-model="formData.remark" />
</el-form-item>
</el-form>
</template>
+76 -16
View File
@@ -1,15 +1,23 @@
<script setup lang="ts">
import { ref } from "vue";
import { h, ref } from "vue";
import { useRole } from "./utils/hook";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { addDialog } from "@/components/ReDialog";
import Delete from "@iconify-icons/ep/delete";
import EditPen from "@iconify-icons/ep/edit-pen";
import Search from "@iconify-icons/ep/search";
import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
import { getRoleInfoApi, RoleDTO } from "@/api/system/role";
import {
AddRoleCommand,
RoleDTO,
UpdateRoleCommand,
addRoleApi,
getRoleInfoApi,
updateRoleApi
} from "@/api/system/role";
import RoleFormModal from "@/views/system/role/role-form-modal.vue";
import { ElMessage } from "element-plus";
@@ -31,11 +39,37 @@ const {
handleDelete
} = useRole();
const opType = ref<"add" | "update">("add");
const modalVisible = ref(false);
const opRow = ref<RoleDTO>();
const roleFormRef = ref();
function getRoleFormData(row?: RoleDTO) {
return {
roleId: row?.roleId ?? 0,
dataScope: row?.dataScope?.toString() ?? "",
menuIds: row?.selectedMenuList ?? [],
remark: row?.remark ?? "",
roleKey: row?.roleKey ?? "",
roleName: row?.roleName ?? "",
roleSort: row?.roleSort ?? 1,
status: row?.status?.toString() ?? ""
};
}
async function submitRoleForm(
type: "add" | "update",
formData: AddRoleCommand & Partial<UpdateRoleCommand>,
done: () => void
) {
if (type === "add") {
await addRoleApi(formData);
} else {
await updateRoleApi(formData as UpdateRoleCommand);
}
ElMessage.success("提交成功");
done();
onSearch();
}
async function openDialog(type: "add" | "update", row?: RoleDTO) {
debugger;
try {
await getMenuTree();
if (row) {
@@ -46,10 +80,43 @@ async function openDialog(type: "add" | "update", row?: RoleDTO) {
} catch (e) {
console.error(e);
ElMessage.error((e as Error)?.message || "加载菜单失败");
return;
}
opType.value = type;
opRow.value = row;
modalVisible.value = true;
const formInline = getRoleFormData(row);
addDialog({
title: type === "add" ? "新增角色" : "更新角色",
props: {
formInline,
menuOptions: menuTree.value
},
width: "40%",
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(RoleFormModal, { ref: roleFormRef }),
beforeSure: (done, { options }) => {
const formRuleRef = roleFormRef.value.getFormRuleRef();
const formData = options.props.formInline as AddRoleCommand &
Partial<UpdateRoleCommand>;
formRuleRef.validate(valid => {
if (valid) {
submitRoleForm(type, formData, () => done());
}
});
}
});
}
function handleSelectionChange(rows: RoleDTO[]) {
void rows;
}
function handleSizeChange() {
onSearch();
}
function handleCurrentChange() {
onSearch();
}
</script>
<template>
@@ -205,13 +272,6 @@ async function openDialog(type: "add" | "update", row?: RoleDTO) {
</pure-table>
</template>
</PureTableBar>
<role-form-modal
v-model="modalVisible"
:type="opType"
:row="opRow"
:menu-options="menuTree"
/>
</div>
</template>
@@ -1,47 +1,29 @@
<script setup lang="ts">
import VDialog from "@/components/VDialog/VDialog.vue";
import { computed, reactive, ref } from "vue";
import { ref } from "vue";
import { useUserStoreHook } from "@/store/modules/user";
import { ElMessage, FormInstance, FormRules } from "element-plus";
import {
AddRoleCommand,
RoleDTO,
UpdateRoleCommand,
addRoleApi,
updateRoleApi
} from "@/api/system/role";
import { ElTree, FormInstance, FormRules } from "element-plus";
import { AddRoleCommand, UpdateRoleCommand } from "@/api/system/role";
import { MenuDTO } from "@/api/system/menu";
interface Props {
type: "add" | "update";
modelValue: boolean;
row?: RoleDTO;
formInline: AddRoleCommand & Partial<UpdateRoleCommand>;
menuOptions: MenuDTO[];
}
const props = defineProps<Props>();
const emits = defineEmits<{
(e: "update:modelValue", v: boolean): void;
(e: "success"): void;
}>();
const visible = computed({
get: () => props.modelValue,
set(v) {
emits("update:modelValue", v);
}
});
const formData = reactive<AddRoleCommand | UpdateRoleCommand>({
roleId: 0,
dataScope: "",
menuIds: [],
remark: "",
roleKey: "",
roleName: "",
roleSort: 1,
status: ""
const props = withDefaults(defineProps<Props>(), {
formInline: () => ({
roleId: 0,
dataScope: "",
menuIds: [],
remark: "",
roleKey: "",
roleName: "",
roleSort: 1,
status: ""
}),
menuOptions: () => []
});
const formData = ref(props.formInline);
const statusList = useUserStoreHook().dictionaryMap["common.status"];
@@ -66,93 +48,58 @@ const rules: FormRules = {
]
};
const formRef = ref<FormInstance>();
function handleOpened() {
console.log("opened", props.row);
if (props.row) {
Object.assign(formData, props.row);
formData.menuIds = props.row.selectedMenuList;
} else {
formRef.value?.resetFields();
}
}
const treeRef = ref<InstanceType<typeof ElTree>>();
function handleCheckChange() {
formData.menuIds = treeRef.value.getCheckedKeys(false) as number[];
formData.value.menuIds = treeRef.value.getCheckedKeys(false) as number[];
}
const loading = ref(false);
async function handleConfirm() {
try {
loading.value = true;
if (props.type === "add") {
await addRoleApi(formData);
} else if (props.type === "update") {
await updateRoleApi(formData as UpdateRoleCommand);
}
ElMessage.info("提交成功");
visible.value = false;
emits("success");
} catch (e) {
console.error(e);
ElMessage.error((e as Error)?.message || "提交失败");
} finally {
loading.value = false;
}
function getFormRuleRef() {
return formRef.value;
}
defineExpose({ getFormRuleRef });
</script>
<template>
<v-dialog
show-full-screen
fixed-body-height
use-body-scrolling
:title="type === 'add' ? '新增角色' : '更新角色'"
v-model="visible"
:loading="loading"
@confirm="handleConfirm"
@cancel="visible = false"
@opened="handleOpened"
>
<el-form :model="formData" label-width="120px" :rules="rules" ref="formRef">
<el-form-item prop="roleName" label="角色名称" required inline-message>
<el-input v-model="formData.roleName" />
</el-form-item>
<el-form-item prop="roleKey" label="权限字符" required>
<el-input v-model="formData.roleKey" />
</el-form-item>
<el-form-item prop="roleSort" label="角色顺序" required>
<el-input-number :min="1" v-model="formData.roleSort" />
</el-form-item>
<el-form-item prop="status" label="角色状态">
<el-radio-group v-model="formData.status">
<el-radio
v-for="item in Object.keys(statusList)"
:key="item"
:label="statusList[item].value"
>{{ statusList[item].label }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单权限" prop="menuIds">
<el-tree
ref="treeRef"
:props="{ label: 'menuName', children: 'children' }"
:data="props.menuOptions"
node-key="id"
check-strictly
show-checkbox
default-expand-all
check-on-click-node
:expand-on-click-node="false"
:default-checked-keys="formData.menuIds"
@check-change="handleCheckChange"
style="width: 100%"
/>
</el-form-item>
<el-form-item prop="remark" label="备注" style="margin-bottom: 0">
<el-input type="textarea" v-model="formData.remark" />
</el-form-item>
</el-form>
</v-dialog>
<el-form :model="formData" label-width="120px" :rules="rules" ref="formRef">
<el-form-item prop="roleName" label="角色名称" required inline-message>
<el-input v-model="formData.roleName" />
</el-form-item>
<el-form-item prop="roleKey" label="权限字符" required>
<el-input v-model="formData.roleKey" />
</el-form-item>
<el-form-item prop="roleSort" label="角色顺序" required>
<el-input-number :min="1" v-model="formData.roleSort" />
</el-form-item>
<el-form-item prop="status" label="角色状态">
<el-radio-group v-model="formData.status">
<el-radio
v-for="item in Object.keys(statusList)"
:key="item"
:label="statusList[item].value"
>{{ statusList[item].label }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单权限" prop="menuIds">
<el-tree
ref="treeRef"
:props="{ label: 'menuName', children: 'children' }"
:data="props.menuOptions"
node-key="id"
check-strictly
show-checkbox
default-expand-all
check-on-click-node
:expand-on-click-node="false"
:default-checked-keys="formData.menuIds"
@check-change="handleCheckChange"
style="width: 100%"
/>
</el-form-item>
<el-form-item prop="remark" label="备注" style="margin-bottom: 0">
<el-input type="textarea" v-model="formData.remark" />
</el-form-item>
</el-form>
</template>